commandIdsByAccelerator = new HashMap<>();
+
+ private void load() {
+ addAccelerator(COPY_COMMAND_ID);
+ addAccelerator(PASTE_COMMAND_ID);
+ }
+
+ private void addAccelerator(String commandId) {
+ TriggerSequence[] bindings = bindingsFor(commandId);
+ for (int i = 0; i < bindings.length; ++i) {
+ if (bindings[i] instanceof KeySequence) {
+ KeyStroke[] keyStrokes = ((KeySequence) bindings[i]).getKeyStrokes();
+ if (keyStrokes.length != 0) {
+ int accelerator = SWTKeySupport.convertKeyStrokeToAccelerator(keyStrokes[0]);
+ commandIdsByAccelerator.put(Integer.valueOf(accelerator), commandId);
+ }
+ }
+ }
+ }
+
+ private static TriggerSequence[] bindingsFor(String commandId) {
+ IBindingService bindingService = bindingService();
+ return bindingService.getActiveBindingsFor(commandId);
+ }
+
+ private static IBindingService bindingService() {
+ return PlatformUI.getWorkbench().getAdapter(IBindingService.class);
+ }
+
+ boolean isCopyAction(int accelerator) {
+ return isMatchingAction(accelerator, COPY_COMMAND_ID);
+ }
+
+ boolean isPasteAction(int accelerator) {
+ return isMatchingAction(accelerator, PASTE_COMMAND_ID);
+ }
+
+ private boolean isMatchingAction(int accelerator, String commandId) {
+ if (commandIdsByAccelerator.isEmpty()) {
+ load();
+ }
+ return commandId.equals(commandIdsByAccelerator.get(Integer.valueOf(accelerator)));
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/IVT100EmulatorBackend.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/IVT100EmulatorBackend.java
new file mode 100644
index 00000000000..1323f3a1f35
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/IVT100EmulatorBackend.java
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [433751] Add option to enable VT100 line wrapping mode
+ * Anton Leherbauer (Wind River) - [458218] Add support for ANSI insert mode
+ * Anton Leherbauer (Wind River) - [458402] Add support for scroll up/down and scroll region
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.emulator;
+
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ *
+ */
+public interface IVT100EmulatorBackend {
+
+ /**
+ * This method erases all text from the Terminal view. Including the history
+ */
+ void clearAll();
+
+ /**
+ * Sets the Dimensions of the addressable scroll space of the screen....
+ * Keeps the cursor position relative to the bottom of the screen!
+ * @param lines
+ * @param cols
+ */
+ void setDimensions(int lines, int cols);
+
+ /**
+ * 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 the line.
+ * 01234
+ * 0 123
+ */
+ void insertCharacters(int charactersToInsert);
+
+ /**
+ * Erases from cursor to end of screen, including cursor position. Cursor does not move.
+ */
+ void eraseToEndOfScreen();
+
+ /**
+ * Erases from beginning of screen to cursor, including cursor position. Cursor does not move.
+ */
+ void eraseToCursor();
+
+ /**
+ * Erases complete display. All lines are erased and changed to single-width. Cursor does not move.
+ */
+ void eraseAll();
+
+ /**
+ * Erases complete line.
+ */
+ void eraseLine();
+
+ /**
+ * Erases from cursor to end of line, including cursor position.
+ */
+ void eraseLineToEnd();
+
+ /**
+ * Erases from beginning of line to cursor, including cursor position.
+ */
+ void eraseLineToCursor();
+
+ /**
+ * Inserts n lines at line with cursor. Lines displayed below cursor move down.
+ * Lines moved past the bottom margin are lost. This sequence is ignored when
+ * cursor is outside scrolling region.
+ * @param n the number of lines to insert
+ */
+ void insertLines(int n);
+
+ /**
+ * Deletes n characters, starting with the character at cursor position.
+ * When a character is deleted, all characters to the right of cursor move
+ * left. This creates a space character at right margin. This character
+ * has same character attribute as the last character moved left.
+ * @param n
+ * 012345
+ * 0145xx
+ */
+ void deleteCharacters(int n);
+
+ /**
+ * Deletes n lines, starting at line with cursor. As lines are deleted,
+ * lines displayed below cursor move up. Lines added to bottom of screen
+ * have spaces with same character attributes as last line moved up. This
+ * sequence is ignored when cursor is outside scrolling region.
+ * @param n the number of lines to delete
+ */
+ void deleteLines(int n);
+
+ TerminalStyle getDefaultStyle();
+
+ void setDefaultStyle(TerminalStyle defaultStyle);
+
+ TerminalStyle getStyle();
+
+ /**
+ * Sets the style to be used from now on
+ * @param style
+ */
+ void setStyle(TerminalStyle 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).
+ *
+ */
+ void appendString(String buffer);
+
+ /**
+ * 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".
+ *
+ */
+ void processNewline();
+
+ /**
+ * This method returns the relative line number of the line containing the
+ * cursor. The returned line number is relative to the topmost visible line,
+ * which has relative line number 0.
+ *
+ * @return The relative line number of the line containing the cursor.
+ */
+ int getCursorLine();
+
+ int getCursorColumn();
+
+ /**
+ * 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.
+ */
+ void setCursor(int targetLine, int targetColumn);
+
+ void setCursorColumn(int targetColumn);
+
+ void setCursorLine(int targetLine);
+
+ int getLines();
+
+ int getColumns();
+
+ /**
+ * Enables VT100 line wrapping mode (default is off).
+ * This corresponds to the VT100 'eat_newline_glitch' terminal capability.
+ * If enabled, writing to the rightmost column does not cause
+ * an immediate wrap to the next line. Instead the line wrap occurs on the
+ * next output character.
+ *
+ * @param enable whether to enable or disable VT100 line wrapping mode
+ */
+ void setVT100LineWrapping(boolean enable);
+
+ /**
+ * @return whether VT100 line wrapping mode is enabled
+ */
+ boolean isVT100LineWrapping();
+
+ /**
+ * Enables/disables insert mode (IRM).
+ *
+ * @param enable whether to enable insert mode
+ */
+ void setInsertMode(boolean enable);
+
+ /**
+ * Set scrolling region. Negative values reset the scroll region.
+ *
+ * @param top top line of scroll region
+ * @param bottom bottom line of scroll region
+ */
+ void setScrollRegion(int top, int bottom);
+
+ /**
+ * Scroll text upwards.
+ *
+ * @param lines number of lines to scroll
+ */
+ void scrollUp(int lines);
+
+ /**
+ * Scroll text downwards.
+ *
+ * @param lines number of lines to scroll
+ */
+ void scrollDown(int lines);
+
+ /**
+ * Process a reverse line feed/reverse index.
+ *
+ * The content is scrolled down if the cursor is at the top of the
+ * scroll region.
+ */
+ void processReverseLineFeed();
+
+ /**
+ * Replaces characters from the cursor position with space characters.
+ *
+ * @param n number of characters to replace
+ */
+ void eraseCharacters(int n);
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/LoggingOutputStream.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/LoggingOutputStream.java
new file mode 100644
index 00000000000..24c50555b26
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/LoggingOutputStream.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.emulator;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.tm.internal.terminal.provisional.api.Logger;
+
+public class LoggingOutputStream extends FilterOutputStream {
+
+ public LoggingOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (Logger.isLogEnabled())
+ Logger.log("Received " + len + " bytes: '" + //$NON-NLS-1$ //$NON-NLS-2$
+ Logger.encode(new String(b, 0, len)) + "'"); //$NON-NLS-1$
+
+ // we cannot call super.write, because this would call our write
+ // which logs character by character.....
+ //super.write(b, off, len);
+ if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
+ throw new IndexOutOfBoundsException();
+
+ for (int i = 0; i < len; i++) {
+ super.write(b[off + i]);
+ }
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ if (Logger.isLogEnabled())
+ Logger.log("Received " + 1 + " bytes: '" + //$NON-NLS-1$ //$NON-NLS-2$
+ Logger.encode(new String(new byte[] { (byte) b }, 0, 1)) + "'"); //$NON-NLS-1$
+ super.write(b);
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100BackendTraceDecorator.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100BackendTraceDecorator.java
new file mode 100644
index 00000000000..013beb1c189
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100BackendTraceDecorator.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [433751] Add option to enable VT100 line wrapping mode
+ * Anton Leherbauer (Wind River) - [458218] Add support for ANSI insert mode
+ * Anton Leherbauer (Wind River) - [458402] Add support for scroll up/down and scroll region
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.emulator;
+
+import java.io.PrintStream;
+
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+public class VT100BackendTraceDecorator implements IVT100EmulatorBackend {
+ final IVT100EmulatorBackend fBackend;
+ final PrintStream fWriter;
+
+ public VT100BackendTraceDecorator(IVT100EmulatorBackend backend, PrintStream out) {
+ fBackend = backend;
+ fWriter = out;
+ }
+
+ @Override
+ public void appendString(String buffer) {
+ fWriter.println("appendString(\"" + buffer + "\")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.appendString(buffer);
+ }
+
+ @Override
+ public void clearAll() {
+ fWriter.println("clearAll()"); //$NON-NLS-1$
+ fBackend.clearAll();
+ }
+
+ @Override
+ public void deleteCharacters(int n) {
+ fWriter.println("deleteCharacters(" + n + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.deleteCharacters(n);
+ }
+
+ @Override
+ public void deleteLines(int n) {
+ fWriter.println("deleteLines(" + n + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.deleteLines(n);
+ }
+
+ @Override
+ public void eraseAll() {
+ fWriter.println("eraseAll()"); //$NON-NLS-1$
+ fBackend.eraseAll();
+ }
+
+ @Override
+ public void eraseLine() {
+ fWriter.println("eraseLine()"); //$NON-NLS-1$
+ fBackend.eraseLine();
+ }
+
+ @Override
+ public void eraseLineToCursor() {
+ fWriter.println("eraseLineToCursor()"); //$NON-NLS-1$
+ fBackend.eraseLineToCursor();
+ }
+
+ @Override
+ public void eraseLineToEnd() {
+ fWriter.println("eraseLineToEnd()"); //$NON-NLS-1$
+ fBackend.eraseLineToEnd();
+ }
+
+ @Override
+ public void eraseToCursor() {
+ fWriter.println("eraseToCursor()"); //$NON-NLS-1$
+ fBackend.eraseToCursor();
+ }
+
+ @Override
+ public void eraseToEndOfScreen() {
+ fWriter.println("eraseToEndOfScreen()"); //$NON-NLS-1$
+ fBackend.eraseToEndOfScreen();
+ }
+
+ @Override
+ public int getColumns() {
+ return fBackend.getColumns();
+ }
+
+ @Override
+ public int getCursorColumn() {
+ return fBackend.getCursorColumn();
+ }
+
+ @Override
+ public int getCursorLine() {
+ return fBackend.getCursorLine();
+ }
+
+ @Override
+ public TerminalStyle getDefaultStyle() {
+ return fBackend.getDefaultStyle();
+ }
+
+ @Override
+ public int getLines() {
+ return fBackend.getLines();
+ }
+
+ @Override
+ public TerminalStyle getStyle() {
+ return fBackend.getStyle();
+ }
+
+ @Override
+ public void insertCharacters(int charactersToInsert) {
+ fWriter.println("insertCharacters(" + charactersToInsert + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.insertCharacters(charactersToInsert);
+ }
+
+ @Override
+ public void insertLines(int n) {
+ fWriter.println("insertLines(" + n + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.insertLines(n);
+ }
+
+ @Override
+ public void processNewline() {
+ fWriter.println("processNewline()"); //$NON-NLS-1$
+ fBackend.processNewline();
+ }
+
+ @Override
+ public void setCursor(int targetLine, int targetColumn) {
+ fWriter.println("setCursor(" + targetLine + ", " + targetColumn + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ fBackend.setCursor(targetLine, targetColumn);
+ }
+
+ @Override
+ public void setCursorColumn(int targetColumn) {
+ fWriter.println("setCursorColumn(" + targetColumn + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.setCursorColumn(targetColumn);
+ }
+
+ @Override
+ public void setCursorLine(int targetLine) {
+ fWriter.println("setCursorLine(" + targetLine + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.setCursorLine(targetLine);
+ }
+
+ @Override
+ public void setDefaultStyle(TerminalStyle defaultStyle) {
+ fWriter.println("setDefaultStyle(" + defaultStyle + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.setDefaultStyle(defaultStyle);
+ }
+
+ @Override
+ public void setDimensions(int lines, int cols) {
+ fWriter.println("setDimensions(" + lines + "," + cols + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ fBackend.setDimensions(lines, cols);
+ }
+
+ @Override
+ public void setStyle(TerminalStyle style) {
+ fWriter.println("setStyle(" + style + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.setStyle(style);
+ }
+
+ @Override
+ public void setVT100LineWrapping(boolean enable) {
+ fWriter.println("setVT100LineWrapping(" + enable + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.setVT100LineWrapping(enable);
+ }
+
+ @Override
+ public boolean isVT100LineWrapping() {
+ return fBackend.isVT100LineWrapping();
+ }
+
+ @Override
+ public void setInsertMode(boolean enable) {
+ fWriter.println("setInsertMode(" + enable + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.setInsertMode(enable);
+ }
+
+ @Override
+ public void setScrollRegion(int top, int bottom) {
+ fWriter.println("setScrollRegion(" + top + ',' + bottom + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.setScrollRegion(top, bottom);
+ }
+
+ @Override
+ public void scrollUp(int lines) {
+ fWriter.println("scrollUp(" + lines + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.scrollUp(lines);
+ }
+
+ @Override
+ public void scrollDown(int lines) {
+ fWriter.println("scrollDown(" + lines + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.scrollDown(lines);
+ }
+
+ @Override
+ public void processReverseLineFeed() {
+ fWriter.println("processReverseLineFeed()"); //$NON-NLS-1$
+ fBackend.processReverseLineFeed();
+ }
+
+ @Override
+ public void eraseCharacters(int n) {
+ fWriter.println("eraseCharacters(" + n + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ fBackend.eraseCharacters(n);
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100Emulator.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100Emulator.java
new file mode 100644
index 00000000000..3e9323812e0
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100Emulator.java
@@ -0,0 +1,1624 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2021 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Initial Contributors:
+ * The following Wind River employees contributed to the Terminal component
+ * that contains this file: Chris Thew, Fran Litterio, Stephen Lamb,
+ * Helmut Haigermoser and Ted Williams.
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - split into core, view and connector plugins
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ * Michael Scharf (Wind River) - [209746] There are cases where some colors not displayed correctly
+ * Martin Oberhuber (Wind River) - [168197] Fix Terminal for CDC-1.1/Foundation-1.1
+ * Michael Scharf (Wind River) - [262996] get rid of TerminalState.OPENED
+ * Martin Oberhuber (Wind River) - [334969] Fix multi-command SGR sequence
+ * Kris De Volder (VMWare) - [392107] Switched interpretation for ESC[0K and ESC[1K sequences
+ * Martin Oberhuber (Wind River) - [401386] Regression: No header on top due to incorrect ESC[K interpretation
+ * Martin Oberhuber (Wind River) - [401480] Handle ESC[39;49m and ESC[G
+ * Anton Leherbauer (Wind River) - [433751] Add option to enable VT100 line wrapping mode
+ * Anton Leherbauer (Wind River) - [458218] Add support for ANSI insert mode
+ * Anton Leherbauer (Wind River) - [458398] Add support for normal/application cursor keys mode
+ * Anton Leherbauer (Wind River) - [458402] Add support for scroll up/down and scroll region
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.emulator;
+
+import static org.eclipse.tm.terminal.model.TerminalColor.BLACK;
+import static org.eclipse.tm.terminal.model.TerminalColor.BLUE;
+import static org.eclipse.tm.terminal.model.TerminalColor.CYAN;
+import static org.eclipse.tm.terminal.model.TerminalColor.GREEN;
+import static org.eclipse.tm.terminal.model.TerminalColor.MAGENTA;
+import static org.eclipse.tm.terminal.model.TerminalColor.RED;
+import static org.eclipse.tm.terminal.model.TerminalColor.WHITE;
+import static org.eclipse.tm.terminal.model.TerminalColor.YELLOW;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Arrays;
+
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.tm.internal.terminal.control.ITerminalListener3.TerminalTitleRequestor;
+import org.eclipse.tm.internal.terminal.control.impl.ITerminalControlForText;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin;
+import org.eclipse.tm.internal.terminal.provisional.api.ITerminalConnector;
+import org.eclipse.tm.internal.terminal.provisional.api.Logger;
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ * This class processes character data received from the remote host and
+ * displays it to the user using the Terminal view's StyledText widget. This
+ * class processes ANSI control characters, including NUL, backspace, carriage
+ * return, linefeed, and a subset of ANSI escape sequences sufficient to allow
+ * use of screen-oriented applications, such as vi, Emacs, and any GNU
+ * readline-enabled application (Bash, bc, ncftp, etc.).
+ *
+ *
+ * @author Fran Litterio
+ * @author Chris Thew
+ */
+public class VT100Emulator implements ControlListener {
+ /** This is a character processing state: Initial state. */
+ private static final int ANSISTATE_INITIAL = 0;
+
+ /** This is a character processing state: We've seen an escape character. */
+ private static final int ANSISTATE_ESCAPE = 1;
+
+ /**
+ * This is a character processing state: We've seen a '[' after an escape
+ * character. Expecting a parameter character or a command character next.
+ */
+ private static final int ANSISTATE_EXPECTING_PARAMETER_OR_COMMAND = 2;
+
+ /**
+ * This is a character processing state: We've seen a ']' after an escape
+ * character. We are now expecting an operating system command that
+ * reprograms an intelligent terminal.
+ */
+ private static final int ANSISTATE_EXPECTING_OS_COMMAND = 3;
+
+ /**
+ * This is a character processing state: We've seen a '[?' after an escape
+ * character. Expecting a parameter character or a command character next.
+ */
+ private static final int ANSISTATE_EXPECTING_DEC_PRIVATE_COMMAND = 4;
+
+ /**
+ * This is a character processing state: We've seen one of ()*+-./ after an escape
+ * character. Expecting a character set designation character.
+ */
+ private static final int ANSISTATE_EXPECTING_CHARSET_DESIGNATION = 5;
+
+ /**
+ * For cases where the OSC (OS Command) ends with a multi-byte ST (i.e. ESC \)
+ * we use this extra state.
+ */
+ private static final int ANSISTATE_EXPECTING_OS_COMMAND_END = 6;
+
+ /**
+ * This field holds the current state of the Finite TerminalState Automaton (FSA)
+ * that recognizes ANSI escape sequences.
+ *
+ * @see #processNewText()
+ */
+ private int ansiState = ANSISTATE_INITIAL;
+
+ /**
+ * This field holds a reference to the {@link TerminalControl} object that
+ * instantiates this class.
+ */
+ private final ITerminalControlForText terminal;
+
+ /**
+ * This field holds a reference to the StyledText widget that is used to
+ * display text to the user.
+ */
+ final private IVT100EmulatorBackend text;
+ /**
+ * This field hold the saved absolute line number of the cursor when
+ * processing the "ESC 7" and "ESC 8" command sequences.
+ */
+ private int savedCursorLine = 0;
+
+ /**
+ * This field hold the saved column number of the cursor when processing the
+ * "ESC 7" and "ESC 8" command sequences.
+ */
+ private 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".
+ */
+ private final StringBuffer[] ansiParameters = new StringBuffer[16];
+
+ /**
+ * This field holds the OS-specific command found in an escape sequence of
+ * the form "\e]...\u0007".
+ */
+ private final StringBuffer ansiOsCommand = new StringBuffer(128);
+
+ /**
+ * This field holds the index of the next unused element of the array stored
+ * in field {@link #ansiParameters}.
+ */
+ private int nextAnsiParameter = 0;
+
+ Reader fReader;
+
+ boolean fCrAfterNewLine;
+
+ /**
+ * The constructor.
+ */
+ public VT100Emulator(ITerminalTextData data, ITerminalControlForText terminal, Reader reader) {
+ super();
+
+ Logger.log("entered"); //$NON-NLS-1$
+
+ this.terminal = terminal;
+
+ for (int i = 0; i < ansiParameters.length; ++i) {
+ ansiParameters[i] = new StringBuffer();
+ }
+ setInputStreamReader(reader);
+ if (TerminalPlugin.isOptionEnabled(Logger.TRACE_DEBUG_LOG_VT100BACKEND))
+ text = new VT100BackendTraceDecorator(new VT100EmulatorBackend(data), System.out);
+ else
+ text = new VT100EmulatorBackend(data);
+
+ // text.setDimensions(24, 80);
+ TerminalStyle style = TerminalStyle.getDefaultStyle();
+ text.setDefaultStyle(style);
+ text.setStyle(style);
+ }
+
+ /**
+ * Set the reader that this Terminal gets its input from.
+ *
+ * The reader can be changed while the Terminal is running, but a change of
+ * the reader likely loses some characters which have not yet been fully
+ * read. Changing the reader can be done in order to change the selected
+ * Encoding, though. This is typically done when the Terminal is
+ * constructed, i.e. before it really starts operation; or, when the user
+ * manually selects a different encoding and thus doesn't care about losing
+ * old characters.
+ *
+ * @param reader the new Reader
+ */
+ public void setInputStreamReader(Reader reader) {
+ fReader = reader;
+ }
+
+ public void setDimensions(int lines, int cols) {
+ text.setDimensions(lines, cols);
+ ITerminalConnector telnetConnection = getConnector();
+ if (telnetConnection != null) {
+ telnetConnection.setTerminalSize(text.getColumns(), text.getLines());
+ }
+
+ }
+
+ /**
+ * This method performs clean up when this VT100Emulator object is no longer
+ * needed. After calling this method, no other method on this object should
+ * be called.
+ */
+ public void dispose() {
+ }
+
+ /**
+ * This method is required by interface ControlListener. It allows us to
+ * know when the StyledText widget is moved.
+ */
+ @Override
+ 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.
+ */
+ @Override
+ public void controlResized(ControlEvent event) {
+ Logger.log("entered"); //$NON-NLS-1$
+ adjustTerminalDimensions();
+ }
+
+ /**
+ * This method erases all text from the Terminal view.
+ */
+ public void clearTerminal() {
+ Logger.log("entered"); //$NON-NLS-1$
+ text.clearAll();
+ }
+
+ /**
+ * 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.
+ */
+ public void fontChanged() {
+ Logger.log("entered"); //$NON-NLS-1$
+
+ if (text != null)
+ adjustTerminalDimensions();
+ }
+
+ // /**
+ // * This method executes in the Display thread to process data received from
+ // * the remote host by class {@link org.eclipse.tm.internal.terminal.telnet.TelnetConnection} and
+ // * other implementors of {@link ITerminalConnector}, like the
+ // * SerialPortHandler.
+ // *
+ // * These connectors write text to the terminal's buffer through
+ // * {@link TerminalControl#writeToTerminal(String)} and then have
+ // * this run method executed in the display thread. This method
+ // * must not execute at the same time as methods
+ // * {@link #setNewText(StringBuffer)} and {@link #clearTerminal()}.
+ // *
+ // * IMPORTANT: This method must be called in strict alternation with method
+ // * {@link #setNewText(StringBuffer)}.
+ // *
+ // */
+ public void processText() {
+ try {
+ // 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?
+
+ try {
+ processNewText();
+ } catch (IOException e) {
+ Logger.logException(e);
+ }
+
+ } catch (Exception ex) {
+ Logger.logException(ex);
+ }
+ }
+
+ /**
+ * This method scans the newly received text, processing ANSI control
+ * characters and escape sequences and displaying normal text.
+ * @throws IOException
+ */
+ private void processNewText() throws IOException {
+ Logger.log("entered"); //$NON-NLS-1$
+
+ // Scan the newly received text.
+
+ while (hasNextChar()) {
+ char character = getNextChar();
+
+ switch (ansiState) {
+ case ANSISTATE_INITIAL:
+ switch (character) {
+ case '\u0000':
+ break; // NUL character. Ignore it.
+
+ case '\u0007':
+ processBEL(); // BEL (Control-G)
+ break;
+
+ case '\b':
+ processBackspace(); // Backspace
+ break;
+
+ case '\t':
+ processTab(); // Tab.
+ break;
+
+ case '\n':
+ processNewline(); // Newline (Control-J)
+ if (fCrAfterNewLine)
+ processCarriageReturn(); // Carriage Return (Control-M)
+ break;
+
+ case '\r':
+ processCarriageReturn(); // Carriage Return (Control-M)
+ break;
+
+ case '\u001b':
+ ansiState = ANSISTATE_ESCAPE; // Escape.
+ break;
+
+ default:
+ processNonControlCharacters(character);
+ 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 ')':
+ case '(':
+ case '*':
+ case '+':
+ case '-':
+ case '.':
+ case '/':
+ ansiState = ANSISTATE_EXPECTING_CHARSET_DESIGNATION;
+ break;
+
+ case '7':
+ // Save cursor position and character attributes
+
+ ansiState = ANSISTATE_INITIAL;
+ savedCursorLine = relativeCursorLine();
+ savedCursorColumn = getCursorColumn();
+ break;
+
+ case '8':
+ // Restore cursor and attributes to previously saved
+ // position
+
+ ansiState = ANSISTATE_INITIAL;
+ moveCursor(savedCursorLine, savedCursorColumn);
+ break;
+
+ case 'c':
+ // Reset the terminal
+ ansiState = ANSISTATE_INITIAL;
+ resetTerminal();
+ break;
+
+ case 'M':
+ // Reverse line feed
+ ansiState = ANSISTATE_INITIAL;
+ text.processReverseLineFeed();
+ 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:
+ // characters `<=>?` mark private commands
+ if (character >= '\u003c' && character <= '\u003f') {
+ ansiState = ANSISTATE_EXPECTING_DEC_PRIVATE_COMMAND;
+ break;
+ }
+
+ // Parameters can appear after the '[' in an escape sequence, but they
+ // are optional.
+
+ if (character >= '\u0040' && character < '\u007f') {
+ ansiState = ANSISTATE_INITIAL;
+ processAnsiCommandCharacter(character);
+ } else {
+ processAnsiParameterCharacter(character);
+ }
+ break;
+
+ case ANSISTATE_EXPECTING_OS_COMMAND:
+ // A BEL (\u0007) or ESC \ ('\e\\') character marks the end of the OSC sequence.
+
+ if (character == '\u0007') {
+ ansiState = ANSISTATE_INITIAL;
+ processAnsiOsCommand();
+ } else if (character == '\u001b') {
+ ansiState = ANSISTATE_EXPECTING_OS_COMMAND_END;
+ } else {
+ ansiOsCommand.append(character);
+ }
+ break;
+
+ case ANSISTATE_EXPECTING_OS_COMMAND_END:
+ ansiState = ANSISTATE_INITIAL;
+ if (character == '\\') {
+ processAnsiOsCommand();
+ } else {
+ Logger.log("Unsupported escape sequence: escape '" + ansiOsCommand + " \\e" + character + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ break;
+
+ case ANSISTATE_EXPECTING_DEC_PRIVATE_COMMAND:
+ // Parameters can appear after the '[?' in an escape sequence, but they
+ // are optional.
+
+ if (character >= '\u0040' && character < '\u007f') {
+ ansiState = ANSISTATE_INITIAL;
+ processDecPrivateCommandCharacter(character);
+ } else {
+ processAnsiParameterCharacter(character);
+ }
+ break;
+
+ case ANSISTATE_EXPECTING_CHARSET_DESIGNATION:
+ if (character != '%')
+ ansiState = ANSISTATE_INITIAL;
+ // Character set designation commands are ignored
+ 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;
+ break;
+ }
+ }
+ }
+
+ private void resetTerminal() {
+ text.eraseAll();
+ text.setCursor(0, 0);
+ text.setStyle(text.getDefaultStyle());
+ text.setScrollRegion(-1, -1);
+ text.setInsertMode(false);
+ terminal.enableApplicationCursorKeys(false);
+ }
+
+ /**
+ * 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.
+ */
+ private 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.setTerminalTitle(ansiOsCommand.substring(2), TerminalTitleRequestor.ANSI);
+ }
+
+ /**
+ * 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.
+ *
+ */
+ private 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?
+
+ 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 'd':
+ // Line Position Absolute [row] (default = [1,column]) (VPA).
+ 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 'h':
+ // Reset Mode.
+ 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 'l':
+ // Set Mode.
+ 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 'r':
+ // Set Scrolling Region.
+ processAnsiCommand_r();
+ break;
+
+ case 'S':
+ // Scroll up.
+ processAnsiCommand_S();
+ break;
+
+ case 'T':
+ // Scroll down.
+ processAnsiCommand_T();
+ break;
+
+ case 'X':
+ // Erase character.
+ processAnsiCommand_X();
+ 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 dispatches control to various processing methods based on the
+ * command character found in the most recently received DEC private mode escape
+ * sequence. This method only handles command characters that follow the
+ * control sequence CSI ?
+ */
+ private void processDecPrivateCommandCharacter(char commandCharacter) {
+ switch (commandCharacter) {
+ case 'h':
+ // DEC Private Mode Set (DECSET)
+ processDecPrivateCommand_h();
+ break;
+
+ case 'l':
+ // DEC Private Mode Reset (DECRST)
+ processDecPrivateCommand_l();
+ break;
+
+ default:
+ Logger.log("Ignoring unsupported DEC private command character: '" + //$NON-NLS-1$
+ commandCharacter + "'"); //$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 the line.
+ */
+ private void processAnsiCommand_atsign() {
+ int charactersToInsert = getAnsiParameter(0);
+ text.insertCharacters(charactersToInsert);
+ }
+
+ /**
+ * This method moves the cursor up by the number of lines specified by the
+ * escape sequence parameter (default 1).
+ */
+ private 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).
+ */
+ private 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).
+ */
+ private 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).
+ */
+ private void processAnsiCommand_D() {
+ moveCursorBackward(getAnsiParameter(0));
+ }
+
+ /**
+ * This method moves the cursor to a specific row.
+ */
+ private void processAnsiCommand_d() {
+ // Line Position Absolute [row] (default = [1,column]) (VPA).
+ text.setCursorLine(getAnsiParameter(0) - 1);
+ }
+
+ /**
+ * This method moves the cursor to the first column of the Nth next line,
+ * where N is specified by the ANSI parameter (default 1).
+ */
+ private 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).
+ */
+ private 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).
+ */
+ private void processAnsiCommand_G() {
+ moveCursor(relativeCursorLine(), getAnsiParameter(0) - 1);
+ }
+
+ /**
+ * This method sets the cursor to a position specified by the escape
+ * sequence parameters (default is the upper left corner of the screen).
+ */
+ private void processAnsiCommand_H() {
+ moveCursor(getAnsiParameter(0) - 1, getAnsiParameter(1) - 1);
+ }
+
+ /**
+ * This method sets terminal modes.
+ */
+ private void processAnsiCommand_h() {
+ if (getAnsiParameter(0) == 4) {
+ // set insert mode
+ text.setInsertMode(true);
+ }
+ }
+
+ /**
+ * This method deletes some (or all) of the text on the screen without
+ * moving the cursor.
+ */
+ private void processAnsiCommand_J() {
+ int ansiParameter;
+
+ if (ansiParameters[0].length() == 0)
+ ansiParameter = 0;
+ else
+ ansiParameter = getAnsiParameter(0);
+
+ switch (ansiParameter) {
+ case 0:
+ text.eraseToEndOfScreen();
+ break;
+
+ case 1:
+ // Erase from beginning to current position (inclusive).
+ text.eraseToCursor();
+ break;
+
+ case 2:
+ // Erase entire display.
+
+ text.eraseAll();
+ break;
+
+ case 3:
+ // Erase display and clear scrollback (extended "E3" capability)
+ text.clearAll();
+ break;
+
+ default:
+ Logger.log("Unexpected J-command parameter: " + ansiParameter); //$NON-NLS-1$
+ break;
+ }
+ }
+
+ /**
+ * This method deletes some (or all) of the text in the current line without
+ * moving the cursor.
+ */
+ private void processAnsiCommand_K() {
+ //Bug 401386: missing parameter must be interpreted as 0, and not 1 like most other defaults.
+ int ansiParameter = 0;
+ if (ansiParameters[0].length() > 0)
+ ansiParameter = getAnsiParameter(0);
+
+ switch (ansiParameter) {
+ case 0:
+ // Erase from current position to end (inclusive).
+ text.eraseLineToEnd();
+ break;
+
+ case 1:
+ // Erase from beginning to current position (inclusive).
+ text.eraseLineToCursor();
+ break;
+
+ case 2:
+ // Erase entire line.
+ text.eraseLine();
+ break;
+
+ default:
+ Logger.log("Unexpected K-command parameter: " + ansiParameter); //$NON-NLS-1$
+ break;
+ }
+ }
+
+ /**
+ * Insert one or more blank lines. The current line of text moves down. Text
+ * that falls off the bottom of the screen is deleted.
+ */
+ private void processAnsiCommand_L() {
+ text.insertLines(getAnsiParameter(0));
+ }
+
+ /**
+ * This method resets terminal modes.
+ */
+ private void processAnsiCommand_l() {
+ if (getAnsiParameter(0) == 4) {
+ // reset insert mode
+ text.setInsertMode(false);
+ }
+ }
+
+ /**
+ * Delete one or more lines of text. Any lines below the deleted lines move
+ * up, which we implement by appending newlines to the end of the text.
+ */
+ private void processAnsiCommand_M() {
+ text.deleteLines(getAnsiParameter(0));
+ }
+
+ /**
+ * This method sets a new graphics rendition mode, such as
+ * foreground/background color, bold/normal text, and reverse video.
+ */
+ private 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');
+ }
+ TerminalStyle style = text.getStyle();
+ // 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);
+ if (ansiParameter == 1) {
+ String parameter = ansiParameters[parameterIndex].toString();
+ // Special case for ITU's T.416 foreground/background color specification
+ // which uses : to separate parameters instead of ;
+ if (parameter.startsWith("38:") || parameter.startsWith("48:")) { //$NON-NLS-1$ //$NON-NLS-2$
+ String[] split = parameter.split(":"); //$NON-NLS-1$
+ ProcessExtendedColorsReturn retval = processExtendedColors(split, style, true);
+ style = retval.style();
+ parameterIndex++;
+ continue;
+ }
+
+ }
+
+ switch (ansiParameter) {
+ case 0:
+ // Reset all graphics modes.
+ style = text.getDefaultStyle();
+ break;
+
+ case 1:
+ style = style.setBold(true);
+ break;
+
+ case 4:
+ style = style.setUnderline(true);
+ break;
+
+ case 5:
+ style = style.setBlink(true);
+ break;
+
+ case 7:
+ style = style.setReverse(true);
+ break;
+
+ case 10: // Set primary font. Ignored.
+ break;
+
+ case 21:
+ case 22:
+ style = style.setBold(false);
+ break;
+
+ case 24:
+ style = style.setUnderline(false);
+ break;
+
+ case 25:
+ style = style.setBlink(false);
+ break;
+
+ case 27:
+ style = style.setReverse(false);
+ break;
+
+ case 90:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 30:
+ style = style.setForeground(BLACK);
+ break;
+
+ case 91:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 31:
+ style = style.setForeground(RED);
+ break;
+
+ case 92:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 32:
+ style = style.setForeground(GREEN);
+ break;
+
+ case 93:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 33:
+ style = style.setForeground(YELLOW);
+ break;
+
+ case 94:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 34:
+ style = style.setForeground(BLUE);
+ break;
+
+ case 95:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 35:
+ style = style.setForeground(MAGENTA);
+ break;
+
+ case 96:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 36:
+ style = style.setForeground(CYAN);
+ break;
+
+ case 97:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 37:
+ style = style.setForeground(WHITE);
+ break;
+
+ case 39: //Foreground: Default
+ style = style.setForeground(text.getDefaultStyle());
+ break;
+
+ case 100:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 40:
+ style = style.setBackground(BLACK);
+ break;
+
+ case 101:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 41:
+ style = style.setBackground(RED);
+ break;
+
+ case 102:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 42:
+ style = style.setBackground(GREEN);
+ break;
+
+ case 103:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 43:
+ style = style.setBackground(YELLOW);
+ break;
+
+ case 104:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 44:
+ style = style.setBackground(BLUE);
+ break;
+
+ case 105:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 45:
+ style = style.setBackground(MAGENTA);
+ break;
+
+ case 106:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 46:
+ style = style.setBackground(CYAN);
+ break;
+
+ case 107:
+ style = style.setBold(true);
+ //$FALL-THROUGH$
+ case 47:
+ style = style.setBackground(WHITE);
+ break;
+
+ case 49: //Background: Default
+ style = style.setBackground(text.getDefaultStyle());
+ break;
+
+ case 38: // Foreground color defined by sequence
+ case 48: // Background color defined by sequence
+ CharSequence[] params = Arrays.copyOfRange(ansiParameters, parameterIndex, ansiParameters.length);
+ ProcessExtendedColorsReturn retval = processExtendedColors(params, style, false);
+ parameterIndex += retval.consumed() - 1;
+ style = retval.style();
+ break;
+
+ default:
+ Logger.log("Unsupported graphics rendition parameter: " + ansiParameter); //$NON-NLS-1$
+ break;
+ }
+
+ ++parameterIndex;
+ }
+ text.setStyle(style);
+ }
+
+ private interface ProcessExtendedColorsReturn {
+ /**
+ * @return the new style
+ */
+ TerminalStyle style();
+
+ /**
+ * @return number of parameters consumed
+ */
+ int consumed();
+ }
+
+ /**
+ *
+ * @param params array of parameters, starting with 38 or 48 being the command
+ * @param colorspace if a colorspace may be included (ITU T.416 mode)
+ */
+ private ProcessExtendedColorsReturn processExtendedColors(CharSequence[] paramStrings, TerminalStyle style,
+ boolean colorspace) {
+ int params[] = new int[paramStrings.length];
+ for (int i = 0; i < params.length; i++) {
+ try {
+ int parseInt = Integer.parseInt(paramStrings[i].toString());
+ int inMagnitude = parseInt % 256;
+ int inRange = inMagnitude < 0 ? inMagnitude + 256 : inMagnitude;
+ params[i] = inRange;
+ } catch (NumberFormatException ex) {
+ params[i] = 0;
+ }
+ }
+
+ boolean foreground = params[0] == 38;
+ int consumed = 1;
+ if (params.length > 1) {
+ int colorDepth = params[1];
+ switch (colorDepth) {
+ case 2: // 24-bit RGB color
+ int r = 0, g = 0, b = 0;
+ if (colorspace) {
+ if (params.length < 6) {
+ Logger.log(
+ "Not enough parameters for 24-bit color depth, expected 5, one for color space and one for each of RGB"); //$NON-NLS-1$
+ }
+ } else {
+ if (params.length < 5) {
+ Logger.log("Not enough parameters for 24-bit color depth, expected 3, one for each of RGB"); //$NON-NLS-1$
+ }
+ }
+ int start = colorspace ? 3 : 2;
+ if (params.length > start + 0) {
+ r = params[start + 0];
+ }
+ if (params.length > start + 1) {
+ g = params[start + 1];
+ }
+ if (params.length > start + 2) {
+ b = params[start + 2];
+ }
+
+ RGB rgb = new RGB(r, g, b);
+ if (foreground) {
+ style = style.setForeground(rgb);
+ } else {
+ style = style.setBackground(rgb);
+ }
+ consumed = Math.min(6, params.length);
+ break;
+ case 5: // 8-bit color table lookup
+ int index = 0;
+ if (params.length < 3) {
+ Logger.log("Missing parameter for 8-bit color depth"); //$NON-NLS-1$
+ } else {
+ index = params[2];
+ }
+ if (foreground) {
+ style = style.setForeground(index);
+ } else {
+ style = style.setBackground(index);
+ }
+ consumed = Math.min(3, params.length);
+ break;
+ default:
+ Logger.log("Unsupported color depth " + colorDepth + " for: " + params[0]); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ } else {
+ Logger.log("Missing color depth for " + params[0]); //$NON-NLS-1$
+ }
+
+ TerminalStyle finalStyle = style;
+ int finalConsumed = consumed;
+ return new ProcessExtendedColorsReturn() {
+ @Override
+ public TerminalStyle style() {
+ return finalStyle;
+ }
+
+ @Override
+ public int consumed() {
+ return finalConsumed;
+ }
+ };
+ }
+
+ /**
+ * This method responds to an ANSI Device Status Report (DSR) command from
+ * the remote endpoint requesting the ready status or the cursor position.
+ * Requests for other kinds of status are ignored.
+ */
+ private void processAnsiCommand_n() {
+ String reply;
+
+ if (getAnsiParameter(0) == 5) {
+ // Report that the terminal is ready and has no malfunctions.
+ reply = "\u001b[0n"; //$NON-NLS-1$
+
+ } else if (getAnsiParameter(0) == 6) {
+ // Send the ANSI cursor position (which is 1-based) to the remote
+ // endpoint.
+ reply = "\u001b[" + (relativeCursorLine() + 1) + ";" + //$NON-NLS-1$ //$NON-NLS-2$
+ (getCursorColumn() + 1) + "R"; //$NON-NLS-1$
+
+ } else {
+ // Do nothing if the numeric parameter was not 5 or 6.
+ return;
+ }
+
+ try {
+ terminal.getOutputStream().write(reply.getBytes("UTF-8")); //$NON-NLS-1$
+ terminal.getOutputStream().flush();
+ } catch (IOException ex) {
+ Logger.log("Caught IOException!"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * 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.
+ */
+ private void processAnsiCommand_P() {
+ text.deleteCharacters(getAnsiParameter(0));
+ }
+
+ /**
+ * Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM).
+ */
+ private void processAnsiCommand_r() {
+ int top = 0;
+ int bottom = 0;
+ if (ansiParameters[0].length() > 0 && ansiParameters[1].length() > 0) {
+ top = getAnsiParameter(0);
+ bottom = getAnsiParameter(1);
+ }
+ text.setScrollRegion(top - 1, bottom - 1);
+ }
+
+ /**
+ * Scroll up n lines (default = 1 line).
+ */
+ private void processAnsiCommand_S() {
+ text.scrollUp(getAnsiParameter(0));
+ }
+
+ /**
+ * Scroll down n lines (default = 1 line).
+ */
+ private void processAnsiCommand_T() {
+ text.scrollDown(getAnsiParameter(0));
+ }
+
+ /**
+ * Erases n characters from cursor (default = 1 character)
+ */
+ private void processAnsiCommand_X() {
+ text.eraseCharacters(getAnsiParameter(0));
+ }
+
+ private void processDecPrivateCommand_h() {
+ int param = getAnsiParameter(0);
+ switch (param) {
+ case 1:
+ // Enable Application Cursor Keys (DECCKM)
+ terminal.enableApplicationCursorKeys(true);
+ break;
+ case 47:
+ case 1047:
+ case 1048:
+ case 1049:
+ // Use Alternate Screen Buffer (ignored).
+ break;
+ default:
+ Logger.log("Unsupported command parameter: CSI ?" + param + 'h'); //$NON-NLS-1$
+ break;
+ }
+ }
+
+ private void processDecPrivateCommand_l() {
+ int param = getAnsiParameter(0);
+ switch (param) {
+ case 1:
+ // Enable Normal Cursor Keys (DECCKM)
+ terminal.enableApplicationCursorKeys(false);
+ break;
+ case 47:
+ case 1047:
+ case 1048:
+ case 1049:
+ // Use Normal Screen Buffer (ignored, but reset scroll region).
+ text.setScrollRegion(-1, -1);
+ break;
+ default:
+ Logger.log("Unsupported command parameter: CSI ?" + param + 'l'); //$NON-NLS-1$
+ break;
+ }
+ }
+
+ /**
+ * 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 or 1 if parse failed (1 is also a legitimate value)
+ */
+ private int getAnsiParameter(int parameterIndex) {
+ if (parameterIndex < 0 || parameterIndex >= ansiParameters.length) {
+ // This should never happen.
+ return -1;
+ }
+
+ String parameter = ansiParameters[parameterIndex].toString();
+
+ if (parameter.length() == 0)
+ return 1;
+
+ // return 1 on failed parseInt
+ 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. Parameters are the (optional) characters between the leading
+ * "\e[" and the command character in an escape sequence (e.g., in the
+ * escape sequence "\e[20;10H", the parameter characters are "20;10").
+ * Parameters are integers separated by one or more ';'s.
+ */
+ private 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.
+ * @throws IOException
+ */
+ private void processNonControlCharacters(char character) throws IOException {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(character);
+ // Identify a contiguous sequence of non-control characters, starting at
+ // firstNonControlCharacterIndex in newText.
+ while (hasNextChar()) {
+ character = getNextChar();
+ if (character == '\u0000' || character == '\b' || character == '\t' || character == '\u0007'
+ || character == '\n' || character == '\r' || character == '\u001b') {
+ pushBackChar(character);
+ break;
+ }
+ buffer.append(character);
+ }
+
+ // Now insert the sequence of non-control characters in the StyledText widget
+ // at the location of the cursor.
+
+ displayNewText(buffer.toString());
+ }
+
+ /**
+ * 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).
+ *
+ */
+ private void displayNewText(String buffer) {
+ text.appendString(buffer);
+ }
+
+ /**
+ * Process a BEL (Control-G) character.
+ */
+ private void processBEL() {
+ // TODO
+ //Display.getDefault().beep();
+ }
+
+ /**
+ * Process a backspace (Control-H) character.
+ */
+ private void processBackspace() {
+ moveCursorBackward(1);
+ }
+
+ /**
+ * Process a tab (Control-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.
+ */
+ private void processTab() {
+ moveCursorForward(8 - (getCursorColumn() % 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".
+ *
+ */
+ private void processNewline() {
+ text.processNewline();
+ }
+
+ /**
+ * Process a Carriage Return (Control-M).
+ */
+ private void processCarriageReturn() {
+ text.setCursorColumn(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.
+ *
+ */
+ private 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.
+
+ // TODO
+ // if(text.getColumns()!=80 && text.getLines()!=80)
+ // text.setDimensions(24, 80);
+ // 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.
+ ITerminalConnector telnetConnection = getConnector();
+ // TODO MSA: send only if dimensions have really changed!
+ if (telnetConnection != null) {
+ telnetConnection.setTerminalSize(text.getColumns(), text.getLines());
+ }
+
+ }
+
+ private ITerminalConnector getConnector() {
+ if (terminal.getTerminalConnector() != null)
+ return terminal.getTerminalConnector();
+ return null;
+ }
+
+ /**
+ * This method returns the relative line number of the line containing the
+ * cursor. The returned line number is relative to the topmost visible line,
+ * which has relative line number 0.
+ *
+ * @return The relative line number of the line containing the cursor.
+ */
+ private int relativeCursorLine() {
+ return text.getCursorLine();
+ }
+
+ /**
+ * 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.
+ */
+ private void moveCursor(int targetLine, int targetColumn) {
+ text.setCursor(targetLine, 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.
+ */
+ private void moveCursorDown(int lines) {
+ moveCursor(relativeCursorLine() + lines, getCursorColumn());
+ }
+
+ /**
+ * 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.
+ */
+ private void moveCursorUp(int lines) {
+ moveCursor(relativeCursorLine() - lines, getCursorColumn());
+ }
+
+ /**
+ * 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.
+ */
+ private void moveCursorForward(int columnsToMove) {
+ moveCursor(relativeCursorLine(), getCursorColumn() + 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.
+ */
+ private void moveCursorBackward(int columnsToMove) {
+ moveCursor(relativeCursorLine(), getCursorColumn() - columnsToMove);
+ }
+
+ /**
+ * Resets the state of the terminal text (foreground color, background color,
+ * font style and other internal state). It essentially makes it ready for new input.
+ */
+ public void resetState() {
+ ansiState = ANSISTATE_INITIAL;
+ text.setStyle(text.getDefaultStyle());
+ text.setScrollRegion(-1, -1);
+ text.setInsertMode(false);
+ }
+
+ // public OutputStream getOutputStream() {
+ // return fTerminalInputStream.getOutputStream();
+ // }
+
+ /**
+ * Buffer for {@link #pushBackChar(char)}.
+ */
+ private int fNextChar = -1;
+
+ private char getNextChar() throws IOException {
+ int c = -1;
+ if (fNextChar != -1) {
+ c = fNextChar;
+ fNextChar = -1;
+ } else {
+ c = fReader.read();
+ }
+ // TODO: better end of file handling
+ if (c == -1)
+ c = 0;
+ return (char) c;
+ }
+
+ private boolean hasNextChar() throws IOException {
+ if (fNextChar < 0 && fReader.ready()) {
+ fNextChar = fReader.read();
+ }
+ return fNextChar >= 0;
+ }
+
+ /**
+ * Put back one character to the stream. This method can push
+ * back exactly one character. The character is the next character
+ * returned by {@link #getNextChar}
+ * @param c the character to be pushed back.
+ */
+ void pushBackChar(char c) {
+ //assert fNextChar!=-1: "Already a character waiting:"+fNextChar; //$NON-NLS-1$
+ fNextChar = c;
+ }
+
+ private int getCursorColumn() {
+ return text.getCursorColumn();
+ }
+
+ public boolean isCrAfterNewLine() {
+ return fCrAfterNewLine;
+ }
+
+ public void setCrAfterNewLine(boolean crAfterNewLine) {
+ fCrAfterNewLine = crAfterNewLine;
+ }
+
+ void setVT100LineWrapping(boolean enable) {
+ text.setVT100LineWrapping(enable);
+ }
+
+ boolean isVT100LineWrapping() {
+ return text.isVT100LineWrapping();
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackend.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackend.java
new file mode 100644
index 00000000000..14d79198ce1
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackend.java
@@ -0,0 +1,504 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [206329] Changing terminal size right after connect does not scroll properly
+ * Anton Leherbauer (Wind River) - [433751] Add option to enable VT100 line wrapping mode
+ * Anton Leherbauer (Wind River) - [458218] Add support for ANSI insert mode
+ * Anton Leherbauer (Wind River) - [458402] Add support for scroll up/down and scroll region
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.emulator;
+
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ * @noextend This class is not intended to be subclassed by clients.
+ * @noreference This class not intended to be referenced by clients.
+ * It used to be package protected, and it is public only for Unit Tests.
+ *
+ */
+public class VT100EmulatorBackend implements IVT100EmulatorBackend {
+
+ private static class ScrollRegion {
+ static final ScrollRegion FULL_WINDOW = new ScrollRegion(0, Integer.MAX_VALUE - 1);
+ private final int fTop;
+ private final int fBottom;
+
+ ScrollRegion(int top, int bottom) {
+ fTop = top;
+ fBottom = bottom;
+ }
+
+ boolean contains(int line) {
+ return line >= fTop && line <= fBottom;
+ }
+
+ int getTopLine() {
+ return fTop;
+ }
+
+ int getBottomLine() {
+ return fBottom;
+ }
+
+ int getHeight() {
+ return fBottom - fTop + 1;
+ }
+ }
+
+ /**
+ * This field holds the number of the column in which the cursor is
+ * logically positioned. The leftmost column on the screen is column 0, and
+ * column numbers increase to the right. The maximum value of this field is
+ * {@link #widthInColumns} - 1. We track the cursor column using this field
+ * to avoid having to recompute it repeatly using StyledText method calls.
+ *
+ *
+ * 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 fCursorColumn 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 the default line wrapping
+ * mode). If VT100 line wrapping mode is enabled, the cursor does not move
+ * to the next line until the next character is printed (this is known as
+ * the VT100 'eat_newline_glitch').
+ * 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.
+ *
+ */
+ private int fCursorColumn;
+ private int fCursorLine;
+ /* true if last output occurred on rightmost column
+ * and next output requires line wrap */
+ private boolean fWrapPending;
+ private boolean fInsertMode;
+ private TerminalStyle fDefaultStyle;
+ private TerminalStyle fStyle;
+ int fLines;
+ int fColumns;
+ final private ITerminalTextData fTerminal;
+ private boolean fVT100LineWrapping;
+ private ScrollRegion fScrollRegion = ScrollRegion.FULL_WINDOW;
+
+ public VT100EmulatorBackend(ITerminalTextData terminal) {
+ fTerminal = terminal;
+ }
+
+ @Override
+ public void clearAll() {
+ synchronized (fTerminal) {
+ // clear the history
+ int n = fTerminal.getHeight();
+ for (int line = 0; line < n; line++) {
+ fTerminal.cleanLine(line);
+ }
+ fTerminal.setDimensions(fLines, fTerminal.getWidth());
+ setStyle(getDefaultStyle());
+ setCursor(0, 0);
+ }
+ }
+
+ @Override
+ public void setDimensions(int lines, int cols) {
+ synchronized (fTerminal) {
+ if (lines == fLines && cols == fColumns)
+ return; // nothing to do
+ // relative cursor line
+ int cl = getCursorLine();
+ int cc = getCursorColumn();
+ int height = fTerminal.getHeight();
+ // absolute cursor line
+ int acl = cl + height - fLines;
+ int newLines = Math.max(lines, height);
+ if (lines < fLines) {
+ if (height == fLines) {
+ // if the terminal has no history, then resize by
+ // setting the size to the new size
+ // TODO We are assuming that cursor line points at end of text
+ newLines = Math.max(lines, cl + 1);
+ }
+ }
+ fLines = lines;
+ fColumns = cols;
+ // make the terminal at least as high as we need lines
+ fTerminal.setDimensions(newLines, fColumns);
+ // compute relative cursor line
+ cl = acl - (newLines - fLines);
+ setCursor(cl, cc);
+ }
+ }
+
+ /**
+ * @nooverride This method is not intended to be re-implemented or extended by clients.
+ * @noreference This method is not intended to be referenced by clients.
+ * It used to be package protected, and it is public only for Unit Tests.
+ */
+ public int toAbsoluteLine(int line) {
+ synchronized (fTerminal) {
+ return fTerminal.getHeight() - fLines + line;
+ }
+ }
+
+ @Override
+ public void insertCharacters(int charactersToInsert) {
+ synchronized (fTerminal) {
+ int line = toAbsoluteLine(fCursorLine);
+ int n = charactersToInsert;
+ for (int col = fColumns - 1; col >= fCursorColumn + n; col--) {
+ char c = fTerminal.getChar(line, col - n);
+ TerminalStyle style = fTerminal.getStyle(line, col - n);
+ fTerminal.setChar(line, col, c, style);
+ }
+ int last = Math.min(fCursorColumn + n, fColumns);
+ for (int col = fCursorColumn; col < last; col++) {
+ fTerminal.setChar(line, col, '\000', null);
+ }
+ }
+ }
+
+ @Override
+ public void eraseToEndOfScreen() {
+ synchronized (fTerminal) {
+ eraseLineToEnd();
+ for (int line = toAbsoluteLine(fCursorLine + 1); line < toAbsoluteLine(fLines); line++) {
+ fTerminal.cleanLine(line);
+ }
+ }
+
+ }
+
+ @Override
+ public void eraseToCursor() {
+ synchronized (fTerminal) {
+ for (int line = toAbsoluteLine(0); line < toAbsoluteLine(fCursorLine); line++) {
+ fTerminal.cleanLine(line);
+ }
+ eraseLineToCursor();
+ }
+ }
+
+ @Override
+ public void eraseAll() {
+ synchronized (fTerminal) {
+ for (int line = toAbsoluteLine(0); line < toAbsoluteLine(fLines); line++) {
+ fTerminal.cleanLine(line);
+ }
+ }
+ }
+
+ @Override
+ public void eraseLine() {
+ synchronized (fTerminal) {
+ fTerminal.cleanLine(toAbsoluteLine(fCursorLine));
+ }
+ }
+
+ @Override
+ public void eraseLineToEnd() {
+ synchronized (fTerminal) {
+ int line = toAbsoluteLine(fCursorLine);
+ for (int col = fCursorColumn; col < fColumns; col++) {
+ fTerminal.setChar(line, col, '\000', null);
+ }
+ }
+ }
+
+ @Override
+ public void eraseLineToCursor() {
+ synchronized (fTerminal) {
+ int line = toAbsoluteLine(fCursorLine);
+ for (int col = 0; col <= fCursorColumn; col++) {
+ fTerminal.setChar(line, col, '\000', null);
+ }
+ }
+ }
+
+ @Override
+ public void insertLines(int n) {
+ synchronized (fTerminal) {
+ if (!isCusorInScrollingRegion())
+ return;
+ assert n > 0;
+ int line = toAbsoluteLine(fCursorLine);
+ int nLines = Math.min(fTerminal.getHeight() - line, fScrollRegion.getBottomLine() - fCursorLine + 1);
+ fTerminal.scroll(line, nLines, n);
+ }
+ }
+
+ @Override
+ public void deleteCharacters(int n) {
+ synchronized (fTerminal) {
+ int line = toAbsoluteLine(fCursorLine);
+ for (int col = fCursorColumn + n; col < fColumns; col++) {
+ char c = fTerminal.getChar(line, col);
+ TerminalStyle style = fTerminal.getStyle(line, col);
+ fTerminal.setChar(line, col - n, c, style);
+ }
+ int first = Math.max(fCursorColumn, fColumns - n);
+ for (int col = first; col < fColumns; col++) {
+ fTerminal.setChar(line, col, '\000', null);
+ }
+ }
+ }
+
+ @Override
+ public void deleteLines(int n) {
+ synchronized (fTerminal) {
+ if (!isCusorInScrollingRegion())
+ return;
+ assert n > 0;
+ int line = toAbsoluteLine(fCursorLine);
+ int nLines = Math.min(fTerminal.getHeight() - line, fScrollRegion.getBottomLine() - fCursorLine + 1);
+ fTerminal.scroll(line, nLines, -n);
+ }
+ }
+
+ private boolean isCusorInScrollingRegion() {
+ return fScrollRegion.contains(fCursorLine);
+ }
+
+ @Override
+ public TerminalStyle getDefaultStyle() {
+ synchronized (fTerminal) {
+ return fDefaultStyle;
+ }
+ }
+
+ @Override
+ public void setDefaultStyle(TerminalStyle defaultStyle) {
+ synchronized (fTerminal) {
+ fDefaultStyle = defaultStyle;
+ }
+ }
+
+ @Override
+ public TerminalStyle getStyle() {
+ synchronized (fTerminal) {
+ if (fStyle == null)
+ return fDefaultStyle;
+ return fStyle;
+ }
+ }
+
+ @Override
+ public void setStyle(TerminalStyle style) {
+ synchronized (fTerminal) {
+ fStyle = style;
+ }
+ }
+
+ @Override
+ public void appendString(String buffer) {
+ synchronized (fTerminal) {
+ char[] chars = buffer.toCharArray();
+ if (fInsertMode)
+ insertCharacters(chars.length);
+ int line = toAbsoluteLine(fCursorLine);
+ int i = 0;
+ while (i < chars.length) {
+ if (fWrapPending) {
+ line = doLineWrap();
+ }
+ int n = Math.min(fColumns - fCursorColumn, chars.length - i);
+ fTerminal.setChars(line, fCursorColumn, chars, i, n, fStyle);
+ int col = fCursorColumn + n;
+ i += n;
+ // wrap needed?
+ if (col == fColumns) {
+ if (fVT100LineWrapping) {
+ // deferred line wrapping (eat_newline_glitch)
+ setCursorColumn(col - 1);
+ fWrapPending = true;
+ } else {
+ line = doLineWrap();
+ }
+ } else {
+ setCursorColumn(col);
+ }
+ }
+ }
+ }
+
+ private int doLineWrap() {
+ int line;
+ line = toAbsoluteLine(fCursorLine);
+ fTerminal.setWrappedLine(line);
+ doNewline();
+ line = toAbsoluteLine(fCursorLine);
+ setCursorColumn(0);
+ return line;
+ }
+
+ /**
+ * MUST be called from a synchronized block!
+ */
+ private void doNewline() {
+ if (fCursorLine == fScrollRegion.getBottomLine())
+ scrollUp(1);
+ else if (fCursorLine + 1 >= fLines) {
+ int h = fTerminal.getHeight();
+ fTerminal.addLine();
+ if (h != fTerminal.getHeight())
+ setCursorLine(fCursorLine + 1);
+ } else {
+ setCursorLine(fCursorLine + 1);
+ }
+ }
+
+ @Override
+ public void processNewline() {
+ synchronized (fTerminal) {
+ doNewline();
+ }
+ }
+
+ private void doReverseLineFeed() {
+ if (fCursorLine == fScrollRegion.getTopLine())
+ scrollDown(1);
+ else
+ setCursorLine(fCursorLine - 1);
+ }
+
+ @Override
+ public void processReverseLineFeed() {
+ synchronized (fTerminal) {
+ doReverseLineFeed();
+ }
+ }
+
+ @Override
+ public int getCursorLine() {
+ synchronized (fTerminal) {
+ return fCursorLine;
+ }
+ }
+
+ @Override
+ public int getCursorColumn() {
+ synchronized (fTerminal) {
+ return fCursorColumn;
+ }
+ }
+
+ @Override
+ public void setCursor(int targetLine, int targetColumn) {
+ synchronized (fTerminal) {
+ setCursorLine(targetLine);
+ setCursorColumn(targetColumn);
+ }
+ }
+
+ @Override
+ public void setCursorColumn(int targetColumn) {
+ synchronized (fTerminal) {
+ if (targetColumn < 0)
+ targetColumn = 0;
+ else if (targetColumn >= fColumns)
+ targetColumn = fColumns - 1;
+ fCursorColumn = targetColumn;
+ fWrapPending = false;
+ // We make the assumption that nobody is changing the
+ // terminal cursor except this class!
+ // This assumption gives a huge performance improvement
+ fTerminal.setCursorColumn(targetColumn);
+ }
+ }
+
+ @Override
+ public void setCursorLine(int targetLine) {
+ synchronized (fTerminal) {
+ if (targetLine < 0)
+ targetLine = 0;
+ else if (targetLine >= fLines)
+ targetLine = fLines - 1;
+ fCursorLine = targetLine;
+ // We make the assumption that nobody is changing the
+ // terminal cursor except this class!
+ // This assumption gives a huge performance improvement
+ fTerminal.setCursorLine(toAbsoluteLine(targetLine));
+ }
+ }
+
+ @Override
+ public int getLines() {
+ synchronized (fTerminal) {
+ return fLines;
+ }
+ }
+
+ @Override
+ public int getColumns() {
+ synchronized (fTerminal) {
+ return fColumns;
+ }
+ }
+
+ @Override
+ public void setVT100LineWrapping(boolean enable) {
+ fVT100LineWrapping = enable;
+ }
+
+ @Override
+ public boolean isVT100LineWrapping() {
+ return fVT100LineWrapping;
+ }
+
+ @Override
+ public void setInsertMode(boolean enable) {
+ fInsertMode = enable;
+ }
+
+ @Override
+ public void setScrollRegion(int top, int bottom) {
+ if (top < 0 || bottom < 0)
+ fScrollRegion = ScrollRegion.FULL_WINDOW;
+ else if (top < bottom)
+ fScrollRegion = new ScrollRegion(top, bottom);
+ }
+
+ @Override
+ public void scrollUp(int n) {
+ assert n > 0;
+ synchronized (fTerminal) {
+ int line = toAbsoluteLine(fScrollRegion.getTopLine());
+ int nLines = Math.min(fTerminal.getHeight() - line, fScrollRegion.getHeight());
+ fTerminal.scroll(line, nLines, -n);
+ }
+ }
+
+ @Override
+ public void scrollDown(int n) {
+ assert n > 0;
+ synchronized (fTerminal) {
+ int line = toAbsoluteLine(fScrollRegion.getTopLine());
+ int nLines = Math.min(fTerminal.getHeight() - line, fScrollRegion.getHeight());
+ fTerminal.scroll(line, nLines, n);
+ }
+ }
+
+ @Override
+ public void eraseCharacters(int n) {
+ synchronized (fTerminal) {
+ int line = toAbsoluteLine(fCursorLine);
+ int end = Math.min(fCursorColumn + n, fColumns);
+ for (int col = fCursorColumn; col < end; col++) {
+ fTerminal.setChar(line, col, '\000', null);
+ }
+ }
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100TerminalControl.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100TerminalControl.java
new file mode 100644
index 00000000000..3345e2d9ef4
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/emulator/VT100TerminalControl.java
@@ -0,0 +1,1436 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2021 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Initial Contributors:
+ * The following Wind River employees contributed to the Terminal component
+ * that contains this file: Chris Thew, Fran Litterio, Stephen Lamb,
+ * Helmut Haigermoser and Ted Williams.
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - split into core, view and connector plugins
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ * Martin Oberhuber (Wind River) - [206892] State handling: Only allow connect when CLOSED
+ * Martin Oberhuber (Wind River) - [206883] Serial Terminal leaks Jobs
+ * Martin Oberhuber (Wind River) - [208145] Terminal prints garbage after quick disconnect/reconnect
+ * Martin Oberhuber (Wind River) - [207785] NPE when trying to send char while no longer connected
+ * Michael Scharf (Wind River) - [209665] Add ability to log byte streams from terminal
+ * Ruslan Sychev (Xored Software) - [217675] NPE or SWTException when closing Terminal View while connection establishing
+ * Michael Scharf (Wing River) - [196447] The optional terminal input line should be resizeable
+ * Martin Oberhuber (Wind River) - [168197] Replace JFace MessagDialog by SWT MessageBox
+ * Martin Oberhuber (Wind River) - [204796] Terminal should allow setting the encoding to use
+ * Michael Scharf (Wind River) - [237398] Terminal get Invalid Thread Access when the title is set
+ * Martin Oberhuber (Wind River) - [240745] Pressing Ctrl+F1 in the Terminal should bring up context help
+ * Michael Scharf (Wind River) - [240098] The cursor should not blink when the terminal is disconnected
+ * Anton Leherbauer (Wind River) - [335021] Middle mouse button copy/paste does not work with the terminal
+ * Max Stepanov (Appcelerator) - [339768] Fix ANSI code for PgUp / PgDn
+ * Pawel Piech (Wind River) - [333613] "Job found still running" after shutdown
+ * Martin Oberhuber (Wind River) - [348700] Terminal unusable after disconnect
+ * Simon Bernard (Sierra Wireless) - [351424] [terminal] Terminal does not support del and insert key
+ * Martin Oberhuber (Wind River) - [265352][api] Allow setting fonts programmatically
+ * Martin Oberhuber (Wind River) - [378691][api] push Preferences into the Widget
+ * Anton Leherbauer (Wind River) - [433751] Add option to enable VT100 line wrapping mode
+ * Anton Leherbauer (Wind River) - [434294] Incorrect handling of function keys with modifiers
+ * Martin Oberhuber (Wind River) - [434294] Add Mac bindings with COMMAND
+ * Anton Leherbauer (Wind River) - [434749] UnhandledEventLoopException when copying to clipboard while the selection is empty
+ * Martin Oberhuber (Wind River) - [436612] Restore Eclipse 3.4 compatibility by using Reflection
+ * Anton Leherbauer (Wind River) - [458398] Add support for normal/application cursor keys mode
+ * Anton Leherbauer (Wind River) - [420928] Terminal widget leaks memory
+ * Davy Landman (CWI) - [475267][api] Allow custom mouse listeners
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.emulator;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.net.SocketException;
+import java.nio.charset.Charset;
+import java.util.EnumMap;
+import java.util.Map;
+
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.commands.ParameterizedCommand;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.bindings.Binding;
+import org.eclipse.jface.bindings.keys.KeySequence;
+import org.eclipse.jface.bindings.keys.KeyStroke;
+import org.eclipse.jface.bindings.keys.SWTKeySupport;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.resource.DataFormatException;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.StringConverter;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.MessageBox;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tm.internal.terminal.control.ICommandInputField;
+import org.eclipse.tm.internal.terminal.control.ITerminalListener;
+import org.eclipse.tm.internal.terminal.control.ITerminalListener2;
+import org.eclipse.tm.internal.terminal.control.ITerminalListener3;
+import org.eclipse.tm.internal.terminal.control.ITerminalListener3.TerminalTitleRequestor;
+import org.eclipse.tm.internal.terminal.control.ITerminalMouseListener;
+import org.eclipse.tm.internal.terminal.control.ITerminalViewControl;
+import org.eclipse.tm.internal.terminal.control.impl.ITerminalControlForText;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalMessages;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin;
+import org.eclipse.tm.internal.terminal.preferences.ITerminalConstants;
+import org.eclipse.tm.internal.terminal.preferences.TerminalColorPresets;
+import org.eclipse.tm.internal.terminal.provisional.api.ITerminalConnector;
+import org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl;
+import org.eclipse.tm.internal.terminal.provisional.api.Logger;
+import org.eclipse.tm.internal.terminal.provisional.api.TerminalState;
+import org.eclipse.tm.internal.terminal.textcanvas.PipedInputStream;
+import org.eclipse.tm.internal.terminal.textcanvas.PollingTextCanvasModel;
+import org.eclipse.tm.internal.terminal.textcanvas.TextCanvas;
+import org.eclipse.tm.internal.terminal.textcanvas.TextLineRenderer;
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot;
+import org.eclipse.tm.terminal.model.TerminalColor;
+import org.eclipse.tm.terminal.model.TerminalTextDataFactory;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.contexts.IContextActivation;
+import org.eclipse.ui.contexts.IContextService;
+import org.eclipse.ui.handlers.IHandlerService;
+import org.eclipse.ui.keys.IBindingService;
+
+/**
+ *
+ * This class was originally written to use nested classes, which unfortunately makes
+ * this source file larger and more complex than it needs to be. In particular, the
+ * methods in the nested classes directly access the fields of the enclosing class.
+ * One day we should pull the nested classes out into their own source files (but still
+ * in this package).
+ *
+ * @author Chris Thew
+ */
+public class VT100TerminalControl implements ITerminalControlForText, ITerminalControl, ITerminalViewControl {
+ protected final static String[] LINE_DELIMITERS = { "\n" }; //$NON-NLS-1$
+
+ /**
+ * This field holds a reference to a TerminalText object that performs all ANSI
+ * text processing on data received from the remote host and controls how text is
+ * displayed using the view's StyledText widget.
+ */
+ private final VT100Emulator fTerminalText;
+ private Display fDisplay;
+ private TextCanvas fCtlText;
+ private Composite fWndParent;
+ private Clipboard fClipboard;
+ private KeyListener fKeyHandler;
+ private final ITerminalListener fTerminalListener;
+ private String fMsg = ""; //$NON-NLS-1$
+ private TerminalFocusListener fFocusListener;
+ private ITerminalConnector fConnector;
+ private final ITerminalConnector[] fConnectors;
+ private final IPreferenceStore fPreferenceStore;
+ private boolean connectOnEnterIfClosed = true;
+
+ PipedInputStream fInputStream;
+ private Charset fCharset = Charset.defaultCharset();
+ private InputStreamReader fInputStreamReader;
+
+ private ICommandInputField fCommandInputField;
+
+ private volatile TerminalState fState;
+
+ private final ITerminalTextData fTerminalModel;
+
+ private final EditActionAccelerators editActionAccelerators = new EditActionAccelerators();
+
+ private boolean fApplicationCursorKeys;
+
+ /**
+ * Listens to changes in the preferences
+ */
+ private final IPropertyChangeListener fPreferenceListener = this::updatePreferences;
+ private final IPropertyChangeListener fFontListener = this::updateFont;
+
+ /**
+ * Is protected by synchronize on this
+ */
+ volatile private Job fJob;
+
+ private PollingTextCanvasModel fPollingTextCanvasModel;
+
+ /**
+ * In some circumstances (e.g PowerShell on Windows) the backspace
+ * character received from the keypress needs modifying. This
+ * system property allows disabling this new feature in case there
+ * are users who are negatively affected by this conversion.
+ *
+ * \b is ^H which is interpreted by the console as Ctrl + Backspace
+ * which deletes a word. \b on its own should just delete a character
+ * so we send 0x7f to do that.
+ */
+ private boolean convertBackspace = Boolean
+ .parseBoolean(System.getProperty("org.eclipse.tm.terminal.control.convertBackspace", "true")); //$NON-NLS-1$ //$NON-NLS-2$
+
+ /**
+ * Instantiate a Terminal widget.
+ * @param target Callback for notifying the owner of Terminal state changes.
+ * @param wndParent The Window parent to embed the Terminal in.
+ * @param connectors Provided connectors.
+ * @since 3.2
+ */
+ public VT100TerminalControl(ITerminalListener target, Composite wndParent, ITerminalConnector[] connectors) {
+ this(target, wndParent, connectors, null);
+ }
+
+ /**
+ * Instantiate a Terminal widget using the org.eclipse.tm.terminal.TerminalPreferencePage Preference page's
+ * default preference store.
+ * @param target Callback for notifying the owner of Terminal state changes.
+ * @param wndParent The Window parent to embed the Terminal in.
+ * @param connectors Provided connectors.
+ * @param useCommonPrefs If true, the Terminal widget will pick up settings
+ * from the org.eclipse.tm.terminal.TerminalPreferencePage Preference page.
+ * Otherwise, clients need to maintain settings themselves.
+ * @since 3.2
+ */
+ public VT100TerminalControl(ITerminalListener target, Composite wndParent, ITerminalConnector[] connectors,
+ boolean useCommonPrefs) {
+ this(target, wndParent, connectors, useCommonPrefs ? TerminalPlugin.getDefault().getPreferenceStore() : null);
+ }
+
+ /**
+ * Instantiate a Terminal widget.
+ * @param target Callback for notifying the owner of Terminal state changes.
+ * @param wndParent The Window parent to embed the Terminal in.
+ * @param connectors Provided connectors.
+ * @param preferenceStore If non-null, the Terminal widget will pick up settings
+ * from the given store.
+ * @since 5.0
+ */
+ public VT100TerminalControl(ITerminalListener target, Composite wndParent, ITerminalConnector[] connectors,
+ IPreferenceStore preferenceStore) {
+ fConnectors = connectors;
+ fPreferenceStore = preferenceStore;
+ fTerminalListener = target;
+ fTerminalModel = TerminalTextDataFactory.makeTerminalTextData();
+ fTerminalModel.setMaxHeight(1000);
+ fInputStream = new PipedInputStream(8 * 1024);
+ fTerminalText = new VT100Emulator(fTerminalModel, this, null);
+ // Use Default Charset as start, until setCharset() is called
+ setCharset(Charset.defaultCharset());
+ setupTerminal(wndParent);
+ }
+
+ @Override
+ @Deprecated
+ public void setEncoding(String encoding) throws UnsupportedEncodingException {
+ Charset charset;
+ if (encoding == null) {
+ charset = Charset.defaultCharset();
+ } else {
+ charset = Charset.forName(encoding);
+ }
+ // remember encoding if above didn't throw an exception
+ setCharset(charset);
+ }
+
+ @Override
+ public void setCharset(Charset charset) {
+ if (charset == null) {
+ charset = Charset.defaultCharset();
+ }
+ fInputStreamReader = new InputStreamReader(fInputStream, charset);
+ fCharset = charset;
+ fTerminalText.setInputStreamReader(fInputStreamReader);
+ }
+
+ @Override
+ @Deprecated
+ public String getEncoding() {
+ return fCharset.name();
+ }
+
+ @Override
+ public Charset getCharset() {
+ return fCharset;
+ }
+
+ @Override
+ public ITerminalConnector[] getConnectors() {
+ return fConnectors;
+ }
+
+ @Override
+ public void copy() {
+ copy(DND.CLIPBOARD);
+ }
+
+ private void copy(int clipboardType) {
+ String selection = getSelection();
+ if (selection.length() > 0) {
+ Object[] data = new Object[] { selection };
+ Transfer[] types = new Transfer[] { TextTransfer.getInstance() };
+ fClipboard.setContents(data, types, clipboardType);
+ }
+ }
+
+ @Override
+ public void paste() {
+ paste(DND.CLIPBOARD);
+ // TODO paste in another thread.... to avoid blocking
+ // new Thread() {
+ // public void run() {
+ // for (int i = 0; i < strText.length(); i++) {
+ // sendChar(strText.charAt(i), false);
+ // }
+ //
+ // }
+ // }.start();
+ }
+
+ private void paste(int clipboardType) {
+ TextTransfer textTransfer = TextTransfer.getInstance();
+ String strText = (String) fClipboard.getContents(textTransfer, clipboardType);
+ pasteString(strText);
+ }
+
+ /**
+ * @param strText the text to paste
+ */
+ @Override
+ public boolean pasteString(String strText) {
+ if (!isConnected())
+ return false;
+ if (strText == null)
+ return false;
+ sendString(strText);
+ return true;
+ }
+
+ @Override
+ public void selectAll() {
+ getCtlText().selectAll();
+ if (fTerminalListener instanceof ITerminalListener2) {
+ ((ITerminalListener2) fTerminalListener).setTerminalSelectionChanged();
+ }
+ }
+
+ @Override
+ public void sendKey(char character) {
+ Event event;
+ KeyEvent keyEvent;
+
+ event = new Event();
+ event.widget = getCtlText();
+ event.character = character;
+ event.keyCode = 0;
+ event.stateMask = 0;
+ event.doit = true;
+ keyEvent = new KeyEvent(event);
+
+ fKeyHandler.keyPressed(keyEvent);
+ }
+
+ @Override
+ public void clearTerminal() {
+ // The TerminalText object does all text manipulation.
+ getTerminalText().clearTerminal();
+ getCtlText().clearSelection();
+ if (fTerminalListener instanceof ITerminalListener2) {
+ ((ITerminalListener2) fTerminalListener).setTerminalSelectionChanged();
+ }
+ }
+
+ @Override
+ public Clipboard getClipboard() {
+ return fClipboard;
+ }
+
+ /**
+ * @return non null selection
+ */
+ @Override
+ public String getSelection() {
+ String txt = fCtlText.getSelectionText();
+ if (txt == null)
+ txt = ""; //$NON-NLS-1$
+ return txt;
+ }
+
+ @Override
+ public void setFocus() {
+ getCtlText().setFocus();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return getCtlText().isEmpty();
+ }
+
+ @Override
+ public boolean isDisposed() {
+ return getCtlText().isDisposed();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return fState == TerminalState.CONNECTED;
+ }
+
+ @Override
+ public void disposeTerminal() {
+ Logger.log("entered."); //$NON-NLS-1$
+ if (fPreferenceStore != null) {
+ fPreferenceStore.removePropertyChangeListener(fPreferenceListener);
+ }
+ JFaceResources.getFontRegistry().removeListener(fFontListener);
+ disconnectTerminal();
+ fClipboard.dispose();
+ getTerminalText().dispose();
+ }
+
+ @Override
+ public void connectTerminal() {
+ Logger.log("entered."); //$NON-NLS-1$
+ if (getTerminalConnector() == null)
+ return;
+ fTerminalText.resetState();
+ fApplicationCursorKeys = false;
+ if (fConnector.getInitializationErrorMessage() != null) {
+ showErrorMessage(NLS.bind(TerminalMessages.CannotConnectTo, fConnector.getName(),
+ fConnector.getInitializationErrorMessage()));
+ // we cannot connect because the connector was not initialized
+ return;
+ }
+ // clean the error message
+ setMsg(""); //$NON-NLS-1$
+ getTerminalConnector().connect(this);
+ waitForConnect();
+ }
+
+ @Override
+ public ITerminalConnector getTerminalConnector() {
+ return fConnector;
+ }
+
+ @Override
+ public void disconnectTerminal() {
+ Logger.log("entered."); //$NON-NLS-1$
+
+ //Disconnect the remote side first
+ if (getState() != TerminalState.CLOSED) {
+ if (getTerminalConnector() != null) {
+ getTerminalConnector().disconnect();
+ }
+ }
+
+ //Ensure that a new Job can be started; then clean up old Job.
+ Job job;
+ synchronized (this) {
+ job = fJob;
+ fJob = null;
+ }
+ if (job != null) {
+ job.cancel();
+ // Join job to avoid leaving job running after workbench shutdown (333613).
+ // Interrupt to be fast enough; cannot close fInputStream since it is re-used (bug 348700).
+ Thread t = job.getThread();
+ if (t != null)
+ t.interrupt();
+ try {
+ job.join();
+ } catch (InterruptedException e) {
+ }
+ }
+ fPollingTextCanvasModel.stopPolling();
+ }
+
+ private void waitForConnect() {
+ Logger.log("entered."); //$NON-NLS-1$
+
+ // TODO Eliminate the nested dispatch loop
+ do {
+ if (!fDisplay.readAndDispatch())
+ fDisplay.sleep();
+ } while (getState() == TerminalState.CONNECTING);
+
+ if (getCtlText().isDisposed()) {
+ disconnectTerminal();
+ return;
+ }
+ if (getMsg().length() > 0) {
+ showErrorMessage(getMsg());
+ disconnectTerminal();
+ return;
+ }
+ if (getCtlText().isFocusControl()) {
+ if (getState() == TerminalState.CONNECTED)
+ fFocusListener.captureKeyEvents(true);
+ }
+ fPollingTextCanvasModel.startPolling();
+ startReaderJob();
+ }
+
+ private synchronized void startReaderJob() {
+ if (fJob == null) {
+ fJob = new Job("Terminal data reader") { //$NON-NLS-1$
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ IStatus status = Status.OK_STATUS;
+ try {
+ while (true) {
+ while (fInputStream.available() == 0 && !monitor.isCanceled()) {
+ try {
+ fInputStream.waitForAvailable(500);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (monitor.isCanceled()) {
+ //Do not disconnect terminal here because another reader job may already be running
+ status = Status.CANCEL_STATUS;
+ break;
+ }
+ try {
+ // TODO: should block when no text is available!
+ fTerminalText.processText();
+ } catch (Exception e) {
+ disconnectTerminal();
+ status = new Status(IStatus.ERROR, TerminalPlugin.PLUGIN_ID, e.getLocalizedMessage(),
+ e);
+ break;
+ }
+ }
+ } finally {
+ // clean the job: start a new one when the connection gets restarted
+ // Bug 208145: make sure we do not clean an other job that's already started (since it would become a Zombie)
+ synchronized (VT100TerminalControl.this) {
+ if (fJob == this) {
+ fJob = null;
+ }
+ }
+ }
+ return status;
+ }
+
+ };
+ fJob.setSystem(true);
+ fJob.schedule();
+ }
+ }
+
+ private void showErrorMessage(String message) {
+ String strTitle = TerminalMessages.TerminalError;
+ // [168197] Replace JFace MessagDialog by SWT MessageBox
+ //MessageDialog.openError( getShell(), strTitle, message);
+ MessageBox mb = new MessageBox(getShell(), SWT.ICON_ERROR | SWT.OK);
+ mb.setText(strTitle);
+ mb.setMessage(message);
+ mb.open();
+ }
+
+ protected void sendString(String string) {
+ Logger.log(string);
+ try {
+ // Send the string after converting it to an array of bytes using the
+ // platform's default character encoding.
+ //
+ // TODO: Find a way to force this to use the ISO Latin-1 encoding.
+ // TODO: handle Encoding Errors in a better way
+
+ getOutputStream().write(string.getBytes(fCharset));
+ getOutputStream().flush();
+ } catch (SocketException socketException) {
+ displayTextInTerminal(socketException.getMessage());
+
+ String strMsg = TerminalMessages.SocketError + "!\n" + socketException.getMessage(); //$NON-NLS-1$
+ showErrorMessage(strMsg);
+
+ Logger.logException(socketException);
+
+ disconnectTerminal();
+ } catch (IOException ioException) {
+ showErrorMessage(TerminalMessages.IOError + "!\n" + ioException.getMessage());//$NON-NLS-1$
+
+ Logger.logException(ioException);
+
+ disconnectTerminal();
+ }
+ }
+
+ @Override
+ public Shell getShell() {
+ return getCtlText().getShell();
+ }
+
+ protected void sendChar(char chKey, boolean altKeyPressed) {
+ try {
+ int byteToSend = chKey;
+ OutputStream os = getOutputStream();
+ if (os == null) {
+ // Bug 207785: NPE when trying to send char while no longer connected
+ Logger.log("NOT sending '" + byteToSend + "' because no longer connected"); //$NON-NLS-1$ //$NON-NLS-2$
+ } else {
+ if (altKeyPressed) {
+ // When the ALT key is pressed at the same time that a character is
+ // typed, translate it into an ESCAPE followed by the character. The
+ // alternative in this case is to set the high bit of the character
+ // being transmitted, but that will cause input such as ALT-f to be
+ // seen as the ISO Latin-1 character '�', which can be confusing to
+ // European users running Emacs, for whom Alt-f should move forward a
+ // word instead of inserting the '�' character.
+ //
+ // TODO: Make the ESCAPE-vs-highbit behavior user configurable.
+
+ byte[] bytesToSend = String.valueOf(chKey).getBytes(fCharset);
+ StringBuilder b = new StringBuilder("sending ESC"); //$NON-NLS-1$
+ for (int i = 0; i < bytesToSend.length; i++) {
+ if (i != 0)
+ b.append(" +"); //$NON-NLS-1$
+ b.append(" '" + bytesToSend[i] + "'"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ Logger.log(b.toString());
+ os.write('\u001b');
+ os.write(bytesToSend);
+ } else {
+ byte[] bytesToSend = String.valueOf(chKey).getBytes(fCharset);
+ StringBuilder b = new StringBuilder("sending"); //$NON-NLS-1$
+ for (int i = 0; i < bytesToSend.length; i++) {
+ if (i != 0)
+ b.append(" +"); //$NON-NLS-1$
+ b.append(" '" + bytesToSend[i] + "'"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ Logger.log(b.toString());
+ os.write(bytesToSend);
+ }
+ os.flush();
+ }
+ } catch (SocketException socketException) {
+ Logger.logException(socketException);
+
+ displayTextInTerminal(socketException.getMessage());
+
+ String strMsg = TerminalMessages.SocketError + "!\n" + socketException.getMessage(); //$NON-NLS-1$
+
+ showErrorMessage(strMsg);
+ Logger.logException(socketException);
+
+ disconnectTerminal();
+ } catch (IOException ioException) {
+ Logger.logException(ioException);
+
+ displayTextInTerminal(ioException.getMessage());
+
+ String strMsg = TerminalMessages.IOError + "!\n" + ioException.getMessage(); //$NON-NLS-1$
+
+ showErrorMessage(strMsg);
+ Logger.logException(ioException);
+
+ disconnectTerminal();
+ }
+ }
+
+ @Override
+ public void setupTerminal(Composite parent) {
+ Assert.isNotNull(parent);
+ boolean wasDisposed = true;
+ TerminalState oldState = fState;
+ fState = TerminalState.CLOSED;
+ if (fClipboard != null && !fClipboard.isDisposed()) {
+ // terminal was not disposed (DnD)
+ wasDisposed = false;
+ fClipboard.dispose();
+ fPollingTextCanvasModel.stopPolling();
+ }
+ if (fWndParent != null && !fWndParent.isDisposed()) {
+ // terminal widget gets a new parent (DnD)
+ fWndParent.dispose();
+ }
+ setupControls(parent);
+ setCommandInputField(fCommandInputField);
+ setupListeners();
+ if (fPreferenceStore != null && wasDisposed) {
+ updatePreferences(null);
+ fPreferenceStore.addPropertyChangeListener(fPreferenceListener);
+ }
+ JFaceResources.getFontRegistry().addListener(fFontListener);
+ setupHelp(fWndParent, TerminalPlugin.HELP_VIEW);
+
+ if (!wasDisposed) {
+ fState = oldState;
+ }
+ }
+
+ private void updatePreferences(PropertyChangeEvent unused) {
+ int bufferLineLimit = fPreferenceStore.getInt(ITerminalConstants.PREF_BUFFERLINES);
+ boolean invert = fPreferenceStore.getBoolean(ITerminalConstants.PREF_INVERT_COLORS);
+ setBufferLineLimit(bufferLineLimit);
+ setInvertedColors(invert);
+ onTerminalColorsChanged();
+ onTerminalFontChanged();
+ }
+
+ private void onTerminalColorsChanged() {
+ Map map = new EnumMap<>(TerminalColor.class);
+ TerminalColor[] values = TerminalColor.values();
+ for (TerminalColor terminalColor : values) {
+ RGB rgb = null;
+ if (fPreferenceStore != null) {
+ try {
+ rgb = StringConverter.asRGB(
+ fPreferenceStore.getString(ITerminalConstants.getPrefForTerminalColor(terminalColor)));
+ } catch (DataFormatException dfe) {
+ // bad color, use default preset value instead
+ }
+ }
+
+ if (rgb == null) {
+ rgb = TerminalColorPresets.INSTANCE.getDefaultPreset().getRGB(terminalColor);
+ }
+ map.put(terminalColor, rgb);
+ }
+ fCtlText.updateColors(map);
+ }
+
+ private String getFontDefinition() {
+ String definition;
+ if (fPreferenceStore != null) {
+ definition = fPreferenceStore.getString(ITerminalConstants.PREF_FONT_DEFINITION);
+ } else {
+ definition = ITerminalConstants.DEFAULT_FONT_DEFINITION;
+ }
+ if (definition == null || definition.isEmpty()) {
+ definition = "org.eclipse.jface.textfont"; //$NON-NLS-1$
+ }
+ return definition;
+ }
+
+ private void onTerminalFontChanged() {
+ setFont(getFontDefinition());
+ }
+
+ private void updateFont(PropertyChangeEvent event) {
+ if (event.getProperty().equals(getFontDefinition())) {
+ onTerminalFontChanged();
+ }
+ }
+
+ @Override
+ public void setFont(String fontName) {
+ Font font = JFaceResources.getFont(fontName);
+ getCtlText().setFont(font);
+ if (fCommandInputField != null) {
+ fCommandInputField.setFont(font);
+ }
+ // Tell the TerminalControl singleton that the font has changed.
+ fCtlText.updateFont(fontName);
+ getTerminalText().fontChanged();
+ }
+
+ @Override
+ @Deprecated
+ public void setFont(Font font) {
+ getCtlText().setFont(font);
+ if (fCommandInputField != null) {
+ fCommandInputField.setFont(font);
+ }
+
+ // Tell the TerminalControl singleton that the font has changed.
+ fCtlText.onFontChange();
+ getTerminalText().fontChanged();
+ }
+
+ @Override
+ public Font getFont() {
+ return getCtlText().getFont();
+ }
+
+ @Override
+ public Control getControl() {
+ return fCtlText;
+ }
+
+ @Override
+ public Control getRootControl() {
+ return fWndParent;
+ }
+
+ protected void setupControls(Composite parent) {
+ fWndParent = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout();
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ layout.verticalSpacing = 0;
+ fWndParent.setLayout(layout);
+
+ ITerminalTextDataSnapshot snapshot = fTerminalModel.makeSnapshot();
+ // TODO how to get the initial size correctly!
+ snapshot.updateSnapshot(false);
+ fPollingTextCanvasModel = new PollingTextCanvasModel(snapshot);
+ fCtlText = new TextCanvas(fWndParent, fPollingTextCanvasModel, SWT.NONE,
+ new TextLineRenderer(fCtlText, fPollingTextCanvasModel));
+
+ fCtlText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ fCtlText.addResizeHandler((lines, columns) -> fTerminalText.setDimensions(lines, columns));
+ fCtlText.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseUp(MouseEvent e) {
+ // update selection used by middle mouse button paste
+ if (e.button == 1 && getSelection().length() > 0) {
+ copy(DND.SELECTION_CLIPBOARD);
+ }
+ }
+ });
+
+ fDisplay = getCtlText().getDisplay();
+ fClipboard = new Clipboard(fDisplay);
+ }
+
+ protected void setupListeners() {
+ fKeyHandler = new TerminalKeyHandler();
+ fFocusListener = new TerminalFocusListener();
+
+ getCtlText().addKeyListener(fKeyHandler);
+ getCtlText().addFocusListener(fFocusListener);
+
+ }
+
+ /**
+ * Setup all the help contexts for the controls.
+ */
+ protected void setupHelp(Composite parent, String id) {
+ Control[] children = parent.getChildren();
+
+ for (int nIndex = 0; nIndex < children.length; nIndex++) {
+ if (children[nIndex] instanceof Composite) {
+ setupHelp((Composite) children[nIndex], id);
+ }
+ }
+
+ PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, id);
+ }
+
+ @Override
+ public void displayTextInTerminal(String text) {
+ writeToTerminal("\r\n" + text + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private void writeToTerminal(String text) {
+ try {
+ getRemoteToTerminalOutputStream().write(text.getBytes(fCharset));
+ } catch (IOException e) {
+ // should never happen!
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public OutputStream getRemoteToTerminalOutputStream() {
+ if (Logger.isLogEnabled()) {
+ return new LoggingOutputStream(fInputStream.getOutputStream());
+ } else {
+ return fInputStream.getOutputStream();
+ }
+ }
+
+ protected boolean isLogCharEnabled() {
+ return TerminalPlugin.isOptionEnabled(Logger.TRACE_DEBUG_LOG_CHAR);
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ if (getTerminalConnector() != null)
+ return getTerminalConnector().getTerminalToRemoteStream();
+ return null;
+ }
+
+ @Override
+ public void setMsg(String msg) {
+ fMsg = msg;
+ }
+
+ public String getMsg() {
+ return fMsg;
+ }
+
+ protected TextCanvas getCtlText() {
+ return fCtlText;
+ }
+
+ public VT100Emulator getTerminalText() {
+ return fTerminalText;
+ }
+
+ protected class TerminalFocusListener implements FocusListener {
+ private IContextActivation terminalContextActivation = null;
+ private IContextActivation editContextActivation = null;
+
+ protected TerminalFocusListener() {
+ super();
+ }
+
+ @Override
+ public void focusGained(FocusEvent event) {
+ // Disable all keyboard accelerators (e.g., Control-B) so the Terminal view
+ // can see every keystroke. Without this, Emacs, vi, and Bash are unusable
+ // in the Terminal view.
+ if (getState() == TerminalState.CONNECTED)
+ captureKeyEvents(true);
+
+ IContextService contextService = PlatformUI.getWorkbench().getAdapter(IContextService.class);
+ editContextActivation = contextService.activateContext("org.eclipse.tm.terminal.EditContext"); //$NON-NLS-1$
+ }
+
+ @Override
+ public void focusLost(FocusEvent event) {
+ // Enable all keybindings.
+ captureKeyEvents(false);
+
+ // Restore the command context to its previous value.
+
+ IContextService contextService = PlatformUI.getWorkbench().getAdapter(IContextService.class);
+ contextService.deactivateContext(editContextActivation);
+ }
+
+ protected void captureKeyEvents(boolean capture) {
+ IBindingService bindingService = PlatformUI.getWorkbench().getAdapter(IBindingService.class);
+ IContextService contextService = PlatformUI.getWorkbench().getAdapter(IContextService.class);
+
+ boolean enableKeyFilter = !capture;
+ if (bindingService.isKeyFilterEnabled() != enableKeyFilter)
+ bindingService.setKeyFilterEnabled(enableKeyFilter);
+
+ if (capture && terminalContextActivation == null) {
+ // The above code fails to cause Eclipse to disable menu-activation
+ // accelerators (e.g., Alt-F for the File menu), so we set the command
+ // context to be the Terminal view's command context. This enables us to
+ // override menu-activation accelerators with no-op commands in our
+ // plugin.xml file, which enables the Terminal view to see absolutely _all_
+ // key-presses.
+ terminalContextActivation = contextService.activateContext("org.eclipse.tm.terminal.TerminalContext"); //$NON-NLS-1$
+
+ } else if (!capture && terminalContextActivation != null) {
+ contextService.deactivateContext(terminalContextActivation);
+ terminalContextActivation = null;
+ }
+ }
+ }
+
+ protected class TerminalKeyHandler extends KeyAdapter {
+ @Override
+ public void keyPressed(KeyEvent event) {
+ //TODO next 2 lines are probably obsolete now
+ if (getState() == TerminalState.CONNECTING)
+ return;
+
+ //TODO we should no longer handle copy & paste specially.
+ //Instead, we should have Ctrl+Shift always go to local since there is no escape sequence for this.
+ //On Mac, Command+Anything already goes always to local.
+ //Note that this decision means that Command will NOT be Meta in Emacs on a Remote.
+ int accelerator = SWTKeySupport.convertEventToUnmodifiedAccelerator(event);
+ if (editActionAccelerators.isCopyAction(accelerator)) {
+ copy();
+ return;
+ }
+ if (editActionAccelerators.isPasteAction(accelerator)) {
+ paste();
+ return;
+ }
+
+ // We set the event.doit to false to prevent any further processing of this
+ // key event. The only reason this is here is because I was seeing the F10
+ // key both send an escape sequence (due to this method) and switch focus
+ // to the Workbench File menu (forcing the user to click in the Terminal
+ // view again to continue entering text). This fixes that.
+
+ event.doit = false;
+
+ char character = event.character;
+ int modifierKeys = event.stateMask & SWT.MODIFIER_MASK;
+ boolean ctrlKeyPressed = (event.stateMask & SWT.CTRL) != 0;
+ boolean onlyCtrlKeyPressed = modifierKeys == SWT.CTRL;
+ boolean macCmdKeyPressed = (event.stateMask & SWT.COMMAND) != 0;
+
+ // To fix SPR 110341, we consider the Alt key to be pressed only when the
+ // Control key is _not_ also pressed. This works around a bug in SWT where,
+ // on European keyboards, the AltGr key being pressed appears to us as Control
+ // + Alt being pressed simultaneously.
+ boolean altKeyPressed = (event.stateMask & SWT.ALT) != 0 && !ctrlKeyPressed;
+
+ //if (!isConnected()) {
+ if (fState == TerminalState.CLOSED) {
+ // Pressing ENTER while not connected causes us to connect.
+ if (character == '\r' && isConnectOnEnterIfClosed()) {
+ connectTerminal();
+ return;
+ }
+
+ // Ignore all other keyboard input when not connected.
+ // Allow other key handlers (such as Ctrl+F1) do their work
+ event.doit = true;
+ return;
+ }
+
+ // Manage the Del key
+ if (event.keyCode == 0x000007f) {
+ sendString("\u001b[3~"); //$NON-NLS-1$
+ return;
+ }
+
+ // TODO Linux tty is usually expecting a DEL (^?) character
+ // but this causes issues with some telnet servers and
+ // serial connections. Workaround: stty erase ^H
+ //if (event.keyCode == SWT.BS) {
+ // sendChar(SWT.DEL, altKeyPressed);
+ // return;
+ //}
+
+ // If the event character is NUL ('\u0000'), then a special key was pressed
+ // (e.g., PageUp, PageDown, an arrow key, a function key, Shift, Alt,
+ // Control, etc.). The one exception is when the user presses Control-@,
+ // which sends a NUL character, in which case we must send the NUL to the
+ // remote endpoint. This is necessary so that Emacs will work correctly,
+ // because Control-@ (i.e., NUL) invokes Emacs' set-mark-command when Emacs
+ // is running on a terminal. When the user presses Control-@, the keyCode
+ // is 50.
+ // On a Mac, the Cmd key is always used for local commands.
+
+ if (macCmdKeyPressed || (character == '\u0000' && event.keyCode != 50)) {
+ // A special key was pressed. Figure out which one it was and send the
+ // appropriate ANSI escape sequence.
+ //
+ // IMPORTANT: Control will not enter this method for these special keys
+ // unless certain tags are present in the plugin.xml file
+ // for the Terminal view. Do not delete those tags.
+
+ String escSeq = null;
+ boolean anyModifierPressed = modifierKeys != 0;
+ boolean onlyMacCmdKeyPressed = modifierKeys == SWT.COMMAND;
+
+ switch (event.keyCode) {
+ case 0x1000001: // Up arrow.
+ if (!anyModifierPressed)
+ escSeq = fApplicationCursorKeys ? "\u001bOA" : "\u001b[A"; //$NON-NLS-1$ //$NON-NLS-2$
+ break;
+
+ case 0x1000002: // Down arrow.
+ if (!anyModifierPressed)
+ escSeq = fApplicationCursorKeys ? "\u001bOB" : "\u001b[B"; //$NON-NLS-1$ //$NON-NLS-2$
+ break;
+
+ case 0x1000003: // Left arrow.
+ if (onlyCtrlKeyPressed) {
+ escSeq = "\u001b[1;5D"; //$NON-NLS-1$
+ } else if (!anyModifierPressed) {
+ escSeq = fApplicationCursorKeys ? "\u001bOD" : "\u001b[D"; //$NON-NLS-1$ //$NON-NLS-2$
+ } else if (onlyMacCmdKeyPressed) {
+ // Cmd-Left is "Home" on the Mac
+ escSeq = fApplicationCursorKeys ? "\u001bOH" : "\u001b[H"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ break;
+
+ case 0x1000004: // Right arrow.
+ if (onlyCtrlKeyPressed) {
+ escSeq = "\u001b[1;5C"; //$NON-NLS-1$
+ } else if (!anyModifierPressed) {
+ escSeq = fApplicationCursorKeys ? "\u001bOC" : "\u001b[C"; //$NON-NLS-1$ //$NON-NLS-2$
+ } else if (onlyMacCmdKeyPressed) {
+ // Cmd-Right is "End" on the Mac
+ escSeq = fApplicationCursorKeys ? "\u001bOF" : "\u001b[F"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ break;
+
+ case 0x1000005: // PgUp key.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[5~"; //$NON-NLS-1$
+ break;
+
+ case 0x1000006: // PgDn key.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[6~"; //$NON-NLS-1$
+ break;
+
+ case 0x1000007: // Home key.
+ if (!anyModifierPressed)
+ escSeq = fApplicationCursorKeys ? "\u001bOH" : "\u001b[H"; //$NON-NLS-1$ //$NON-NLS-2$
+ break;
+
+ case 0x1000008: // End key.
+ if (!anyModifierPressed)
+ escSeq = fApplicationCursorKeys ? "\u001bOF" : "\u001b[F"; //$NON-NLS-1$ //$NON-NLS-2$
+ break;
+
+ case 0x1000009: // Insert.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[2~"; //$NON-NLS-1$
+ break;
+
+ case 0x100000a: // F1 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001bOP"; //$NON-NLS-1$
+ break;
+
+ case 0x100000b: // F2 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001bOQ"; //$NON-NLS-1$
+ break;
+
+ case 0x100000c: // F3 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001bOR"; //$NON-NLS-1$
+ break;
+
+ case 0x100000d: // F4 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001bOS"; //$NON-NLS-1$
+ break;
+
+ case 0x100000e: // F5 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[15~"; //$NON-NLS-1$
+ break;
+
+ case 0x100000f: // F6 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[17~"; //$NON-NLS-1$
+ break;
+
+ case 0x1000010: // F7 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[18~"; //$NON-NLS-1$
+ break;
+
+ case 0x1000011: // F8 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[19~"; //$NON-NLS-1$
+ break;
+
+ case 0x1000012: // F9 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[20~"; //$NON-NLS-1$
+ break;
+
+ case 0x1000013: // F10 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[21~"; //$NON-NLS-1$
+ break;
+
+ case 0x1000014: // F11 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[23~"; //$NON-NLS-1$
+ break;
+
+ case 0x1000015: // F12 key.
+ if (!anyModifierPressed)
+ escSeq = "\u001b[24~"; //$NON-NLS-1$
+ break;
+
+ default:
+ // Ignore other special keys. Control flows through this case when
+ // the user presses SHIFT, CONTROL, ALT, and any other key not
+ // handled by the above cases.
+ break;
+ }
+
+ if (escSeq == null) {
+ // Any unmapped key should be handled locally by Eclipse
+ event.doit = true;
+ processKeyBinding(event, accelerator);
+ } else
+ sendString(escSeq);
+
+ // It's ok to return here, because we never locally echo special keys.
+
+ return;
+ }
+
+ Logger.log("stateMask = " + event.stateMask); //$NON-NLS-1$
+
+ if (onlyCtrlKeyPressed) {
+ switch (character) {
+ case ' ':
+ // Send a NUL character -- many terminal emulators send NUL when
+ // Control-Space is pressed. This is used to set the mark in Emacs.
+ character = '\u0000';
+ break;
+ case '/':
+ // Ctrl+/ is undo in emacs
+ character = '\u001f';
+ break;
+ }
+ }
+
+ // see javadoc on convertBackspace for details
+ if (convertBackspace && !ctrlKeyPressed && character == '\b') {
+ character = 0x7f;
+ }
+
+ //TODO: At this point, Ctrl+M sends the same as Ctrl+Shift+M .
+ //This is undesired. Fixing this here might make the special Ctrl+Shift+C
+ //handling unnecessary further up.
+ sendChar(character, altKeyPressed);
+
+ // Now decide if we should locally echo the character we just sent. We do
+ // _not_ locally echo the character if any of these conditions are true:
+ //
+ // o This is a serial connection.
+ //
+ // o This is a TCP connection (i.e., m_telnetConnection is not null) and
+ // the remote endpoint is not a TELNET server.
+ //
+ // o The ALT (or META) key is pressed.
+ //
+ // o The character is any of the first 32 ISO Latin-1 characters except
+ // Control-I or Control-M.
+ //
+ // o The character is the DELETE character.
+
+ if (getTerminalConnector() == null || getTerminalConnector().isLocalEcho() == false || altKeyPressed
+ || (character >= '\u0001' && character < '\t') || (character > '\t' && character < '\r')
+ || (character > '\r' && character <= '\u001f') || character == '\u007f') {
+ // No local echoing.
+ return;
+ }
+
+ // Locally echo the character.
+
+ StringBuffer charBuffer = new StringBuffer();
+ charBuffer.append(character);
+
+ // If the character is a carriage return, we locally echo it as a CR + LF
+ // combination.
+
+ if (character == '\r')
+ charBuffer.append('\n');
+
+ writeToTerminal(charBuffer.toString());
+ }
+
+ /*
+ * Process given event as Eclipse key binding.
+ */
+ private void processKeyBinding(KeyEvent event, int accelerator) {
+ IBindingService bindingService = PlatformUI.getWorkbench().getAdapter(IBindingService.class);
+ KeyStroke keyStroke = SWTKeySupport.convertAcceleratorToKeyStroke(accelerator);
+ Binding binding = bindingService.getPerfectMatch(KeySequence.getInstance(keyStroke));
+ if (binding != null) {
+ ParameterizedCommand cmd = binding.getParameterizedCommand();
+ if (cmd != null) {
+ IHandlerService handlerService = PlatformUI.getWorkbench().getAdapter(IHandlerService.class);
+ Event cmdEvent = new Event();
+ cmdEvent.type = SWT.KeyDown;
+ cmdEvent.display = event.display;
+ cmdEvent.widget = event.widget;
+ cmdEvent.character = event.character;
+ cmdEvent.keyCode = event.keyCode;
+ ////Bug - KeyEvent.keyLocation was introduced in Eclipse 3.6
+ ////Use reflection for now to remain backward compatible down to Eclipse 3.4
+ //cmdEvent.keyLocation = event.keyLocation;
+ try {
+ Field f1 = event.getClass().getField("keyLocation"); //$NON-NLS-1$
+ Field f2 = cmdEvent.getClass().getField("keyLocation"); //$NON-NLS-1$
+ f2.set(cmdEvent, f1.get(event));
+ } catch (NoSuchFieldException nsfe) {
+ /* ignore, this is Eclipse 3.5 or earlier */
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ cmdEvent.stateMask = event.stateMask;
+ event.doit = false;
+ try {
+ handlerService.executeCommand(cmd, cmdEvent);
+ } catch (ExecutionException e) {
+ TerminalPlugin.getDefault().getLog()
+ .log(new Status(IStatus.ERROR, TerminalPlugin.PLUGIN_ID, e.getLocalizedMessage(), e));
+ } catch (Exception e) {
+ // ignore other exceptions from cmd execution
+ }
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public void setTerminalTitle(String title) {
+ setTerminalTitle(title, TerminalTitleRequestor.OTHER);
+ }
+
+ @SuppressWarnings("removal")
+ @Override
+ public void setTerminalTitle(String title, TerminalTitleRequestor requestor) {
+ if (fTerminalListener instanceof ITerminalListener3 listener3) {
+ listener3.setTerminalTitle(title, requestor);
+ } else {
+ fTerminalListener.setTerminalTitle(title);
+ }
+
+ }
+
+ @Override
+ public TerminalState getState() {
+ return fState;
+ }
+
+ @Override
+ public void setState(TerminalState state) {
+ fState = state;
+ fTerminalListener.setState(state);
+ // enable the (blinking) cursor if the terminal is connected
+ runAsyncInDisplayThread(() -> {
+ if (fCtlText != null && !fCtlText.isDisposed()) {
+ if (isConnected()) {
+ fCtlText.setCursorEnabled(true);
+ } else {
+ fCtlText.setCursorEnabled(false);
+ // Stop capturing all key events
+ fFocusListener.captureKeyEvents(false);
+ }
+ }
+ });
+ }
+
+ /**
+ * @param runnable run in display thread
+ */
+ private void runAsyncInDisplayThread(Runnable runnable) {
+ if (Display.findDisplay(Thread.currentThread()) != null)
+ runnable.run();
+ else if (PlatformUI.isWorkbenchRunning() && PlatformUI.getWorkbench().getDisplay() != null
+ && !PlatformUI.getWorkbench().getDisplay().isDisposed())
+ PlatformUI.getWorkbench().getDisplay().asyncExec(runnable);
+ // else should not happen and we ignore it...
+ }
+
+ @Override
+ public String getSettingsSummary() {
+ if (getTerminalConnector() != null)
+ return getTerminalConnector().getSettingsSummary();
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public void setConnector(ITerminalConnector connector) {
+ fConnector = connector;
+
+ }
+
+ @Override
+ public ICommandInputField getCommandInputField() {
+ return fCommandInputField;
+ }
+
+ @Override
+ public void setCommandInputField(ICommandInputField inputField) {
+ if (fCommandInputField != null)
+ fCommandInputField.dispose();
+ fCommandInputField = inputField;
+ if (fCommandInputField != null)
+ fCommandInputField.createControl(fWndParent, this);
+ if (fWndParent.isVisible())
+ fWndParent.layout(true);
+ }
+
+ @Override
+ public int getBufferLineLimit() {
+ return fTerminalModel.getMaxHeight();
+ }
+
+ @Override
+ public void setBufferLineLimit(int bufferLineLimit) {
+ if (bufferLineLimit <= 0)
+ return;
+ synchronized (fTerminalModel) {
+ if (fTerminalModel.getHeight() > bufferLineLimit)
+ fTerminalModel.setDimensions(bufferLineLimit, fTerminalModel.getWidth());
+ fTerminalModel.setMaxHeight(bufferLineLimit);
+ }
+ }
+
+ @Override
+ public boolean isScrollLock() {
+ return fCtlText.isScrollLock();
+ }
+
+ @Override
+ public void setScrollLock(boolean on) {
+ fCtlText.setScrollLock(on);
+ }
+
+ @Override
+ public void setInvertedColors(boolean invert) {
+ fCtlText.setInvertedColors(invert);
+ }
+
+ @Override
+ public boolean isInvertedColors() {
+ return fCtlText.isInvertedColors();
+ }
+
+ @Override
+ public final void setConnectOnEnterIfClosed(boolean on) {
+ connectOnEnterIfClosed = on;
+ }
+
+ @Override
+ public final boolean isConnectOnEnterIfClosed() {
+ return connectOnEnterIfClosed;
+ }
+
+ @Override
+ public void setVT100LineWrapping(boolean enable) {
+ getTerminalText().setVT100LineWrapping(enable);
+ }
+
+ @Override
+ public boolean isVT100LineWrapping() {
+ return getTerminalText().isVT100LineWrapping();
+ }
+
+ @Override
+ public void enableApplicationCursorKeys(boolean enable) {
+ fApplicationCursorKeys = enable;
+ }
+
+ @Override
+ public void addMouseListener(ITerminalMouseListener listener) {
+ getCtlText().addTerminalMouseListener(listener);
+ }
+
+ @Override
+ public void removeMouseListener(ITerminalMouseListener listener) {
+ getCtlText().removeTerminalMouseListener(listener);
+ }
+
+ @Override
+ public String getHoverSelection() {
+ return fCtlText.getHoverSelection();
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/ISnapshotChanges.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/ISnapshotChanges.java
new file mode 100644
index 00000000000..9f51f2150ac
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/ISnapshotChanges.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.model;
+
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+
+public interface ISnapshotChanges {
+
+ /**
+ * @param line might bigger than the number of lines....
+ */
+ void markLineChanged(int line);
+
+ /**
+ * Marks all lines in the range as changed
+ * @param line >=0
+ * @param n might be out of range
+ */
+ void markLinesChanged(int line, int n);
+
+ /**
+ * Marks all lines within the scrolling region
+ * changed and resets the scrolling information
+ */
+ void convertScrollingIntoChanges();
+
+ /**
+ * @return true if something has changed
+ */
+ boolean hasChanged();
+
+ /**
+ * @param startLine
+ * @param size
+ * @param shift
+ */
+ void scroll(int startLine, int size, int shift);
+
+ /**
+ * Mark all lines changed
+ * @param height if no window is set this is the number of
+ * lines that are marked as changed
+ */
+ void setAllChanged(int height);
+
+ int getFirstChangedLine();
+
+ int getLastChangedLine();
+
+ int getScrollWindowStartLine();
+
+ int getScrollWindowSize();
+
+ int getScrollWindowShift();
+
+ boolean hasLineChanged(int line);
+
+ void markDimensionsChanged();
+
+ boolean hasDimensionsChanged();
+
+ void markCursorChanged();
+
+ /**
+ * @return true if the terminal data has changed
+ */
+ boolean hasTerminalChanged();
+
+ /**
+ * mark the terminal as changed
+ */
+ void setTerminalChanged();
+
+ void copyChangedLines(ITerminalTextData dest, ITerminalTextData source);
+
+ /**
+ * @param startLine -1 means follow the end of the data
+ * @param size number of lines to follow
+ */
+ void setInterestWindow(int startLine, int size);
+
+ int getInterestWindowStartLine();
+
+ int getInterestWindowSize();
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/SnapshotChanges.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/SnapshotChanges.java
new file mode 100644
index 00000000000..c1b4638c5dc
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/SnapshotChanges.java
@@ -0,0 +1,421 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.model;
+
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+
+/**
+ * Collects the changes of the {@link ITerminalTextData}
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ * @noreference This class is not intended to be referenced by clients.
+ * @noinstantiate This class is not intended to be instantiated by clients.
+ * This class used to be package-protected. It is public only for access by the Unit Tests.
+ */
+public class SnapshotChanges implements ISnapshotChanges {
+ /**
+ * The first line changed
+ */
+ private int fFirstChangedLine;
+ /**
+ * The last line changed
+ */
+ private int fLastChangedLine;
+ private int fScrollWindowStartLine;
+ private int fScrollWindowSize;
+ private int fScrollWindowShift;
+ /**
+ * true, if scrolling should not tracked anymore
+ */
+ private boolean fScrollDontTrack;
+ /**
+ * The lines that need to be copied
+ * into the snapshot (lines that have
+ * not changed don't have to be copied)
+ */
+ private boolean[] fChangedLines;
+
+ private int fInterestWindowSize;
+ private int fInterestWindowStartLine;
+ private boolean fDimensionsChanged;
+ private boolean fTerminalHasChanged;
+ private boolean fCursorHasChanged;
+
+ public SnapshotChanges(int nLines) {
+ setChangedLinesLength(nLines);
+ fFirstChangedLine = Integer.MAX_VALUE;
+ fLastChangedLine = -1;
+ }
+
+ public SnapshotChanges(int windowStart, int windowSize) {
+ setChangedLinesLength(windowStart + windowSize);
+ fFirstChangedLine = Integer.MAX_VALUE;
+ fLastChangedLine = -1;
+ fInterestWindowStartLine = windowStart;
+ fInterestWindowSize = windowSize;
+
+ }
+
+ /**
+ * This is used in asserts to throw an {@link RuntimeException}.
+ * This is useful for tests.
+ * @return never -- throws an exception
+ */
+ private boolean throwRuntimeException() {
+ throw new RuntimeException();
+ }
+
+ /**
+ * @param line
+ * @param size
+ * @return true if the range overlaps with the interest window
+ * @nooverride This method is not intended to be re-implemented or extended by clients.
+ * @noreference This method is not intended to be referenced by clients.
+ * It used to be package protected, and it is public only for Unit Tests.
+ */
+ public boolean isInInterestWindow(int line, int size) {
+ if (fInterestWindowSize <= 0)
+ return true;
+ if (line + size <= fInterestWindowStartLine || line >= fInterestWindowStartLine + fInterestWindowSize)
+ return false;
+ return true;
+ }
+
+ /**
+ * @param line
+ * @return true if the line is within the interest window
+ * @nooverride This method is not intended to be re-implemented or extended by clients.
+ * @noreference This method is not intended to be referenced by clients.
+ * It used to be package protected, and it is public only for Unit Tests.
+ */
+ public boolean isInInterestWindow(int line) {
+ if (fInterestWindowSize <= 0)
+ return true;
+ if (line < fInterestWindowStartLine || line >= fInterestWindowStartLine + fInterestWindowSize)
+ return false;
+ return true;
+ }
+
+ /**
+ * @param line
+ * @return the line within the window
+ * @nooverride This method is not intended to be re-implemented or extended by clients.
+ * @noreference This method is not intended to be referenced by clients.
+ * It used to be package protected, and it is public only for Unit Tests.
+ */
+ public int fitLineToWindow(int line) {
+ if (fInterestWindowSize <= 0)
+ return line;
+ if (line < fInterestWindowStartLine)
+ return fInterestWindowStartLine;
+ return line;
+ }
+
+ /**
+ * The result is only defined if {@link #isInInterestWindow(int, int)} returns true!
+ * @param line the line before {@link #fitLineToWindow(int)} has been called!
+ * @param size
+ * @return the adjusted size.
+ * @nooverride This method is not intended to be re-implemented or extended by clients.
+ * @noreference This method is not intended to be referenced by clients.
+ * It used to be package protected, and it is public only for Unit Tests.
+ *
+ * Note:
{@link #fitLineToWindow(int)} has to be called on the line to
+ * move the window correctly!
+ */
+ public int fitSizeToWindow(int line, int size) {
+ if (fInterestWindowSize <= 0)
+ return size;
+ if (line < fInterestWindowStartLine) {
+ size -= fInterestWindowStartLine - line;
+ line = fInterestWindowStartLine;
+ }
+ if (line + size > fInterestWindowStartLine + fInterestWindowSize)
+ size = fInterestWindowStartLine + fInterestWindowSize - line;
+ return size;
+ }
+
+ @Override
+ public void markLineChanged(int line) {
+ if (!isInInterestWindow(line))
+ return;
+ line = fitLineToWindow(line);
+ if (line < fFirstChangedLine)
+ fFirstChangedLine = line;
+ if (line > fLastChangedLine)
+ fLastChangedLine = line;
+ // in case the terminal got resized we expand
+ // don't remember the changed line because
+ // there is nothing to copy
+ if (line < getChangedLineLength()) {
+ setChangedLine(line, true);
+ }
+ }
+
+ @Override
+ public void markLinesChanged(int line, int n) {
+ if (n <= 0 || !isInInterestWindow(line, n))
+ return;
+ // do not exceed the bounds of fChangedLines
+ // the terminal might have been resized and
+ // we can only keep changes for the size of the
+ // previous terminal
+ n = fitSizeToWindow(line, n);
+ line = fitLineToWindow(line);
+ int m = Math.min(line + n, getChangedLineLength());
+ for (int i = line; i < m; i++) {
+ setChangedLine(i, true);
+ }
+ // this sets fFirstChangedLine as well
+ markLineChanged(line);
+ // this sets fLastChangedLine as well
+ markLineChanged(line + n - 1);
+ }
+
+ @Override
+ public void markCursorChanged() {
+ fCursorHasChanged = true;
+ }
+
+ @Override
+ public void convertScrollingIntoChanges() {
+ markLinesChanged(fScrollWindowStartLine, fScrollWindowSize);
+ fScrollWindowStartLine = 0;
+ fScrollWindowSize = 0;
+ fScrollWindowShift = 0;
+ }
+
+ @Override
+ public boolean hasChanged() {
+ if (fFirstChangedLine != Integer.MAX_VALUE || fLastChangedLine > 0 || fScrollWindowShift != 0
+ || fDimensionsChanged || fCursorHasChanged)
+ return true;
+ return false;
+ }
+
+ @Override
+ public void markDimensionsChanged() {
+ fDimensionsChanged = true;
+ }
+
+ @Override
+ public boolean hasDimensionsChanged() {
+ return fDimensionsChanged;
+ }
+
+ @Override
+ public boolean hasTerminalChanged() {
+ return fTerminalHasChanged;
+ }
+
+ @Override
+ public void setTerminalChanged() {
+ fTerminalHasChanged = true;
+ }
+
+ @Override
+ public void scroll(int startLine, int size, int shift) {
+ size = fitSizeToWindow(startLine, size);
+ startLine = fitLineToWindow(startLine);
+ // let's track only negative shifts
+ if (fScrollDontTrack) {
+ // we are in a state where we cannot track scrolling
+ // so let's simply mark the scrolled lines as changed
+ markLinesChanged(startLine, size);
+ } else if (shift >= 0) {
+ // we cannot handle positive scroll
+ // forget about clever caching of scroll events
+ doNotTrackScrollingAnymore();
+ // mark all lines inside the scroll region as changed
+ markLinesChanged(startLine, size);
+ } else {
+ // we have already scrolled
+ if (fScrollWindowShift < 0) {
+ // we have already scrolled
+ if (fScrollWindowStartLine == startLine && fScrollWindowSize == size) {
+ // we are scrolling the same region again?
+ fScrollWindowShift += shift;
+ scrollChangesLinesWithNegativeShift(startLine, size, shift);
+ } else {
+ // mark all lines in the old scroll region as changed
+ doNotTrackScrollingAnymore();
+ // mark all lines changed, because
+ markLinesChanged(startLine, size);
+ }
+ } else {
+ // first scroll in this change -- we just notify it
+ fScrollWindowStartLine = startLine;
+ fScrollWindowSize = size;
+ fScrollWindowShift = shift;
+ scrollChangesLinesWithNegativeShift(startLine, size, shift);
+ }
+ }
+ }
+
+ /**
+ * Some incompatible scrolling occurred. We cannot do the
+ * scroll optimization anymore...
+ */
+ private void doNotTrackScrollingAnymore() {
+ if (fScrollWindowSize > 0) {
+ // convert the current scrolling into changes
+ markLinesChanged(fScrollWindowStartLine, fScrollWindowSize);
+ fScrollWindowStartLine = 0;
+ fScrollWindowSize = 0;
+ fScrollWindowShift = 0;
+ }
+ // don't be clever on scrolling anymore
+ fScrollDontTrack = true;
+ }
+
+ /**
+ * Scrolls the changed lines data
+ *
+ * @param line
+ * @param n
+ * @param shift must be negative!
+ */
+ private void scrollChangesLinesWithNegativeShift(int line, int n, int shift) {
+ assert shift < 0 || throwRuntimeException();
+ // scroll the region
+ // don't run out of bounds!
+ int m = Math.min(line + n + shift, getChangedLineLength() + shift);
+ for (int i = line; i < m; i++) {
+ setChangedLine(i, hasLineChanged(i - shift));
+ // move the first changed line up.
+ // We don't have to move the maximum down,
+ // because with a shift scroll, the max is moved
+ // my the next loop in this method
+ if (i < fFirstChangedLine && hasLineChanged(i)) {
+ fFirstChangedLine = i;
+ }
+ }
+ // mark the "opened" lines as changed
+ for (int i = Math.max(0, line + n + shift); i < line + n; i++) {
+ markLineChanged(i);
+ }
+ }
+
+ @Override
+ public void setAllChanged(int height) {
+ fScrollWindowStartLine = 0;
+ fScrollWindowSize = 0;
+ fScrollWindowShift = 0;
+ fFirstChangedLine = fitLineToWindow(0);
+ fLastChangedLine = fFirstChangedLine + fitSizeToWindow(0, height) - 1;
+ // no need to keep an array of changes anymore
+ setChangedLinesLength(0);
+ }
+
+ @Override
+ public int getFirstChangedLine() {
+ return fFirstChangedLine;
+ }
+
+ @Override
+ public int getLastChangedLine() {
+ return fLastChangedLine;
+ }
+
+ @Override
+ public int getScrollWindowStartLine() {
+ return fScrollWindowStartLine;
+ }
+
+ @Override
+ public int getScrollWindowSize() {
+ return fScrollWindowSize;
+ }
+
+ @Override
+ public int getScrollWindowShift() {
+ return fScrollWindowShift;
+ }
+
+ @Override
+ public void copyChangedLines(ITerminalTextData dest, ITerminalTextData source) {
+ int n = Math.min(fLastChangedLine + 1, source.getHeight());
+ for (int i = fFirstChangedLine; i < n; i++) {
+ if (hasLineChanged(i))
+ dest.copyLine(source, i, i);
+ }
+ }
+
+ @Override
+ public int getInterestWindowSize() {
+ return fInterestWindowSize;
+ }
+
+ @Override
+ public int getInterestWindowStartLine() {
+ return fInterestWindowStartLine;
+ }
+
+ @Override
+ public void setInterestWindow(int startLine, int size) {
+ int oldStartLine = fInterestWindowStartLine;
+ int oldSize = fInterestWindowSize;
+ fInterestWindowStartLine = startLine;
+ fInterestWindowSize = size;
+ if (oldSize > 0) {
+ int shift = oldStartLine - startLine;
+ if (shift == 0) {
+ if (size > oldSize) {
+ // add lines to the end
+ markLinesChanged(oldStartLine + oldSize, size - oldSize);
+ }
+ // else no lines within the window have changed
+
+ } else if (Math.abs(shift) < size) {
+ if (shift < 0) {
+ // we can scroll
+ scroll(startLine, oldSize, shift);
+ // mark the lines at the end as new
+ for (int i = oldStartLine + oldSize; i < startLine + size; i++) {
+ markLineChanged(i);
+ }
+ } else {
+ // we cannot shift positive -- mark all changed
+ markLinesChanged(startLine, size);
+ }
+ } else {
+ // no scrolling possible
+ markLinesChanged(startLine, size);
+ }
+
+ }
+ }
+
+ @Override
+ public boolean hasLineChanged(int line) {
+ if (line < fChangedLines.length)
+ return fChangedLines[line];
+ // since the height of the terminal could
+ // have changed but we have tracked only changes
+ // of the previous terminal height, any line outside
+ // the the range of the previous height has changed
+ return isInInterestWindow(line);
+ }
+
+ int getChangedLineLength() {
+ return fChangedLines.length;
+ }
+
+ void setChangedLine(int line, boolean changed) {
+ fChangedLines[line] = changed;
+ }
+
+ void setChangedLinesLength(int length) {
+ fChangedLines = new boolean[length];
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/SynchronizedTerminalTextData.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/SynchronizedTerminalTextData.java
new file mode 100644
index 00000000000..54f637a78ab
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/SynchronizedTerminalTextData.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [453393] Add support for copying wrapped lines without line break
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.model;
+
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot;
+import org.eclipse.tm.terminal.model.LineSegment;
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ * This is a decorator to make all access to
+ * ITerminalTextData synchronized
+ *
+ */
+public class SynchronizedTerminalTextData implements ITerminalTextData {
+ final ITerminalTextData fData;
+
+ public SynchronizedTerminalTextData(ITerminalTextData data) {
+ fData = data;
+ }
+
+ @Override
+ synchronized public void addLine() {
+ fData.addLine();
+ }
+
+ @Override
+ synchronized public void cleanLine(int line) {
+ fData.cleanLine(line);
+ }
+
+ @Override
+ synchronized public void copy(ITerminalTextData source) {
+ fData.copy(source);
+ }
+
+ @Override
+ synchronized public void copyLine(ITerminalTextData source, int sourceLine, int destLine) {
+ fData.copyLine(source, sourceLine, destLine);
+ }
+
+ @Override
+ synchronized public void copyRange(ITerminalTextData source, int sourceStartLine, int destStartLine, int length) {
+ fData.copyRange(source, sourceStartLine, destStartLine, length);
+ }
+
+ @Override
+ synchronized public char getChar(int line, int column) {
+ return fData.getChar(line, column);
+ }
+
+ @Override
+ synchronized public char[] getChars(int line) {
+ return fData.getChars(line);
+ }
+
+ @Override
+ synchronized public int getCursorColumn() {
+ return fData.getCursorColumn();
+ }
+
+ @Override
+ synchronized public int getCursorLine() {
+ return fData.getCursorLine();
+ }
+
+ @Override
+ synchronized public int getHeight() {
+ return fData.getHeight();
+ }
+
+ @Override
+ synchronized public LineSegment[] getLineSegments(int line, int startCol, int numberOfCols) {
+ return fData.getLineSegments(line, startCol, numberOfCols);
+ }
+
+ @Override
+ synchronized public int getMaxHeight() {
+ return fData.getMaxHeight();
+ }
+
+ @Override
+ synchronized public TerminalStyle getStyle(int line, int column) {
+ return fData.getStyle(line, column);
+ }
+
+ @Override
+ synchronized public TerminalStyle[] getStyles(int line) {
+ return fData.getStyles(line);
+ }
+
+ @Override
+ synchronized public int getWidth() {
+ return fData.getWidth();
+ }
+
+ @Override
+ synchronized public ITerminalTextDataSnapshot makeSnapshot() {
+ return fData.makeSnapshot();
+ }
+
+ @Override
+ synchronized public void scroll(int startLine, int size, int shift) {
+ fData.scroll(startLine, size, shift);
+ }
+
+ @Override
+ synchronized public void setChar(int line, int column, char c, TerminalStyle style) {
+ fData.setChar(line, column, c, style);
+ }
+
+ @Override
+ synchronized public void setChars(int line, int column, char[] chars, int start, int len, TerminalStyle style) {
+ fData.setChars(line, column, chars, start, len, style);
+ }
+
+ @Override
+ synchronized public void setChars(int line, int column, char[] chars, TerminalStyle style) {
+ fData.setChars(line, column, chars, style);
+ }
+
+ @Override
+ synchronized public void setCursorColumn(int column) {
+ fData.setCursorColumn(column);
+ }
+
+ @Override
+ synchronized public void setCursorLine(int line) {
+ fData.setCursorLine(line);
+ }
+
+ @Override
+ synchronized public void setDimensions(int height, int width) {
+ fData.setDimensions(height, width);
+ }
+
+ @Override
+ synchronized public void setMaxHeight(int height) {
+ fData.setMaxHeight(height);
+ }
+
+ @Override
+ synchronized public boolean isWrappedLine(int line) {
+ return fData.isWrappedLine(line);
+ }
+
+ @Override
+ synchronized public void setWrappedLine(int line) {
+ fData.setWrappedLine(line);
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/SystemDefaultColors.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/SystemDefaultColors.java
new file mode 100644
index 00000000000..930a97a1e73
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/SystemDefaultColors.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Kichwa Coders Canada Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.model;
+
+import java.util.function.Supplier;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferenceConverter;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.ui.preferences.ScopedPreferenceStore;
+import org.eclipse.ui.themes.ColorUtil;
+
+/**
+ * Wrapper class to get standard colors from Eclipse trying to match existing theme where possible
+ * by using standard editor colors.
+ *
+ * This class has an implied and optional dependency on org.eclipse.ui.editors bundle by reading
+ * that bundles preferences.
+ */
+public enum SystemDefaultColors implements Supplier {
+
+ /**
+ * Standard text foreground. Typically black in Light theme.
+ */
+ FOREGROUND("Foreground", "COLOR_LIST_FOREGROUND", new RGB(0, 0, 0)), //$NON-NLS-1$ //$NON-NLS-2$
+
+ /**
+ * Standard text background. Typically white in Light theme.
+ */
+ BACKGROUND("Background", "COLOR_LIST_BACKGROUND", new RGB(255, 255, 255)), //$NON-NLS-1$ //$NON-NLS-2$
+
+ /**
+ * Selection foreground. Typically white in Light theme.
+ */
+ SELECTION_FOREGROUND("SelectionForeground", "COLOR_LIST_SELECTION_TEXT", //$NON-NLS-1$ //$NON-NLS-2$
+ new RGB(255, 255, 255)),
+
+ /**
+ * Selection background. Typically blue in Light theme.
+ */
+ SELECTION_BACKGROUND("SelectionBackground", "COLOR_LIST_SELECTION", new RGB(74, 144, 9)); //$NON-NLS-1$ //$NON-NLS-2$
+
+ private static final String EDITOR_SCOPE = "org.eclipse.ui.editors"; //$NON-NLS-1$
+ private static final String PREF_PREFIX = "AbstractTextEditor.Color."; //$NON-NLS-1$
+ private static final String PREF_SYSTEM_DEFAULT_SUFFIX = ".SystemDefault"; //$NON-NLS-1$
+
+ /**
+ * SWT Name of Color
+ *
+ * Values from SWT
+ */
+ private String swtColor;
+
+ /**
+ * Preference name for color.
+ *
+ * Values from org.eclipse.ui.texteditor.AbstractTextEditor....
+ */
+ private String editorColor;
+
+ /** If all else fails, use this standard color */
+ private RGB fallbackColor;
+
+ SystemDefaultColors(String editorColor, String swtColor, RGB rgb) {
+ this.editorColor = editorColor;
+ this.swtColor = swtColor;
+ this.fallbackColor = rgb;
+ }
+
+ /**
+ * Get the color for this enum value.
+ *
+ * @return the RGB color or a non-null color as a fallback.
+ */
+ @Override
+ public RGB get() {
+ IPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, EDITOR_SCOPE);
+
+ RGB rgb = null;
+ String pref = PREF_PREFIX + editorColor;
+ String prefSystemDefault = pref + PREF_SYSTEM_DEFAULT_SUFFIX;
+ if (Platform.getPreferencesService() != null) {
+ if (!store.getBoolean(prefSystemDefault)) {
+ if (store.contains(pref)) {
+ if (store.isDefault(pref))
+ rgb = PreferenceConverter.getDefaultColor(store, pref);
+ else {
+ rgb = PreferenceConverter.getColor(store, pref);
+ }
+ }
+ }
+ }
+
+ if (rgb == null) {
+ rgb = ColorUtil.getColorValue(swtColor);
+ }
+
+ if (rgb == null) {
+ rgb = fallbackColor;
+ }
+
+ return rgb;
+ }
+}
\ No newline at end of file
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextData.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextData.java
new file mode 100644
index 00000000000..675bfd66de2
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextData.java
@@ -0,0 +1,333 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - [168197] Fix Terminal for CDC-1.1/Foundation-1.1
+ * Anton Leherbauer (Wind River) - [453393] Add support for copying wrapped lines without line break
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.model;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+import org.eclipse.tm.terminal.model.ITerminalTextDataReadOnly;
+import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot;
+import org.eclipse.tm.terminal.model.LineSegment;
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ * This class is thread safe.
+ *
+ */
+public class TerminalTextData implements ITerminalTextData {
+ final ITerminalTextData fData;
+ /**
+ * A list of active snapshots
+ */
+ public TerminalTextDataSnapshot[] fSnapshots = new TerminalTextDataSnapshot[0];
+ private int fCursorColumn;
+ private int fCursorLine;
+
+ /**
+ * Debug helper method -- use as "New Detail Formatter.." in the
+ * debugger variables view:
+ * TerminalTextData.toMultiLineText(this,0,200)
+ * @param term the terminal
+ * @param start start line to show
+ * @param len number of lines to show -- negative numbers means show all
+ * @return a string representation of the content
+ */
+ static public String toMultiLineText(ITerminalTextDataReadOnly term, int start, int len) {
+ if (len < 0)
+ len = term.getHeight();
+ StringBuffer buff = new StringBuffer();
+ int width = term.getWidth();
+ int n = Math.min(len, term.getHeight() - start);
+ for (int line = start; line < n; line++) {
+ if (line > 0)
+ buff.append("\n"); //$NON-NLS-1$
+ for (int column = 0; column < width; column++) {
+ buff.append(term.getChar(line, column));
+ }
+ }
+ // get rid of the empty space at the end of the lines
+ //return buff.toString().replaceAll("\000+", ""); //$NON-NLS-1$//$NON-NLS-2$
+ //
+ int i = buff.length() - 1;
+ while (i >= 0 && buff.charAt(i) == '\000') {
+ i--;
+ }
+ buff.setLength(i + 1);
+ return buff.toString();
+ //
+ }
+
+ /**
+ * Show the first 100 lines
+ * see {@link #toMultiLineText(ITerminalTextDataReadOnly, int, int)}
+ * @param term A read-only terminal model
+ * @return a string representation of the terminal
+ */
+ static public String toMultiLineText(ITerminalTextDataReadOnly term) {
+ return toMultiLineText(term, 0, 100);
+ }
+
+ public TerminalTextData() {
+ this(new TerminalTextDataFastScroll());
+
+ // this(new TerminalTextDataStore());
+ }
+
+ public TerminalTextData(ITerminalTextData data) {
+ fData = data;
+ }
+
+ @Override
+ public int getWidth() {
+ return fData.getWidth();
+ }
+
+ @Override
+ public int getHeight() {
+ // no need for an extra variable
+ return fData.getHeight();
+ }
+
+ @Override
+ public void setDimensions(int height, int width) {
+ int h = getHeight();
+ int w = getWidth();
+ if (w == width && h == height)
+ return;
+ fData.setDimensions(height, width);
+ sendDimensionsChanged(h, w, height, width);
+ }
+
+ private void sendDimensionsChanged(int oldHeight, int oldWidth, int newHeight, int newWidth) {
+ // determine what has changed
+ if (oldWidth == newWidth) {
+ if (oldHeight < newHeight)
+ sendLinesChangedToSnapshot(oldHeight, newHeight - oldHeight);
+ else
+ sendLinesChangedToSnapshot(newHeight, oldHeight - newHeight);
+ } else {
+ sendLinesChangedToSnapshot(0, oldHeight);
+ }
+ sendDimensionsChanged();
+ }
+
+ @Override
+ public LineSegment[] getLineSegments(int line, int column, int len) {
+ return fData.getLineSegments(line, column, len);
+ }
+
+ @Override
+ public char getChar(int line, int column) {
+ return fData.getChar(line, column);
+ }
+
+ @Override
+ public TerminalStyle getStyle(int line, int column) {
+ return fData.getStyle(line, column);
+ }
+
+ @Override
+ public void setChar(int line, int column, char c, TerminalStyle style) {
+ fData.setChar(line, column, c, style);
+ sendLineChangedToSnapshots(line);
+ }
+
+ @Override
+ public void setChars(int line, int column, char[] chars, TerminalStyle style) {
+ fData.setChars(line, column, chars, style);
+ sendLineChangedToSnapshots(line);
+ }
+
+ @Override
+ public void setChars(int line, int column, char[] chars, int start, int len, TerminalStyle style) {
+ fData.setChars(line, column, chars, start, len, style);
+ sendLineChangedToSnapshots(line);
+ }
+
+ @Override
+ public void scroll(int startLine, int size, int shift) {
+ fData.scroll(startLine, size, shift);
+ sendScrolledToSnapshots(startLine, size, shift);
+ }
+
+ @Override
+ public String toString() {
+ return fData.toString();
+ }
+
+ private void sendDimensionsChanged() {
+ for (int i = 0; i < fSnapshots.length; i++) {
+ fSnapshots[i].markDimensionsChanged();
+ }
+ }
+
+ /**
+ * @param line notifies snapshots that line line has changed
+ */
+ protected void sendLineChangedToSnapshots(int line) {
+ for (int i = 0; i < fSnapshots.length; i++) {
+ fSnapshots[i].markLineChanged(line);
+ }
+ }
+
+ /**
+ * Notify snapshots that multiple lines have changed
+ * @param line changed line
+ * @param n number of changed lines
+ */
+ protected void sendLinesChangedToSnapshot(int line, int n) {
+ for (int i = 0; i < fSnapshots.length; i++) {
+ fSnapshots[i].markLinesChanged(line, n);
+ }
+ }
+
+ /**
+ * Notify snapshot that a region was scrolled
+ * @param startLine first line of scrolled region
+ * @param size size of scrolled region (number of lines)
+ * @param shift delta by which the region is scrolled
+ */
+ protected void sendScrolledToSnapshots(int startLine, int size, int shift) {
+ for (int i = 0; i < fSnapshots.length; i++) {
+ fSnapshots[i].scroll(startLine, size, shift);
+ }
+ }
+
+ protected void sendCursorChanged() {
+ for (int i = 0; i < fSnapshots.length; i++) {
+ fSnapshots[i].markCursorChanged();
+ }
+ }
+
+ /**
+ * Removes the snapshot from the @observer@ list
+ * @param snapshot A snapshot of a terminal model
+ */
+ protected void removeSnapshot(TerminalTextDataSnapshot snapshot) {
+ // poor mans approach to modify the array
+ List list = new ArrayList<>();
+ list.addAll(Arrays.asList(fSnapshots));
+ list.remove(snapshot);
+ fSnapshots = list.toArray(new TerminalTextDataSnapshot[list.size()]);
+ }
+
+ @Override
+ public ITerminalTextDataSnapshot makeSnapshot() {
+ // poor mans approach to modify the array
+ TerminalTextDataSnapshot snapshot = new TerminalTextDataSnapshot(this);
+ snapshot.markDimensionsChanged();
+ List list = new ArrayList<>();
+ list.addAll(Arrays.asList(fSnapshots));
+ list.add(snapshot);
+ fSnapshots = list.toArray(new TerminalTextDataSnapshot[list.size()]);
+ return snapshot;
+ }
+
+ @Override
+ public void addLine() {
+ int oldHeight = getHeight();
+ fData.addLine();
+ // was is an append or a scroll?
+ int newHeight = getHeight();
+ if (newHeight > oldHeight) {
+ //the line was appended
+ sendLinesChangedToSnapshot(oldHeight, 1);
+ int width = getWidth();
+ sendDimensionsChanged(oldHeight, width, newHeight, width);
+
+ } else {
+ // the line was scrolled
+ sendScrolledToSnapshots(0, oldHeight, -1);
+ }
+
+ }
+
+ @Override
+ public void copy(ITerminalTextData source) {
+ fData.copy(source);
+ fCursorLine = source.getCursorLine();
+ fCursorColumn = source.getCursorColumn();
+ }
+
+ @Override
+ public void copyLine(ITerminalTextData source, int sourceLine, int destLine) {
+ fData.copyLine(source, sourceLine, destLine);
+ }
+
+ @Override
+ public void copyRange(ITerminalTextData source, int sourceStartLine, int destStartLine, int length) {
+ fData.copyRange(source, sourceStartLine, destStartLine, length);
+ }
+
+ @Override
+ public char[] getChars(int line) {
+ return fData.getChars(line);
+ }
+
+ @Override
+ public TerminalStyle[] getStyles(int line) {
+ return fData.getStyles(line);
+ }
+
+ @Override
+ public int getMaxHeight() {
+ return fData.getMaxHeight();
+ }
+
+ @Override
+ public void setMaxHeight(int height) {
+ fData.setMaxHeight(height);
+ }
+
+ @Override
+ public void cleanLine(int line) {
+ fData.cleanLine(line);
+ sendLineChangedToSnapshots(line);
+ }
+
+ @Override
+ public int getCursorColumn() {
+ return fCursorColumn;
+ }
+
+ @Override
+ public int getCursorLine() {
+ return fCursorLine;
+ }
+
+ @Override
+ public void setCursorColumn(int column) {
+ fCursorColumn = column;
+ sendCursorChanged();
+ }
+
+ @Override
+ public void setCursorLine(int line) {
+ fCursorLine = line;
+ sendCursorChanged();
+ }
+
+ @Override
+ public boolean isWrappedLine(int line) {
+ return fData.isWrappedLine(line);
+ }
+
+ @Override
+ public void setWrappedLine(int line) {
+ fData.setWrappedLine(line);
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataFastScroll.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataFastScroll.java
new file mode 100644
index 00000000000..519fad0a3e1
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataFastScroll.java
@@ -0,0 +1,302 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [453393] Add support for copying wrapped lines without line break
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.model;
+
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot;
+import org.eclipse.tm.terminal.model.LineSegment;
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ * This class is optimized for scrolling the entire {@link #getHeight()}.
+ * The scrolling is done by moving an offset into the data and using
+ * the modulo operator.
+ *
+ */
+public class TerminalTextDataFastScroll implements ITerminalTextData {
+
+ final ITerminalTextData fData;
+ private int fHeight;
+ private int fMaxHeight;
+ /**
+ * The offset into the array.
+ */
+ int fOffset;
+
+ public TerminalTextDataFastScroll(ITerminalTextData data, int maxHeight) {
+ fMaxHeight = maxHeight;
+ fData = data;
+ fData.setDimensions(maxHeight, fData.getWidth());
+ if (maxHeight > 2)
+ assert shiftOffset(-2) || throwRuntimeException();
+ }
+
+ public TerminalTextDataFastScroll(int maxHeight) {
+ this(new TerminalTextDataStore(), maxHeight);
+ }
+
+ public TerminalTextDataFastScroll() {
+ this(new TerminalTextDataStore(), 1);
+ }
+
+ /**
+ * This is used in asserts to throw an {@link RuntimeException}.
+ * This is useful for tests.
+ * @return never -- throws an exception
+ */
+ private boolean throwRuntimeException() {
+ throw new RuntimeException();
+ }
+
+ /**
+ *
+ * @param line
+ * @return the actual line number in {@link #fData}
+ */
+ int getPositionOfLine(int line) {
+ return (line + fOffset) % fMaxHeight;
+ }
+
+ /**
+ * Moves offset by delta. This does not move the data!
+ * @param delta
+ */
+ void moveOffset(int delta) {
+ assert Math.abs(delta) < fMaxHeight || throwRuntimeException();
+ fOffset = (fMaxHeight + fOffset + delta) % fMaxHeight;
+
+ }
+
+ /**
+ * Test method to shift the offset for testing (if assert ==true)
+ * @param shift TODO
+ * @return true
+ */
+ private boolean shiftOffset(int shift) {
+ moveOffset(shift);
+ return true;
+ }
+
+ @Override
+ public void addLine() {
+ if (getHeight() < fMaxHeight) {
+ setDimensions(getHeight() + 1, getWidth());
+ } else {
+ scroll(0, getHeight(), -1);
+ }
+ }
+
+ @Override
+ public void cleanLine(int line) {
+ fData.cleanLine(getPositionOfLine(line));
+ }
+
+ @Override
+ public void copy(ITerminalTextData source) {
+ int n = source.getHeight();
+ setDimensions(source.getHeight(), source.getWidth());
+ for (int i = 0; i < n; i++) {
+ fData.copyLine(source, i, getPositionOfLine(i));
+ }
+ }
+
+ @Override
+ public void copyLine(ITerminalTextData source, int sourceLine, int destLine) {
+ fData.copyLine(source, sourceLine, getPositionOfLine(destLine));
+ }
+
+ @Override
+ public void copyRange(ITerminalTextData source, int sourceStartLine, int destStartLine, int length) {
+ assert (destStartLine >= 0 && destStartLine + length <= fHeight) || throwRuntimeException();
+ for (int i = 0; i < length; i++) {
+ fData.copyLine(source, i + sourceStartLine, getPositionOfLine(i + destStartLine));
+ }
+ }
+
+ @Override
+ public char getChar(int line, int column) {
+ assert (line >= 0 && line < fHeight) || throwRuntimeException();
+ return fData.getChar(getPositionOfLine(line), column);
+ }
+
+ @Override
+ public char[] getChars(int line) {
+ assert (line >= 0 && line < fHeight) || throwRuntimeException();
+ return fData.getChars(getPositionOfLine(line));
+ }
+
+ @Override
+ public int getHeight() {
+ return fHeight;
+ }
+
+ @Override
+ public LineSegment[] getLineSegments(int line, int startCol, int numberOfCols) {
+ assert (line >= 0 && line < fHeight) || throwRuntimeException();
+ return fData.getLineSegments(getPositionOfLine(line), startCol, numberOfCols);
+ }
+
+ @Override
+ public int getMaxHeight() {
+ return fMaxHeight;
+ }
+
+ @Override
+ public TerminalStyle getStyle(int line, int column) {
+ assert (line >= 0 && line < fHeight) || throwRuntimeException();
+ return fData.getStyle(getPositionOfLine(line), column);
+ }
+
+ @Override
+ public TerminalStyle[] getStyles(int line) {
+ assert (line >= 0 && line < fHeight) || throwRuntimeException();
+ return fData.getStyles(getPositionOfLine(line));
+ }
+
+ @Override
+ public int getWidth() {
+ return fData.getWidth();
+ }
+
+ @Override
+ public ITerminalTextDataSnapshot makeSnapshot() {
+ return fData.makeSnapshot();
+ }
+
+ private void cleanLines(int line, int len) {
+ for (int i = line; i < line + len; i++) {
+ fData.cleanLine(getPositionOfLine(i));
+ }
+ }
+
+ @Override
+ public void scroll(int startLine, int size, int shift) {
+ assert (startLine >= 0 && startLine + size <= fHeight) || throwRuntimeException();
+ if (shift >= fMaxHeight || -shift >= fMaxHeight) {
+ cleanLines(startLine, fMaxHeight - startLine);
+ return;
+ }
+ if (size == fHeight) {
+ // This is the case this class is optimized for!
+ moveOffset(-shift);
+ // we only have to clean the lines that appear by the move
+ if (shift < 0) {
+ cleanLines(Math.max(startLine, startLine + size + shift), Math.min(-shift, getHeight() - startLine));
+ } else {
+ cleanLines(startLine, Math.min(shift, getHeight() - startLine));
+ }
+ } else {
+ // we have to copy the lines.
+ if (shift < 0) {
+ // move the region up
+ // shift is negative!!
+ for (int i = startLine; i < startLine + size + shift; i++) {
+ fData.copyLine(fData, getPositionOfLine(i - shift), getPositionOfLine(i));
+ }
+ // then clean the opened lines
+ cleanLines(Math.max(0, startLine + size + shift), Math.min(-shift, getHeight() - startLine));
+ } else {
+ for (int i = startLine + size - 1; i >= startLine && i - shift >= 0; i--) {
+ fData.copyLine(fData, getPositionOfLine(i - shift), getPositionOfLine(i));
+ }
+ cleanLines(startLine, Math.min(shift, getHeight() - startLine));
+ }
+ }
+ }
+
+ @Override
+ public void setChar(int line, int column, char c, TerminalStyle style) {
+ assert (line >= 0 && line < fHeight) || throwRuntimeException();
+ fData.setChar(getPositionOfLine(line), column, c, style);
+ }
+
+ @Override
+ public void setChars(int line, int column, char[] chars, int start, int len, TerminalStyle style) {
+ assert (line >= 0 && line < fHeight) || throwRuntimeException();
+ fData.setChars(getPositionOfLine(line), column, chars, start, len, style);
+ }
+
+ @Override
+ public void setChars(int line, int column, char[] chars, TerminalStyle style) {
+ assert (line >= 0 && line < fHeight) || throwRuntimeException();
+ fData.setChars(getPositionOfLine(line), column, chars, style);
+ }
+
+ @Override
+ public void setDimensions(int height, int width) {
+ assert height >= 0 || throwRuntimeException();
+ assert width >= 0 || throwRuntimeException();
+ if (height > fMaxHeight)
+ setMaxHeight(height);
+ fHeight = height;
+ if (width != fData.getWidth())
+ fData.setDimensions(fMaxHeight, width);
+ }
+
+ @Override
+ public void setMaxHeight(int maxHeight) {
+ assert maxHeight >= fHeight || throwRuntimeException();
+ // move everything to offset0
+ int start = getPositionOfLine(0);
+ if (start != 0) {
+ // invent a more efficient algorithm....
+ ITerminalTextData buffer = new TerminalTextDataStore();
+ // create a buffer with the expected height
+ buffer.setDimensions(maxHeight, getWidth());
+ int n = Math.min(fMaxHeight - start, maxHeight);
+ // copy the first part
+ buffer.copyRange(fData, start, 0, n);
+ // copy the second part
+ if (n < maxHeight)
+ buffer.copyRange(fData, 0, n, Math.min(fMaxHeight - n, maxHeight - n));
+ // copy the buffer back to our data
+ fData.copy(buffer);
+ shiftOffset(-start);
+ } else {
+ fData.setDimensions(maxHeight, fData.getWidth());
+ }
+ fMaxHeight = maxHeight;
+ }
+
+ @Override
+ public int getCursorColumn() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getCursorLine() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setCursorColumn(int column) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setCursorLine(int line) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isWrappedLine(int line) {
+ assert (line >= 0 && line < fHeight) || throwRuntimeException();
+ return fData.isWrappedLine(getPositionOfLine(line));
+ }
+
+ @Override
+ public void setWrappedLine(int line) {
+ assert (line >= 0 && line < fHeight) || throwRuntimeException();
+ fData.setWrappedLine(getPositionOfLine(line));
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataSnapshot.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataSnapshot.java
new file mode 100644
index 00000000000..26a6982c871
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataSnapshot.java
@@ -0,0 +1,325 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [453393] Add support for copying wrapped lines without line break
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.model;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot;
+import org.eclipse.tm.terminal.model.LineSegment;
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ * The public methods of this class have to be called from one thread!
+ *
+ * Threading considerations:
+ * This class is not threadsafe!
+ */
+class TerminalTextDataSnapshot implements ITerminalTextDataSnapshot {
+ /**
+ * The changes of the current snapshot relative to the
+ * previous snapshot
+ */
+ volatile ISnapshotChanges fCurrentChanges;
+ /**
+ * Keeps track of changes that happened since the current
+ * snapshot has been made.
+ */
+ ISnapshotChanges fFutureChanges;
+ /**
+ * Is used as lock and is the reference to the terminal we take snapshots from.
+ */
+ final TerminalTextData fTerminal;
+ /**
+ * A snapshot copy of of fTerminal
+ */
+ // snapshot does not need internal synchronisation
+ final TerminalTextDataWindow fSnapshot;
+ // this variable is synchronized on fTerminal!
+ private SnapshotOutOfDateListener[] fListener = new SnapshotOutOfDateListener[0];
+ // this variable is synchronized on fTerminal!
+ private boolean fListenersNeedNotify;
+ private int fInterestWindowSize;
+ private int fInterestWindowStartLine;
+
+ TerminalTextDataSnapshot(TerminalTextData terminal) {
+ fSnapshot = new TerminalTextDataWindow();
+ fTerminal = terminal;
+ fCurrentChanges = new SnapshotChanges(fTerminal.getHeight());
+ fCurrentChanges.setTerminalChanged();
+ fFutureChanges = new SnapshotChanges(fTerminal.getHeight());
+ fFutureChanges.markLinesChanged(0, fTerminal.getHeight());
+ fListenersNeedNotify = true;
+ fInterestWindowSize = -1;
+ }
+
+ /**
+ * This is used in asserts to throw an {@link RuntimeException}.
+ * This is useful for tests.
+ * @return never -- throws an exception
+ */
+ private boolean throwRuntimeException() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public void detach() {
+ fTerminal.removeSnapshot(this);
+ }
+
+ @Override
+ public boolean isOutOfDate() {
+ // this is called from fTerminal, therefore we lock on fTerminal
+ synchronized (fTerminal) {
+ return fFutureChanges.hasChanged();
+ }
+ }
+
+ @Override
+ public void updateSnapshot(boolean detectScrolling) {
+ // make sure terminal does not change while we make the snapshot
+ synchronized (fTerminal) {
+ // let's make the future changes current
+ fCurrentChanges = fFutureChanges;
+ fFutureChanges = new SnapshotChanges(fTerminal.getHeight());
+ fFutureChanges.setInterestWindow(fInterestWindowStartLine, fInterestWindowSize);
+ // and update the snapshot
+ if (fSnapshot.getHeight() != fTerminal.getHeight() || fSnapshot.getWidth() != fTerminal.getWidth()) {
+ if (fInterestWindowSize == -1)
+ fSnapshot.setWindow(0, fTerminal.getHeight());
+ // if the dimensions have changed, we need a full copy
+ fSnapshot.copy(fTerminal);
+ // and we mark all lines as changed
+ fCurrentChanges.setAllChanged(fTerminal.getHeight());
+ } else {
+ // first we do the scroll on the copy
+ int start = fCurrentChanges.getScrollWindowStartLine();
+ int lines = Math.min(fCurrentChanges.getScrollWindowSize(), fSnapshot.getHeight() - start);
+ fSnapshot.scroll(start, lines, fCurrentChanges.getScrollWindowShift());
+ // and then create the snapshot of the changed lines
+ fCurrentChanges.copyChangedLines(fSnapshot, fTerminal);
+ }
+ fListenersNeedNotify = true;
+ fSnapshot.setCursorLine(fTerminal.getCursorLine());
+ fSnapshot.setCursorColumn(fTerminal.getCursorColumn());
+ }
+ if (!detectScrolling) {
+ // let's pretend there was no scrolling and
+ // convert the scrolling into line changes
+ fCurrentChanges.convertScrollingIntoChanges();
+ }
+ }
+
+ @Override
+ public char getChar(int line, int column) {
+ return fSnapshot.getChar(line, column);
+ }
+
+ @Override
+ public int getHeight() {
+ return fSnapshot.getHeight();
+ }
+
+ @Override
+ public LineSegment[] getLineSegments(int line, int column, int len) {
+ return fSnapshot.getLineSegments(line, column, len);
+ }
+
+ @Override
+ public TerminalStyle getStyle(int line, int column) {
+ return fSnapshot.getStyle(line, column);
+ }
+
+ @Override
+ public int getWidth() {
+ return fSnapshot.getWidth();
+ }
+
+ @Override
+ public int getFirstChangedLine() {
+ return fCurrentChanges.getFirstChangedLine();
+ }
+
+ @Override
+ public int getLastChangedLine() {
+ return fCurrentChanges.getLastChangedLine();
+ }
+
+ @Override
+ public boolean hasLineChanged(int line) {
+ return fCurrentChanges.hasLineChanged(line);
+ }
+
+ @Override
+ public boolean hasDimensionsChanged() {
+ return fCurrentChanges.hasDimensionsChanged();
+ }
+
+ @Override
+ public boolean hasTerminalChanged() {
+ return fCurrentChanges.hasTerminalChanged();
+ }
+
+ @Override
+ public int getScrollWindowStartLine() {
+ return fCurrentChanges.getScrollWindowStartLine();
+ }
+
+ @Override
+ public int getScrollWindowSize() {
+ return fCurrentChanges.getScrollWindowSize();
+ }
+
+ @Override
+ public int getScrollWindowShift() {
+ return fCurrentChanges.getScrollWindowShift();
+ }
+
+ /**
+ * Announces a change in line line
+ * @param line
+ */
+ void markLineChanged(int line) {
+ // threading
+ fFutureChanges.markLineChanged(line);
+ fFutureChanges.setTerminalChanged();
+ notifyListers();
+ }
+
+ /**
+ * Announces a change of n lines beginning with line line
+ * @param line
+ * @param n
+ */
+ void markLinesChanged(int line, int n) {
+ fFutureChanges.markLinesChanged(line, n);
+ fFutureChanges.setTerminalChanged();
+ notifyListers();
+ }
+
+ void markDimensionsChanged() {
+ fFutureChanges.markDimensionsChanged();
+ fFutureChanges.setTerminalChanged();
+ notifyListers();
+ }
+
+ void markCursorChanged() {
+ fFutureChanges.markCursorChanged();
+ fFutureChanges.setTerminalChanged();
+ notifyListers();
+ }
+
+ /**
+ * @param startLine
+ * @param size
+ * @param shift
+ */
+ void scroll(int startLine, int size, int shift) {
+ fFutureChanges.scroll(startLine, size, shift);
+ fFutureChanges.setTerminalChanged();
+ notifyListers();
+ }
+
+ /**
+ * Notifies listeners about the change
+ */
+ private void notifyListers() {
+ // this code has to be called from a block synchronized on fTerminal
+ synchronized (fTerminal) {
+ if (fListenersNeedNotify) {
+ for (int i = 0; i < fListener.length; i++) {
+ fListener[i].snapshotOutOfDate(this);
+ }
+ fListenersNeedNotify = false;
+ }
+ }
+ }
+
+ @Override
+ public ITerminalTextDataSnapshot makeSnapshot() {
+ return fSnapshot.makeSnapshot();
+ }
+
+ @Override
+ synchronized public void addListener(SnapshotOutOfDateListener listener) {
+ List list = new ArrayList<>();
+ list.addAll(Arrays.asList(fListener));
+ list.add(listener);
+ fListener = list.toArray(new SnapshotOutOfDateListener[list.size()]);
+ }
+
+ @Override
+ synchronized public void removeListener(SnapshotOutOfDateListener listener) {
+ List list = new ArrayList<>();
+ list.addAll(Arrays.asList(fListener));
+ list.remove(listener);
+ fListener = list.toArray(new SnapshotOutOfDateListener[list.size()]);
+ }
+
+ @Override
+ public String toString() {
+ return fSnapshot.toString();
+ }
+
+ @Override
+ public int getInterestWindowSize() {
+ return fInterestWindowSize;
+ }
+
+ @Override
+ public int getInterestWindowStartLine() {
+ return fInterestWindowStartLine;
+ }
+
+ @Override
+ public void setInterestWindow(int startLine, int size) {
+ assert startLine >= 0 || throwRuntimeException();
+ assert size >= 0 || throwRuntimeException();
+ fInterestWindowStartLine = startLine;
+ fInterestWindowSize = size;
+ fSnapshot.setWindow(startLine, size);
+ fFutureChanges.setInterestWindow(startLine, size);
+ notifyListers();
+ }
+
+ @Override
+ public char[] getChars(int line) {
+ return fSnapshot.getChars(line);
+ }
+
+ @Override
+ public TerminalStyle[] getStyles(int line) {
+ return fSnapshot.getStyles(line);
+ }
+
+ @Override
+ public int getCursorColumn() {
+ return fSnapshot.getCursorColumn();
+ }
+
+ @Override
+ public int getCursorLine() {
+ return fSnapshot.getCursorLine();
+ }
+
+ @Override
+ public ITerminalTextData getTerminalTextData() {
+ return fTerminal;
+ }
+
+ @Override
+ public boolean isWrappedLine(int line) {
+ return fSnapshot.isWrappedLine(line);
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataStore.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataStore.java
new file mode 100644
index 00000000000..f0472aba928
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataStore.java
@@ -0,0 +1,355 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [453393] Add support for copying wrapped lines without line break
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.model;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot;
+import org.eclipse.tm.terminal.model.LineSegment;
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ * This class is thread safe.
+ *
+ */
+public class TerminalTextDataStore implements ITerminalTextData {
+ private char[][] fChars;
+ private TerminalStyle[][] fStyle;
+ private int fWidth;
+ private int fHeight;
+ private int fMaxHeight;
+ private int fCursorColumn;
+ private int fCursorLine;
+ final private BitSet fWrappedLines = new BitSet();
+
+ public TerminalTextDataStore() {
+ fChars = new char[0][];
+ fStyle = new TerminalStyle[0][];
+ fWidth = 0;
+ }
+
+ /**
+ * This is used in asserts to throw an {@link RuntimeException}.
+ * This is useful for tests.
+ * @return never -- throws an exception
+ */
+ private boolean throwRuntimeException() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public int getWidth() {
+ return fWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return fHeight;
+ }
+
+ @Override
+ public void setDimensions(int height, int width) {
+ assert height >= 0 || throwRuntimeException();
+ assert width >= 0 || throwRuntimeException();
+ // just extend the region
+ if (height > fChars.length) {
+ int h = 4 * height / 3;
+ if (fMaxHeight > 0 && h > fMaxHeight)
+ h = fMaxHeight;
+ fStyle = (TerminalStyle[][]) resizeArray(fStyle, height);
+ fChars = (char[][]) resizeArray(fChars, height);
+ }
+ // clean the new lines
+ if (height > fHeight) {
+ for (int i = fHeight; i < height; i++) {
+ cleanLine(i);
+ }
+ }
+ // set dimensions after successful resize!
+ fWidth = width;
+ fHeight = height;
+ }
+
+ /**
+ * Reallocates an array with a new size, and copies the contents of the old
+ * array to the new array.
+ *
+ * @param origArray the old array, to be reallocated.
+ * @param newSize the new array size.
+ * @return A new array with the same contents (chopped off if needed or filled with 0 or null).
+ */
+ private Object resizeArray(Object origArray, int newSize) {
+ int oldSize = Array.getLength(origArray);
+ if (oldSize == newSize)
+ return origArray;
+ Class> elementType = origArray.getClass().getComponentType();
+ Object newArray = Array.newInstance(elementType, newSize);
+ int preserveLength = Math.min(oldSize, newSize);
+ if (preserveLength > 0)
+ System.arraycopy(origArray, 0, newArray, 0, preserveLength);
+ return newArray;
+ }
+
+ @Override
+ public LineSegment[] getLineSegments(int line, int column, int len) {
+ // get the styles and chars for this line
+ TerminalStyle[] styles = fStyle[line];
+ char[] chars = fChars[line];
+ int col = column;
+ int n = column + len;
+
+ // expand the line if needed....
+ if (styles == null)
+ styles = new TerminalStyle[n];
+ else if (styles.length < n)
+ styles = (TerminalStyle[]) resizeArray(styles, n);
+
+ if (chars == null)
+ chars = new char[n];
+ else if (chars.length < n)
+ chars = (char[]) resizeArray(chars, n);
+
+ // and create the line segments
+ TerminalStyle style = styles[column];
+ List segments = new ArrayList<>();
+ for (int i = column; i < n; i++) {
+ if (styles[i] != style) {
+ segments.add(new LineSegment(col, new String(chars, col, i - col), style));
+ style = styles[i];
+ col = i;
+ }
+ }
+ if (col < n) {
+ segments.add(new LineSegment(col, new String(chars, col, n - col), style));
+ }
+ return segments.toArray(new LineSegment[segments.size()]);
+ }
+
+ @Override
+ public char getChar(int line, int column) {
+ assert column < fWidth || throwRuntimeException();
+ if (fChars[line] == null || column >= fChars[line].length)
+ return 0;
+ return fChars[line][column];
+ }
+
+ @Override
+ public TerminalStyle getStyle(int line, int column) {
+ assert column < fWidth || throwRuntimeException();
+ if (fStyle[line] == null || column >= fStyle[line].length)
+ return null;
+ return fStyle[line][column];
+ }
+
+ void ensureLineLength(int iLine, int length) {
+ if (length > fWidth)
+ throw new RuntimeException();
+ if (fChars[iLine] == null) {
+ fChars[iLine] = new char[length];
+ } else if (fChars[iLine].length < length) {
+ fChars[iLine] = (char[]) resizeArray(fChars[iLine], length);
+ }
+ if (fStyle[iLine] == null) {
+ fStyle[iLine] = new TerminalStyle[length];
+ } else if (fStyle[iLine].length < length) {
+ fStyle[iLine] = (TerminalStyle[]) resizeArray(fStyle[iLine], length);
+ }
+ }
+
+ @Override
+ public void setChar(int line, int column, char c, TerminalStyle style) {
+ ensureLineLength(line, column + 1);
+ fChars[line][column] = c;
+ fStyle[line][column] = style;
+ }
+
+ @Override
+ public void setChars(int line, int column, char[] chars, TerminalStyle style) {
+ setChars(line, column, chars, 0, chars.length, style);
+ }
+
+ @Override
+ public void setChars(int line, int column, char[] chars, int start, int len, TerminalStyle style) {
+ ensureLineLength(line, column + len);
+ for (int i = 0; i < len; i++) {
+ fChars[line][column + i] = chars[i + start];
+ fStyle[line][column + i] = style;
+ }
+ }
+
+ @Override
+ public void scroll(int startLine, int size, int shift) {
+ assert startLine + size <= getHeight() || throwRuntimeException();
+ if (shift < 0) {
+ // move the region up
+ // shift is negative!!
+ for (int i = startLine; i < startLine + size + shift; i++) {
+ fChars[i] = fChars[i - shift];
+ fStyle[i] = fStyle[i - shift];
+ fWrappedLines.set(i, fWrappedLines.get(i - shift));
+ }
+ // then clean the opened lines
+ cleanLines(Math.max(startLine, startLine + size + shift), Math.min(-shift, getHeight() - startLine));
+ // cleanLines(Math.max(0, startLine+size+shift),Math.min(-shift, getHeight()-startLine));
+ } else {
+ for (int i = startLine + size - 1; i >= startLine && i - shift >= 0; i--) {
+ fChars[i] = fChars[i - shift];
+ fStyle[i] = fStyle[i - shift];
+ fWrappedLines.set(i, fWrappedLines.get(i - shift));
+ }
+ cleanLines(startLine, Math.min(shift, getHeight() - startLine));
+ }
+ }
+
+ /**
+ * Replaces the lines with new empty data
+ * @param line
+ * @param len
+ */
+ private void cleanLines(int line, int len) {
+ for (int i = line; i < line + len; i++) {
+ cleanLine(i);
+ }
+ }
+
+ /*
+ * @return a text representation of the object.
+ * Lines are separated by '\n'. No style information is returned.
+ */
+ @Override
+ public String toString() {
+ StringBuffer buff = new StringBuffer();
+ for (int line = 0; line < getHeight(); line++) {
+ if (line > 0)
+ buff.append("\n"); //$NON-NLS-1$
+ for (int column = 0; column < fWidth; column++) {
+ buff.append(getChar(line, column));
+ }
+ }
+ return buff.toString();
+ }
+
+ @Override
+ public ITerminalTextDataSnapshot makeSnapshot() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addLine() {
+ if (fMaxHeight > 0 && getHeight() < fMaxHeight) {
+ setDimensions(getHeight() + 1, getWidth());
+ } else {
+ scroll(0, getHeight(), -1);
+ }
+ }
+
+ @Override
+ public void copy(ITerminalTextData source) {
+ fWidth = source.getWidth();
+ int n = source.getHeight();
+ if (getHeight() != n) {
+ fChars = new char[n][];
+ fStyle = new TerminalStyle[n][];
+ }
+ for (int i = 0; i < n; i++) {
+ copyLine(source, i, i);
+ }
+ fHeight = n;
+ fCursorLine = source.getCursorLine();
+ fCursorColumn = source.getCursorColumn();
+ }
+
+ @Override
+ public void copyRange(ITerminalTextData source, int sourceStartLine, int destStartLine, int length) {
+ for (int i = 0; i < length; i++) {
+ copyLine(source, i + sourceStartLine, i + destStartLine);
+ }
+ }
+
+ @Override
+ public void copyLine(ITerminalTextData source, int sourceLine, int destLine) {
+ fChars[destLine] = source.getChars(sourceLine);
+ fStyle[destLine] = source.getStyles(sourceLine);
+ fWrappedLines.set(destLine, source.isWrappedLine(sourceLine));
+ }
+
+ @Override
+ public char[] getChars(int line) {
+ if (fChars[line] == null)
+ return null;
+ return fChars[line].clone();
+ }
+
+ @Override
+ public TerminalStyle[] getStyles(int line) {
+ if (fStyle[line] == null)
+ return null;
+ return fStyle[line].clone();
+ }
+
+ public void setLine(int line, char[] chars, TerminalStyle[] styles) {
+ fChars[line] = chars.clone();
+ fStyle[line] = styles.clone();
+ fWrappedLines.clear(line);
+ }
+
+ @Override
+ public void setMaxHeight(int height) {
+ fMaxHeight = height;
+ }
+
+ @Override
+ public int getMaxHeight() {
+ return fMaxHeight;
+ }
+
+ @Override
+ public void cleanLine(int line) {
+ fChars[line] = null;
+ fStyle[line] = null;
+ fWrappedLines.clear(line);
+ }
+
+ @Override
+ public int getCursorColumn() {
+ return fCursorColumn;
+ }
+
+ @Override
+ public int getCursorLine() {
+ return fCursorLine;
+ }
+
+ @Override
+ public void setCursorColumn(int column) {
+ fCursorColumn = column;
+ }
+
+ @Override
+ public void setCursorLine(int line) {
+ fCursorLine = line;
+ }
+
+ @Override
+ public boolean isWrappedLine(int line) {
+ return fWrappedLines.get(line);
+ }
+
+ @Override
+ public void setWrappedLine(int line) {
+ fWrappedLines.set(line);
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataWindow.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataWindow.java
new file mode 100644
index 00000000000..31e6d625901
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataWindow.java
@@ -0,0 +1,259 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [453393] Add support for copying wrapped lines without line break
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.model;
+
+import org.eclipse.tm.terminal.model.ITerminalTextData;
+import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot;
+import org.eclipse.tm.terminal.model.LineSegment;
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ * This class stores the data only within a window {@link #setWindow(int, int)} and
+ * {@link #getWindowStartLine()} and {@link #getWindowSize()}. Everything outside
+ * the is char=='\000' and style=null.
+ *
+ */
+public class TerminalTextDataWindow implements ITerminalTextData {
+ final ITerminalTextData fData;
+ int fWindowStartLine;
+ int fWindowSize;
+ int fHeight;
+ int fMaxHeight;
+
+ public TerminalTextDataWindow(ITerminalTextData data) {
+ fData = data;
+ }
+
+ public TerminalTextDataWindow() {
+ this(new TerminalTextDataStore());
+ }
+
+ /**
+ * This is used in asserts to throw an {@link RuntimeException}.
+ * This is useful for tests.
+ * @return never -- throws an exception
+ */
+ private boolean throwRuntimeException() {
+ throw new RuntimeException();
+ }
+
+ /**
+ * @param line
+ * @return true if the line is within the window
+ */
+ boolean isInWindow(int line) {
+ return line >= fWindowStartLine && line < fWindowStartLine + fWindowSize;
+ }
+
+ @Override
+ public char getChar(int line, int column) {
+ if (!isInWindow(line))
+ return 0;
+ return fData.getChar(line - fWindowStartLine, column);
+ }
+
+ @Override
+ public char[] getChars(int line) {
+ if (!isInWindow(line))
+ return null;
+ return fData.getChars(line - fWindowStartLine);
+ }
+
+ @Override
+ public int getHeight() {
+ return fHeight;
+ }
+
+ @Override
+ public LineSegment[] getLineSegments(int line, int startCol, int numberOfCols) {
+ if (!isInWindow(line))
+ return new LineSegment[] { new LineSegment(startCol, new String(new char[numberOfCols]), null) };
+ return fData.getLineSegments(line - fWindowStartLine, startCol, numberOfCols);
+ }
+
+ @Override
+ public int getMaxHeight() {
+ return fMaxHeight;
+ }
+
+ @Override
+ public TerminalStyle getStyle(int line, int column) {
+ if (!isInWindow(line))
+ return null;
+ return fData.getStyle(line - fWindowStartLine, column);
+ }
+
+ @Override
+ public TerminalStyle[] getStyles(int line) {
+ if (!isInWindow(line))
+ return null;
+ return fData.getStyles(line - fWindowStartLine);
+ }
+
+ @Override
+ public int getWidth() {
+ return fData.getWidth();
+ }
+
+ @Override
+ public ITerminalTextDataSnapshot makeSnapshot() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addLine() {
+ if (fMaxHeight > 0 && getHeight() < fMaxHeight) {
+ setDimensions(getHeight() + 1, getWidth());
+ } else {
+ scroll(0, getHeight(), -1);
+ }
+ }
+
+ @Override
+ public void copy(ITerminalTextData source) {
+ // we inherit the dimensions of the source
+ setDimensions(source.getHeight(), source.getWidth());
+ int n = Math.min(fWindowSize, source.getHeight() - fWindowStartLine);
+ if (n > 0)
+ fData.copyRange(source, fWindowStartLine, 0, n);
+ }
+
+ @Override
+ public void copyRange(ITerminalTextData source, int sourceStartLine, int destStartLine, int length) {
+ int n = length;
+ int dStart = destStartLine - fWindowStartLine;
+ int sStart = sourceStartLine;
+ // if start outside our range, cut the length to copy
+ if (dStart < 0) {
+ n += dStart;
+ sStart -= dStart;
+ dStart = 0;
+ }
+ // do not exceed the window size
+ n = Math.min(n, fWindowSize);
+ if (n > 0)
+ fData.copyRange(source, sStart, dStart, n);
+
+ }
+
+ @Override
+ public void copyLine(ITerminalTextData source, int sourceLine, int destLine) {
+ if (isInWindow(destLine))
+ fData.copyLine(source, sourceLine, destLine - fWindowStartLine);
+ }
+
+ @Override
+ public void scroll(int startLine, int size, int shift) {
+ assert (startLine >= 0 && startLine + size <= fHeight) || throwRuntimeException();
+ int n = size;
+ int start = startLine - fWindowStartLine;
+ // if start outside our range, cut the length to copy
+ if (start < 0) {
+ n += start;
+ start = 0;
+ }
+ n = Math.min(n, fWindowSize - start);
+ // do not exceed the window size
+ if (n > 0)
+ fData.scroll(start, n, shift);
+ }
+
+ @Override
+ public void setChar(int line, int column, char c, TerminalStyle style) {
+ if (!isInWindow(line))
+ return;
+ fData.setChar(line - fWindowStartLine, column, c, style);
+ }
+
+ @Override
+ public void setChars(int line, int column, char[] chars, int start, int len, TerminalStyle style) {
+ if (!isInWindow(line))
+ return;
+ fData.setChars(line - fWindowStartLine, column, chars, start, len, style);
+ }
+
+ @Override
+ public void setChars(int line, int column, char[] chars, TerminalStyle style) {
+ if (!isInWindow(line))
+ return;
+ fData.setChars(line - fWindowStartLine, column, chars, style);
+ }
+
+ @Override
+ public void setDimensions(int height, int width) {
+ assert height >= 0 || throwRuntimeException();
+ fData.setDimensions(fWindowSize, width);
+ fHeight = height;
+ }
+
+ @Override
+ public void setMaxHeight(int height) {
+ fMaxHeight = height;
+ }
+
+ public void setWindow(int startLine, int size) {
+ fWindowStartLine = startLine;
+ fWindowSize = size;
+ fData.setDimensions(fWindowSize, getWidth());
+ }
+
+ public int getWindowStartLine() {
+ return fWindowStartLine;
+ }
+
+ public int getWindowSize() {
+ return fWindowSize;
+ }
+
+ public void setHeight(int height) {
+ fHeight = height;
+ }
+
+ @Override
+ public void cleanLine(int line) {
+ if (isInWindow(line))
+ fData.cleanLine(line - fWindowStartLine);
+ }
+
+ @Override
+ public int getCursorColumn() {
+ return fData.getCursorColumn();
+ }
+
+ @Override
+ public int getCursorLine() {
+ return fData.getCursorLine();
+ }
+
+ @Override
+ public void setCursorColumn(int column) {
+ fData.setCursorColumn(column);
+ }
+
+ @Override
+ public void setCursorLine(int line) {
+ fData.setCursorLine(line);
+ }
+
+ @Override
+ public boolean isWrappedLine(int line) {
+ if (isInWindow(line))
+ return fData.isWrappedLine(line - fWindowStartLine);
+ return false;
+ }
+
+ @Override
+ public void setWrappedLine(int line) {
+ if (isInWindow(line))
+ fData.setWrappedLine(line - fWindowStartLine);
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/ITerminalConstants.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/ITerminalConstants.java
new file mode 100644
index 00000000000..9f87a310388
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/ITerminalConstants.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ * Martin Oberhuber (Wind River) - [378691][api] push Preferences into the Widget
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.preferences;
+
+import org.eclipse.tm.terminal.model.TerminalColor;
+
+/**
+ * Constants for Terminal Preferences.
+ *
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface ITerminalConstants {
+
+ public static final String FONT_DEFINITION = "terminal.views.view.font.definition"; //$NON-NLS-1$
+ public static final String PREF_HAS_MIGRATED = "TerminalPref.migrated"; //$NON-NLS-1$
+
+ public static final String PREF_BUFFERLINES = "TerminalPrefBufferLines"; //$NON-NLS-1$
+ public static final String PREF_INVERT_COLORS = "TerminalPrefInvertColors"; //$NON-NLS-1$
+ /**
+ * @since 5.0
+ */
+ public static final String PREF_FONT_DEFINITION = "TerminalFontDefinition"; //$NON-NLS-1$
+ public static final int DEFAULT_BUFFERLINES = 1000;
+ public static final boolean DEFAULT_INVERT_COLORS = false;
+ /**
+ * @since 5.0
+ */
+ public static final String DEFAULT_FONT_DEFINITION = FONT_DEFINITION;
+
+ /**
+ * @since 5.0
+ */
+ public static String getPrefForTerminalColor(TerminalColor tc) {
+ return tc.toString();
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalColorPresets.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalColorPresets.java
new file mode 100644
index 00000000000..108ac7de0c2
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalColorPresets.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Kichwa Coders Canada Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.preferences;
+
+import static org.eclipse.tm.terminal.model.TerminalColor.BACKGROUND;
+import static org.eclipse.tm.terminal.model.TerminalColor.BLACK;
+import static org.eclipse.tm.terminal.model.TerminalColor.BLUE;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_BLACK;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_BLUE;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_CYAN;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_GREEN;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_MAGENTA;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_RED;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_WHITE;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_YELLOW;
+import static org.eclipse.tm.terminal.model.TerminalColor.CYAN;
+import static org.eclipse.tm.terminal.model.TerminalColor.FOREGROUND;
+import static org.eclipse.tm.terminal.model.TerminalColor.GREEN;
+import static org.eclipse.tm.terminal.model.TerminalColor.MAGENTA;
+import static org.eclipse.tm.terminal.model.TerminalColor.RED;
+import static org.eclipse.tm.terminal.model.TerminalColor.SELECTION_BACKGROUND;
+import static org.eclipse.tm.terminal.model.TerminalColor.SELECTION_FOREGROUND;
+import static org.eclipse.tm.terminal.model.TerminalColor.WHITE;
+import static org.eclipse.tm.terminal.model.TerminalColor.YELLOW;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalMessages;
+import org.eclipse.tm.internal.terminal.model.SystemDefaultColors;
+import org.eclipse.tm.terminal.model.TerminalColor;
+
+/**
+ * @since 5.0
+ */
+public enum TerminalColorPresets {
+
+ INSTANCE;
+
+ private final List presets = new ArrayList<>();
+
+ public List getPresets() {
+ return presets.stream().map(Preset::getName).collect(Collectors.toList());
+ }
+
+ public Preset getPreset(int index) {
+ return presets.get(index);
+ }
+
+ public static class Preset {
+ private String name;
+ private Map> map = new EnumMap<>(TerminalColor.class);
+
+ Preset(String name) {
+ this.name = name;
+ set(BLACK, 0, 0, 0);
+ set(RED, 205, 0, 0);
+ set(GREEN, 0, 205, 0);
+ set(YELLOW, 205, 205, 0);
+ set(BLUE, 0, 0, 238);
+ set(MAGENTA, 205, 0, 205);
+ set(CYAN, 0, 205, 205);
+ set(WHITE, 229, 229, 229);
+
+ set(BRIGHT_BLACK, 0, 0, 0);
+ set(BRIGHT_RED, 255, 0, 0);
+ set(BRIGHT_GREEN, 0, 255, 0);
+ set(BRIGHT_YELLOW, 255, 255, 0);
+ set(BRIGHT_BLUE, 92, 92, 255);
+ set(BRIGHT_MAGENTA, 255, 0, 255);
+ set(BRIGHT_CYAN, 0, 255, 255);
+ set(BRIGHT_WHITE, 255, 255, 255);
+
+ set(FOREGROUND, SystemDefaultColors.FOREGROUND);
+ set(BACKGROUND, SystemDefaultColors.BACKGROUND);
+ set(SELECTION_FOREGROUND, SystemDefaultColors.SELECTION_FOREGROUND);
+ set(SELECTION_BACKGROUND, SystemDefaultColors.SELECTION_BACKGROUND);
+ }
+
+ Preset set(TerminalColor color, RGB rgb) {
+ return set(color, () -> rgb);
+ }
+
+ Preset set(TerminalColor color, int r, int g, int b) {
+ return set(color, new RGB(r, g, b));
+ }
+
+ Preset set(TerminalColor color, Supplier rgbSupplier) {
+ map.put(color, rgbSupplier);
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the preset value for the given color. Will never return null
+ * because each color must be defined in the map.
+ *
+ * @param terminalColor to get RGB value for
+ * @return non-null color
+ */
+ public RGB getRGB(TerminalColor terminalColor) {
+ return map.getOrDefault(terminalColor, () -> new RGB(0, 0, 0)).get();
+ }
+ }
+
+ TerminalColorPresets() {
+ presets.add(new Preset(TerminalMessages.TerminalColorPresets_TerminalDefaults));
+ presets.add(new Preset(TerminalMessages.TerminalColorPresets_EclipseLight) //
+ .set(FOREGROUND, getDefaultPreset().getRGB(BLACK)) //
+ .set(BACKGROUND, getDefaultPreset().getRGB(WHITE)));
+ presets.add(new Preset(TerminalMessages.TerminalColorPresets_EclipseDark) //
+ .set(FOREGROUND, getDefaultPreset().getRGB(WHITE)) //
+ .set(BACKGROUND, getDefaultPreset().getRGB(BLACK)));
+ }
+
+ public Preset getDefaultPreset() {
+ return presets.get(0);
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalColorsFieldEditor.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalColorsFieldEditor.java
new file mode 100644
index 00000000000..c8835ae1965
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalColorsFieldEditor.java
@@ -0,0 +1,226 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Kichwa Coders Canada Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.preferences;
+
+import static org.eclipse.tm.terminal.model.TerminalColor.BACKGROUND;
+import static org.eclipse.tm.terminal.model.TerminalColor.BLACK;
+import static org.eclipse.tm.terminal.model.TerminalColor.BLUE;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_BLACK;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_BLUE;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_CYAN;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_GREEN;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_MAGENTA;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_RED;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_WHITE;
+import static org.eclipse.tm.terminal.model.TerminalColor.BRIGHT_YELLOW;
+import static org.eclipse.tm.terminal.model.TerminalColor.CYAN;
+import static org.eclipse.tm.terminal.model.TerminalColor.FOREGROUND;
+import static org.eclipse.tm.terminal.model.TerminalColor.GREEN;
+import static org.eclipse.tm.terminal.model.TerminalColor.MAGENTA;
+import static org.eclipse.tm.terminal.model.TerminalColor.RED;
+import static org.eclipse.tm.terminal.model.TerminalColor.SELECTION_BACKGROUND;
+import static org.eclipse.tm.terminal.model.TerminalColor.SELECTION_FOREGROUND;
+import static org.eclipse.tm.terminal.model.TerminalColor.WHITE;
+import static org.eclipse.tm.terminal.model.TerminalColor.YELLOW;
+
+import java.util.EnumMap;
+
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.preference.ColorSelector;
+import org.eclipse.jface.preference.FieldEditor;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferenceConverter;
+import org.eclipse.jface.resource.FontDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalMessages;
+import org.eclipse.tm.terminal.model.TerminalColor;
+
+/**
+ * A field editor that can be used for editing terminal colors.
+ *
+ * @since 5.0
+ */
+public class TerminalColorsFieldEditor extends FieldEditor {
+
+ private EnumMap colorSelectors;
+ private Composite controls;
+ private Font boldFont;
+
+ /**
+ * Creates a field editor for editing colors of {@link TerminalColor}.
+ * The preference names used are as they are returned from {@link TerminalColor#getPreferenceName()}
+ * @param parent
+ */
+ public TerminalColorsFieldEditor(Composite parent) {
+ super("", "", parent); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ @Override
+ protected void adjustForNumColumns(int numColumns) {
+ GridData gd = (GridData) controls.getLayoutData();
+ gd.horizontalSpan = numColumns;
+ }
+
+ @Override
+ public int getNumberOfControls() {
+ return 1;
+ }
+
+ @Override
+ protected void doFillIntoGrid(Composite parent, int numColumns) {
+ colorSelectors = new EnumMap<>(TerminalColor.class);
+ controls = new Composite(parent, SWT.NONE);
+ GridDataFactory.fillDefaults().applyTo(controls);
+ GridLayoutFactory.fillDefaults().applyTo(controls);
+
+ FontDescriptor boldDescriptor = FontDescriptor.createFrom(parent.getFont()).setStyle(SWT.BOLD);
+ boldFont = boldDescriptor.createFont(parent.getDisplay());
+
+ Group general = new Group(controls, SWT.SHADOW_NONE);
+ general.setText(TerminalMessages.TerminalColorsFieldEditor_GeneralColors);
+ general.setFont(boldFont);
+ GridDataFactory.fillDefaults().applyTo(general);
+ GridLayoutFactory.swtDefaults().numColumns(4).applyTo(general);
+
+ createLabelledSelector(general, FOREGROUND, TerminalMessages.TerminalColorsFieldEditor_TextColor);
+ createLabelledSelector(general, BACKGROUND, TerminalMessages.TerminalColorsFieldEditor_Background);
+ createLabelledSelector(general, SELECTION_BACKGROUND, TerminalMessages.TerminalColorsFieldEditor_Selection);
+ createLabelledSelector(general, SELECTION_FOREGROUND, TerminalMessages.TerminalColorsFieldEditor_SelectedText);
+
+ Group palette = new Group(controls, SWT.SHADOW_NONE);
+ palette.setText(TerminalMessages.TerminalColorsFieldEditor_PaletteColors);
+ palette.setFont(boldFont);
+ GridDataFactory.fillDefaults().applyTo(palette);
+ GridLayoutFactory.swtDefaults().numColumns(8).applyTo(palette);
+
+ createSelector(palette, BLACK, TerminalMessages.TerminalColorsFieldEditor_Black);
+ createSelector(palette, RED, TerminalMessages.TerminalColorsFieldEditor_Red);
+ createSelector(palette, GREEN, TerminalMessages.TerminalColorsFieldEditor_Green);
+ createSelector(palette, YELLOW, TerminalMessages.TerminalColorsFieldEditor_Yellow);
+ createSelector(palette, BLUE, TerminalMessages.TerminalColorsFieldEditor_Blue);
+ createSelector(palette, MAGENTA, TerminalMessages.TerminalColorsFieldEditor_Magenta);
+ createSelector(palette, CYAN, TerminalMessages.TerminalColorsFieldEditor_Cyan);
+ createSelector(palette, WHITE, TerminalMessages.TerminalColorsFieldEditor_White);
+
+ createSelector(palette, BRIGHT_BLACK, TerminalMessages.TerminalColorsFieldEditor_BrightBlack);
+ createSelector(palette, BRIGHT_RED, TerminalMessages.TerminalColorsFieldEditor_BrightRed);
+ createSelector(palette, BRIGHT_GREEN, TerminalMessages.TerminalColorsFieldEditor_BrightGreen);
+ createSelector(palette, BRIGHT_YELLOW, TerminalMessages.TerminalColorsFieldEditor_BrightYellow);
+ createSelector(palette, BRIGHT_BLUE, TerminalMessages.TerminalColorsFieldEditor_BrightBlue);
+ createSelector(palette, BRIGHT_MAGENTA, TerminalMessages.TerminalColorsFieldEditor_BrightMagenta);
+ createSelector(palette, BRIGHT_CYAN, TerminalMessages.TerminalColorsFieldEditor_BrightCyan);
+ createSelector(palette, BRIGHT_WHITE, TerminalMessages.TerminalColorsFieldEditor_BrightWhite);
+
+ Group presets = new Group(controls, SWT.SHADOW_NONE);
+ presets.setText(TerminalMessages.TerminalColorsFieldEditor_Presets);
+ presets.setFont(boldFont);
+ GridDataFactory.fillDefaults().applyTo(presets);
+ GridLayoutFactory.swtDefaults().numColumns(2).applyTo(presets);
+ Combo presetsCombo = new Combo(presets, SWT.DROP_DOWN | SWT.READ_ONLY);
+ presetsCombo.add(TerminalMessages.TerminalColorsFieldEditor_LoadPresets);
+ TerminalColorPresets colorPresets = TerminalColorPresets.INSTANCE;
+ colorPresets.getPresets().forEach(presetsCombo::add);
+ presetsCombo.addListener(SWT.Selection, e -> {
+ int selectionIndex = presetsCombo.getSelectionIndex();
+ if (selectionIndex > 0) {
+ int selectedPresetIndex = selectionIndex - 1; // account for "Load Presets..." entry
+ colorSelectors.forEach((terminalColor, colorSelector) -> colorSelector
+ .setColorValue(colorPresets.getPreset(selectedPresetIndex).getRGB(terminalColor)));
+
+ }
+ });
+ presetsCombo.select(0);
+ }
+
+ @Override
+ public void dispose() {
+ if (boldFont != null) {
+ boldFont.dispose();
+ }
+ }
+
+ private void createLabelledSelector(Composite parent, TerminalColor color, String label) {
+ Label labelControl = new Label(parent, SWT.LEFT);
+ labelControl.setText(label);
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).applyTo(labelControl);
+ createSelector(parent, color, label);
+ }
+
+ private void createSelector(Composite parent, TerminalColor color, String label) {
+ ColorSelector colorSelector = new ColorSelector(parent);
+ colorSelector.getButton().setToolTipText(label);
+ GridDataFactory.fillDefaults().applyTo(colorSelector.getButton());
+ colorSelectors.put(color, colorSelector);
+ }
+
+ @Override
+ protected void doLoad() {
+ IPreferenceStore store = getPreferenceStore();
+ colorSelectors.forEach((terminalColor, colorSelector) -> colorSelector.setColorValue(
+ PreferenceConverter.getColor(store, ITerminalConstants.getPrefForTerminalColor(terminalColor))));
+ }
+
+ @Override
+ protected void doLoadDefault() {
+ IPreferenceStore store = getPreferenceStore();
+ colorSelectors.forEach((terminalColor, colorSelector) -> colorSelector.setColorValue(
+ PreferenceConverter.getDefaultColor(store, ITerminalConstants.getPrefForTerminalColor(terminalColor))));
+ }
+
+ @Override
+ public void store() {
+ IPreferenceStore store = getPreferenceStore();
+ if (store == null) {
+ return;
+ }
+
+ if (presentsDefaultValue()) {
+ doStoreDefault(store);
+ } else {
+ doStore();
+ }
+ }
+
+ /**
+ * Stores the default preference value from this field editor into
+ * the preference store.
+ */
+ protected void doStoreDefault(IPreferenceStore store) {
+ colorSelectors.forEach((terminalColor, colorSelector) -> store
+ .setToDefault(ITerminalConstants.getPrefForTerminalColor(terminalColor)));
+ }
+
+ @Override
+ protected void doStore() {
+ IPreferenceStore store = getPreferenceStore();
+ colorSelectors.forEach((terminalColor, colorSelector) -> PreferenceConverter.setValue(store,
+ ITerminalConstants.getPrefForTerminalColor(terminalColor), colorSelector.getColorValue()));
+ }
+
+ @Override
+ public String getPreferenceName() {
+ throw new IllegalArgumentException(
+ "preference name should not be accessed as this class represent multiple preferences"); //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLabelText() {
+ throw new IllegalArgumentException(
+ "label text should not be accessed as this class represent multiple preferences"); //$NON-NLS-1$
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalPreferenceInitializer.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalPreferenceInitializer.java
new file mode 100644
index 00000000000..d6d737459b4
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalPreferenceInitializer.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ * Martin Oberhuber (Wind River) - [378691][api] push Preferences into the Widget
+ * Martin Oberhuber (Wind River) - [436612] Restore Eclipse 3.4 compatibility
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.preferences;
+
+import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
+import org.eclipse.core.runtime.preferences.DefaultScope;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.jface.resource.StringConverter;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin;
+import org.eclipse.tm.internal.terminal.preferences.TerminalColorPresets.Preset;
+import org.eclipse.tm.terminal.model.TerminalColor;
+
+/**
+ * Terminal Preference Initializer.
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ * @noinstantiate This class is not intended to be instantiated by clients.
+ * @noreference This class is not intended to be referenced by clients.
+ */
+public class TerminalPreferenceInitializer extends AbstractPreferenceInitializer {
+
+ public TerminalPreferenceInitializer() {
+ }
+
+ @Override
+ public void initializeDefaultPreferences() {
+ //DefaultScope.INSTANCE was added in Eclipse 3.7
+ IEclipsePreferences defaultPrefs = DefaultScope.INSTANCE.getNode(TerminalPlugin.PLUGIN_ID);
+ defaultPrefs.putBoolean(ITerminalConstants.PREF_INVERT_COLORS, ITerminalConstants.DEFAULT_INVERT_COLORS);
+ defaultPrefs.putInt(ITerminalConstants.PREF_BUFFERLINES, ITerminalConstants.DEFAULT_BUFFERLINES);
+ defaultPrefs.put(ITerminalConstants.PREF_FONT_DEFINITION, ITerminalConstants.DEFAULT_FONT_DEFINITION);
+
+ Preset defaultPresets = TerminalColorPresets.INSTANCE.getDefaultPreset();
+ TerminalColor[] colors = TerminalColor.values();
+ for (TerminalColor color : colors) {
+ defaultPrefs.put(ITerminalConstants.getPrefForTerminalColor(color),
+ StringConverter.asString(defaultPresets.getRGB(color)));
+ }
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalPreferencePage.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalPreferencePage.java
new file mode 100644
index 00000000000..27888302669
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/preferences/TerminalPreferencePage.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Initial Contributors:
+ * The following Wind River employees contributed to the Terminal component
+ * that contains this file: Chris Thew, Fran Litterio, Stephen Lamb,
+ * Helmut Haigermoser and Ted Williams.
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - split into core, view and connector plugins
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ * Martin Oberhuber (Wind River) - [378691][api] push Preferences into the Widget
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.preferences;
+
+import org.eclipse.jface.preference.BooleanFieldEditor;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.IntegerFieldEditor;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalMessages;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+
+/**
+ * Terminal Preference Page.
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ * @noinstantiate This class is not intended to be instantiated by clients.
+ * @noreference This class is not intended to be referenced by clients.
+ */
+public class TerminalPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
+
+ private TerminalColorsFieldEditor terminalColorsFieldEditor;
+
+ public TerminalPreferencePage() {
+ super(GRID);
+ }
+
+ @Override
+ protected void createFieldEditors() {
+ setupPage();
+ }
+
+ @Override
+ public void init(IWorkbench workbench) {
+ // do nothing
+ }
+
+ protected void setupPage() {
+ setupData();
+ setupEditors();
+ }
+
+ protected void setupData() {
+ TerminalPlugin plugin;
+ IPreferenceStore preferenceStore;
+
+ plugin = TerminalPlugin.getDefault();
+ preferenceStore = plugin.getPreferenceStore();
+ setPreferenceStore(preferenceStore);
+ }
+
+ protected void setupEditors() {
+ addField(new BooleanFieldEditor(ITerminalConstants.PREF_INVERT_COLORS, TerminalMessages.INVERT_COLORS,
+ getFieldEditorParent()));
+
+ addField(new IntegerFieldEditor(ITerminalConstants.PREF_BUFFERLINES, TerminalMessages.BUFFERLINES,
+ getFieldEditorParent()));
+
+ terminalColorsFieldEditor = new TerminalColorsFieldEditor(getFieldEditorParent());
+ addField(terminalColorsFieldEditor);
+ }
+
+ @Override
+ public void dispose() {
+ if (terminalColorsFieldEditor != null) {
+ terminalColorsFieldEditor.dispose();
+ }
+
+ super.dispose();
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/AbstractSettingsPage.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/AbstractSettingsPage.java
new file mode 100644
index 00000000000..971e30e05f1
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/AbstractSettingsPage.java
@@ -0,0 +1,176 @@
+/*******************************************************************************
+ * Copyright (c) 2013, 2018 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 2.0 which accompanies this distribution, and is
+ * available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.provisional.api;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.jface.fieldassist.FieldDecoration;
+import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * Abstract settings page providing a common implementation of the listener handling.
+ */
+public abstract class AbstractSettingsPage implements ISettingsPage, IMessageProvider {
+ // A message associated with the control.
+ private String message = null;
+
+ // The message type of the associated message.
+ private int messageType = IMessageProvider.NONE;
+
+ // Reference to the listener
+ private final ListenerList listeners = new ListenerList<>();
+
+ // Flag to control the control decorations
+ private boolean hasDecoration = false;
+
+ @Override
+ public void addListener(Listener listener) {
+ Assert.isNotNull(listener);
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(Listener listener) {
+ Assert.isNotNull(listener);
+ listeners.remove(listener);
+ }
+
+ /**
+ * Fire the listeners for the given control.
+ *
+ * @param control The control or null.
+ */
+ public void fireListeners(Control control) {
+ Object[] list = listeners.getListeners();
+ for (int i = 0; i < list.length; i++) {
+ Object l = list[i];
+ if (!(l instanceof Listener))
+ continue;
+ ((Listener) l).onSettingsPageChanged(control);
+ }
+ }
+
+ @Override
+ public final String getMessage() {
+ return message;
+ }
+
+ @Override
+ public final int getMessageType() {
+ return messageType;
+ }
+
+ /**
+ * Set the message and the message type.
+ *
+ * @param message The message or null.
+ * @param messageType The type of the message (NONE, INFORMATION, WARNING, ERROR).
+ */
+ public final void setMessage(String message, int messageType) {
+ this.message = message;
+ this.messageType = messageType;
+ }
+
+ /**
+ * Sets if or if not the settings panel widgets will have control decorations
+ * or not. The method has effect only if called before {@link #createControl(org.eclipse.swt.widgets.Composite)}.
+ *
+ * @param value True if the panel widgets have control decorations, false otherwise.
+ */
+ public final void setHasControlDecoration(boolean value) {
+ this.hasDecoration = value;
+ }
+
+ /**
+ * Returns if or if not the settings panel widgets will have control
+ * decorations or not.
+ *
+ * @return True if the panel widgets have control decorations, false otherwise.
+ */
+ protected final boolean hasControlDecoration() {
+ return hasDecoration;
+ }
+
+ /**
+ * Creates a new instance of a {@link ControlDecoration} object associated with
+ * the given control. The method is called after the control has been created.
+ *
+ * @param control The control. Must not be null.
+ * @return The control decoration object instance.
+ */
+ protected final ControlDecoration createControlDecoration(Control control) {
+ Assert.isNotNull(control);
+ if (!hasDecoration)
+ return null;
+ ControlDecoration controlDecoration = new ControlDecoration(control, getControlDecorationPosition());
+ controlDecoration.setShowOnlyOnFocus(false);
+ control.setData("controlDecoration", controlDecoration); //$NON-NLS-1$
+ return controlDecoration;
+ }
+
+ /**
+ * Returns the control decoration position. The default is
+ * {@link SWT#TOP} | {@link SWT#LEFT}.
+ *
+ * @return The control position.
+ */
+ protected int getControlDecorationPosition() {
+ return SWT.TOP | SWT.LEFT;
+ }
+
+ /**
+ * Updates the control decoration of the given control to represent the given message
+ * and message type. If the message is null or the message type is
+ * {@link IMessageProvider#NONE} no decoration will be shown.
+ *
+ * @param control The control. Must not be null.
+ * @param message The message.
+ * @param messageType The message type.
+ */
+ protected final void updateControlDecoration(Control control, String message, int messageType) {
+ Assert.isNotNull(control);
+
+ ControlDecoration controlDecoration = (ControlDecoration) control.getData("controlDecoration"); //$NON-NLS-1$
+ if (controlDecoration != null) {
+ // The description is the same as the message
+ controlDecoration.setDescriptionText(message);
+
+ // The icon depends on the message type
+ FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault();
+
+ // Determine the id of the decoration to show
+ String decorationId = FieldDecorationRegistry.DEC_INFORMATION;
+ if (messageType == IMessageProvider.ERROR) {
+ decorationId = FieldDecorationRegistry.DEC_ERROR;
+ } else if (messageType == IMessageProvider.WARNING) {
+ decorationId = FieldDecorationRegistry.DEC_WARNING;
+ }
+
+ // Get the field decoration
+ FieldDecoration fieldDeco = registry.getFieldDecoration(decorationId);
+ if (fieldDeco != null) {
+ controlDecoration.setImage(fieldDeco.getImage());
+ }
+
+ if (message == null || messageType == IMessageProvider.NONE) {
+ controlDecoration.hide();
+ } else {
+ controlDecoration.show();
+ }
+ }
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ISettingsPage.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ISettingsPage.java
new file mode 100644
index 00000000000..33e895a0578
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ISettingsPage.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.provisional.api;
+
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * @author Michael Scharf
+ *
+ * TODO: Michael Scharf: provide a long description of a wizard
+ * TODO: Michael Scharf: allow multiple pages to be generated
+ *
+ * EXPERIMENTAL. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will
+ * work or that it will remain the same. Please do not use this API without
+ * consulting with the Target Management team.
+ *
+ */
+public interface ISettingsPage {
+
+ public interface Listener {
+
+ /**
+ * Invoked by settings page controls to signal that the settings page
+ * changed and page container may update their state.
+ *
+ * @param control The control which triggered the event or null
+ */
+ public void onSettingsPageChanged(Control control);
+ }
+
+ /**
+ * Create a page to be shown in a dialog or wizard to setup the connection.
+ * @param parent
+ */
+ void createControl(Composite parent);
+
+ /**
+ * Called before the page is shown. Loads the state from the {@link ITerminalConnector}.
+ */
+ void loadSettings();
+
+ /**
+ * Called when the OK button is pressed.
+ */
+ void saveSettings();
+
+ /**
+ * @return true if the
+ */
+ boolean validateSettings();
+
+ /**
+ * Adds the given listener.
+ *
+ * Has not effect if the same listener is already registered.
+ *
+ * @param listener The listener. Must not be null.
+ */
+ public void addListener(Listener listener);
+
+ /**
+ * Removes the given listener.
+ *
+ * Has no effect if the same listener was not registered.
+ *
+ * @param listener The listener. Must not be null.
+ */
+ public void removeListener(Listener listener);
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ISettingsStore.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ISettingsStore.java
new file mode 100644
index 00000000000..71cfc58164b
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ISettingsStore.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.provisional.api;
+
+/**
+ * A simple interface to a store to persist the state of a connection.
+ *
+ * @author Michael Scharf
+ *
+ * EXPERIMENTAL. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will
+ * work or that it will remain the same. Please do not use this API without
+ * consulting with the Target Management team.
+ *
+ */
+public interface ISettingsStore {
+ /**
+ * @param key alpha numeric key, may contain dots (.)
+ * @return value
+ */
+ String get(String key);
+
+ /**
+ * @param key alpha numeric key, may contain dots (.)
+ * @param defaultValue
+ * @return the value or the default
+ */
+ String get(String key, String defaultValue);
+
+ /**
+ * Save a string value
+ * @param key alpha numeric key, may contain dots (.)
+ * @param value
+ */
+ void put(String key, String value);
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ITerminalConnector.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ITerminalConnector.java
new file mode 100644
index 00000000000..d1a5fe55f70
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ITerminalConnector.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ * Martin Oberhuber (Wind River) - [261486][api][cleanup] Mark @noimplement interfaces as @noextend
+ * Uwe Stieber (Wind River) - [282996] [terminal][api] Add "hidden" attribute to terminal connector extension point
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.provisional.api;
+
+import java.io.OutputStream;
+import java.util.Optional;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.tm.internal.terminal.control.ITerminalViewControl;
+import org.eclipse.tm.internal.terminal.provisional.api.provider.TerminalConnectorImpl;
+
+/**
+ * A contributed connection type to manage a single connection.
+ *
+ * Implementations of this class are contributed through the
+ * org.eclipse.tm.terminal.control.connectors extension point. This
+ * class gives access to the static markup of a terminal connector extension as
+ * well as providing the lifecycle management for the dynamically loaded
+ * {@link TerminalConnectorImpl} instance, which performs the actual
+ * communications. This pattern allows for lazy initialization, bundle
+ * activation and class loading of the actual {@link TerminalConnectorImpl}
+ * instance.
+ *
+ * Clients can get terminal connector instances from the
+ * {@link TerminalConnectorExtension} class, or from
+ * {@link ITerminalViewControl#getTerminalConnector()} when running inside an
+ * active terminal widget.
+ *
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ *
+ * @author Michael Scharf
+ *
+ * EXPERIMENTAL. This class or interface has been added
+ * as part of a work in progress. There is no guarantee that this API
+ * will work or that it will remain the same. Please do not use this API
+ * without consulting with the Target Management team.
+ *
+ */
+public interface ITerminalConnector extends IAdaptable {
+ /**
+ * @return an ID of this connector. The id from the plugin.xml.
+ * @since org.eclipse.tm.terminal 2.0
+ */
+ String getId();
+
+ /**
+ * @return null the name (as specified in the plugin.xml)
+ * @since org.eclipse.tm.terminal 2.0
+ */
+ String getName();
+
+ /**
+ * @return True if the connector is not visible in user
+ * selections.
+ * @since org.eclipse.tm.terminal 3.0.1
+ */
+ boolean isHidden();
+
+ /**
+ * @return true if the {@link TerminalConnectorImpl} has been initialized.
+ * If there was an initialization error, {@link #getInitializationErrorMessage()}
+ * returns the error message.
+ * @since org.eclipse.tm.terminal 2.0
+ */
+ boolean isInitialized();
+
+ /**
+ * This method initializes the connector if it is not initialized!
+ * If the connector was initialized successfully, null is
+ * returned. Otherwise an error message describing the problem is returned.
+ * @return null or a localized error message.
+ * @since org.eclipse.tm.terminal 2.0
+ */
+ String getInitializationErrorMessage();
+
+ /**
+ * Connect using the current state of the settings.
+ * @param control Used to inform the UI about state changes and messages from the connection.
+ */
+ void connect(ITerminalControl control);
+
+ /**
+ * Disconnect if connected. Else do nothing.
+ */
+ void disconnect();
+
+ /**
+ * @return true if a local echo is needed.
+ * TODO:Michael Scharf: this should be handed within the connection....
+ */
+ boolean isLocalEcho();
+
+ /**
+ * Notify the remote site that the size of the terminal has changed.
+ * @param newWidth
+ * @param newHeight
+ */
+ void setTerminalSize(int newWidth, int newHeight);
+
+ /**
+ * @return the terminal to remote stream (bytes written to this stream will
+ * be sent to the remote site). For the stream in the other direction (remote to
+ * terminal see {@link ITerminalControl#getRemoteToTerminalOutputStream()}
+ * @since org.eclipse.tm.terminal 2.0
+ */
+ OutputStream getTerminalToRemoteStream();
+
+ /**
+ * Load the state of this connection. Is typically called before
+ * {@link #connect(ITerminalControl)}.
+ *
+ * @param store a string based data store. Short keys like "foo" can be used to
+ * store the state of the connection.
+ */
+ void load(ISettingsStore store);
+
+ /**
+ * When the view or dialog containing the terminal is closed,
+ * the state of the connection is saved into the settings store store
+ * @param store
+ */
+ void save(ISettingsStore store);
+
+ /**
+ * Set or reset the settings store to the default values.
+ */
+ void setDefaultSettings();
+
+ /**
+ * @return A string that represents the settings of the connection. This representation
+ * may be shown in the status line of the terminal view.
+ */
+ String getSettingsSummary();
+
+ /**
+ * @return An optional with the absolute path if available of the current working dir, empty otherwise.
+ * @since 5.2
+ */
+ default Optional getWorkingDirectory() {
+ return Optional.empty();
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ITerminalControl.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ITerminalControl.java
new file mode 100644
index 00000000000..b47c0788a2e
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/ITerminalControl.java
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ * Martin Oberhuber (Wind River) - [204796] Terminal should allow setting the encoding to use
+ * Martin Oberhuber (Wind River) - [261486][api][cleanup] Mark @noimplement interfaces as @noextend
+ * Anton Leherbauer (Wind River) - [433751] Add option to enable VT100 line wrapping mode
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.provisional.api;
+
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tm.internal.terminal.control.ITerminalListener3.TerminalTitleRequestor;
+
+/**
+ * Represents the terminal view as seen by a terminal connection.
+ *
+ * EXPERIMENTAL. This class or interface has been added as part
+ * of a work in progress. There is no guarantee that this API will work or that
+ * it will remain the same. Please do not use this API without consulting with
+ * the Target Management team.
+ *
+ *
+ * @author Michael Scharf
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ */
+public interface ITerminalControl {
+
+ /**
+ * @return the current state of the connection
+ */
+ TerminalState getState();
+
+ /**
+ * @param state
+ */
+ void setState(TerminalState state);
+
+ /**
+ * Setup the terminal control within the given parent composite.
+ *
+ * @param parent The parent composite. Must not be null.
+ */
+ void setupTerminal(Composite parent);
+
+ /**
+ * A shell to show dialogs.
+ * @return the shell in which the terminal is shown.
+ */
+ Shell getShell();
+
+ /**
+ * Set the encoding that the Terminal uses to decode bytes from the
+ * Terminal-to-remote-Stream into Unicode Characters used in Java; or, to
+ * encode Characters typed by the user into bytes sent over the wire to the
+ * remote.
+ *
+ * By default, the local Platform Default Encoding is used. Also note that
+ * the encoding must not be applied in case the terminal stream is processed
+ * by some data transfer protocol which requires binary data.
+ *
+ * Validity of the encoding set here is not checked. Since some encodings do
+ * not cover the entire range of Unicode characters, it can happen that a
+ * particular Unicode String typed in by the user can not be encoded into a
+ * byte Stream with the encoding specified. and UnsupportedEncodingException
+ * will be thrown in this case at the time the String is about to be
+ * processed.
+ *
+ * The concrete encoding to use can either be specified manually by a user,
+ * by means of a dialog, or a connector can try to obtain it automatically
+ * from the remote side e.g. by evaluating an environment variable such as
+ * LANG on UNIX systems.
+ *
+ * @since org.eclipse.tm.terminal 2.0
+ * @deprecated Use {@link #setCharset(Charset)} and do the error handling in the UI code.
+ */
+ @Deprecated
+ void setEncoding(String encoding) throws UnsupportedEncodingException;
+
+ /**
+ * Set the charset that the Terminal uses to decode bytes from the
+ * Terminal-to-remote-Stream into Unicode Characters used in Java; or, to
+ * encode Characters typed by the user into bytes sent over the wire to the
+ * remote.
+ *
+ * By default, the local Platform Default charset is used. Also note that
+ * the encoding must not be applied in case the terminal stream is processed
+ * by some data transfer protocol which requires binary data.
+ *
+ * Validity of the charset set here is not checked. Since some encodings do
+ * not cover the entire range of Unicode characters, it can happen that a
+ * particular Unicode String typed in by the user can not be encoded into a
+ * byte Stream with the encoding specified. and UnsupportedEncodingException
+ * will be thrown in this case at the time the String is about to be
+ * processed.
+ *
+ * The concrete encoding to use can either be specified manually by a user,
+ * by means of a dialog, or a connector can try to obtain it automatically
+ * from the remote side e.g. by evaluating an environment variable such as
+ * LANG on UNIX systems.
+ *
+ * @param charset Charset to use, or null for platform's default charset.
+ *
+ * @since 5.3
+ */
+ void setCharset(Charset charset);
+
+ /**
+ * Return the current encoding. That's interesting when the previous
+ * setEncoding() call failed and the fallback default encoding should be
+ * queried, such that e.g. a combobox with encodings to choose can be
+ * properly initialized.
+ *
+ * @return the current Encoding of the Terminal.
+ * @since org.eclipse.tm.terminal 2.0
+ * @deprecated Use {@link #getCharset()} and call {@link Charset#name()} on the result
+ */
+ @Deprecated
+ String getEncoding();
+
+ /**
+ * Return the current charset.
+ *
+ * @return the non-null current charset of the Terminal
+ * @since 5.3
+ */
+ Charset getCharset();
+
+ /**
+ * Show a text in the terminal. If puts newlines at the beginning and the
+ * end.
+ *
+ * @param text TODO: Michael Scharf: Is this really needed?
+ */
+ void displayTextInTerminal(String text);
+
+ /**
+ * @return a stream used to write to the terminal. Any bytes written to this
+ * stream appear in the terminal or are interpreted by the emulator as
+ * control sequences. The stream in the opposite direction, terminal
+ * to remote is in {@link ITerminalConnector#getTerminalToRemoteStream()}.
+ */
+ OutputStream getRemoteToTerminalOutputStream();
+
+ /**
+ * @deprecated call {@link #setTerminalTitle(String, String)} instead
+ */
+ @Deprecated(forRemoval = true)
+ void setTerminalTitle(String title);
+
+ /**
+ * Set the title of the terminal view.
+ * @param title Termianl title.
+ * @param requestor Item that requests terminal title update.
+ * @since 5.5
+ */
+ void setTerminalTitle(String title, TerminalTitleRequestor requestor);
+
+ /**
+ * Show an error message during connect.
+ * @param msg
+ * TODO: Michael Scharf: Should be replaced by a better error notification mechanism!
+ */
+ void setMsg(String msg);
+
+ /**
+ * Sets if or if not the terminal view control should try to reconnect
+ * the terminal connection if the user hits ENTER in a closed terminal.
+ *
+ * Reconnect on ENTER if terminal is closed is enabled by default.
+ *
+ * @param on True to enable the reconnect, false to disable it.
+ */
+ void setConnectOnEnterIfClosed(boolean on);
+
+ /**
+ * Returns if or if not the terminal view control should try to reconnect
+ * the terminal connection if the user hits ENTER in a closed terminal.
+ *
+ * @return True the reconnect is enabled, false if disabled.
+ */
+ boolean isConnectOnEnterIfClosed();
+
+ /**
+ * Enables VT100 line wrapping mode (default is off).
+ * This corresponds to the VT100 'eat_newline_glitch' terminal capability.
+ * If enabled, writing to the rightmost column does not cause
+ * an immediate wrap to the next line. Instead the line wrap occurs on the
+ * next output character.
+ *
+ * @param enable whether to enable or disable VT100 line wrapping mode
+ */
+ void setVT100LineWrapping(boolean enable);
+
+ /**
+ * @return whether VT100 line wrapping mode is enabled
+ */
+ boolean isVT100LineWrapping();
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/Logger.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/Logger.java
new file mode 100644
index 00000000000..36096598ac4
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/Logger.java
@@ -0,0 +1,247 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Fran Litterio (Wind River) - initial API and implementation
+ * Ted Williams (Wind River) - refactored into org.eclipse namespace
+ * Michael Scharf (Wind River) - split into core, view and connector plugins
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.provisional.api;
+
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.lang.StackWalker.StackFrame;
+import java.util.Optional;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin;
+
+/**
+ * A simple logger class. Every method in this class is static, so they can be
+ * called from both class and instance methods. To use this class, write code
+ * like this:
+ *
+ *
+ *
+ * Logger.log("something has happened");
+ * Logger.log("counter is " + counter);
+ *
+ *
+ * @author Fran Litterio
+ *
+ * EXPERIMENTAL. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will
+ * work or that it will remain the same. Please do not use this API without
+ * consulting with the Target Management team.
+ *
+ */
+public final class Logger {
+ public static final String TRACE_DEBUG_LOG = "org.eclipse.tm.terminal.control/debug/log"; //$NON-NLS-1$
+ public static final String TRACE_DEBUG_LOG_CHAR = "org.eclipse.tm.terminal.control/debug/log/char"; //$NON-NLS-1$
+ public static final String TRACE_DEBUG_LOG_VT100BACKEND = "org.eclipse.tm.terminal.control/debug/log/VT100Backend"; //$NON-NLS-1$
+ /** @since 5.2 */
+ public static final String TRACE_DEBUG_LOG_HOVER = "org.eclipse.tm.terminal.control/debug/log/hover"; //$NON-NLS-1$
+
+ private static PrintStream logStream;
+ private static StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+
+ private static boolean underTest = false;
+
+ /**
+ * When underTest we want exception that are deep inside the code to be surfaced to the test
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ public static void setUnderTest(boolean underTest) {
+ Logger.underTest = underTest;
+ }
+
+ static {
+ // Any of the known debugging options turns on the creation of the log file
+ boolean createLogFile = TerminalPlugin.isOptionEnabled(TRACE_DEBUG_LOG)
+ || TerminalPlugin.isOptionEnabled(TRACE_DEBUG_LOG_CHAR)
+ || TerminalPlugin.isOptionEnabled(TRACE_DEBUG_LOG_VT100BACKEND)
+ || TerminalPlugin.isOptionEnabled(TRACE_DEBUG_LOG_HOVER);
+
+ // Log only if tracing is enabled
+ if (createLogFile && TerminalPlugin.getDefault() != null) {
+ IPath logFile = Platform.getStateLocation(TerminalPlugin.getDefault().getBundle());
+ if (logFile != null && logFile.toFile().isDirectory()) {
+ logFile = logFile.append("tmterminal.log"); //$NON-NLS-1$
+ try {
+ logStream = new PrintStream(new FileOutputStream(logFile.toFile(), true));
+ } catch (Exception ex) {
+ logStream = System.err;
+ logStream.println("Exception when opening log file -- logging to stderr!"); //$NON-NLS-1$
+ ex.printStackTrace(logStream);
+ }
+ }
+ }
+ }
+
+ /**
+ * Encodes a String such that non-printable control characters are
+ * converted into user-readable escape sequences for logging.
+ * @param message String to encode
+ * @return encoded String
+ */
+ public static final String encode(String message) {
+ boolean encoded = false;
+ StringBuffer buf = new StringBuffer(message.length() + 32);
+ for (int i = 0; i < message.length(); i++) {
+ char c = message.charAt(i);
+ switch (c) {
+ case '\\':
+ case '\'':
+ buf.append('\\');
+ buf.append(c);
+ encoded = true;
+ break;
+ case '\r':
+ buf.append('\\');
+ buf.append('r');
+ encoded = true;
+ break;
+ case '\n':
+ buf.append('\\');
+ buf.append('n');
+ encoded = true;
+ break;
+ case '\t':
+ buf.append('\\');
+ buf.append('t');
+ encoded = true;
+ break;
+ case '\f':
+ buf.append('\\');
+ buf.append('f');
+ encoded = true;
+ break;
+ case '\b':
+ buf.append('\\');
+ buf.append('b');
+ encoded = true;
+ break;
+ default:
+ if (c <= '\u000f') {
+ buf.append('\\');
+ buf.append('x');
+ buf.append('0');
+ buf.append(Integer.toHexString(c));
+ encoded = true;
+ } else if (c >= ' ' && c < '\u007f') {
+ buf.append(c);
+ } else if (c <= '\u00ff') {
+ buf.append('\\');
+ buf.append('x');
+ buf.append(Integer.toHexString(c));
+ encoded = true;
+ } else {
+ buf.append('\\');
+ buf.append('u');
+ if (c <= '\u0fff') {
+ buf.append('0');
+ }
+ buf.append(Integer.toHexString(c));
+ encoded = true;
+ }
+ }
+ }
+ if (encoded) {
+ return buf.toString();
+ }
+ return message;
+ }
+
+ /**
+ * Checks if logging is enabled.
+ * @return true if logging is enabled.
+ */
+ public static final boolean isLogEnabled() {
+ return (logStream != null);
+ }
+
+ /**
+ * Logs the specified message. Do not append a newline to parameter
+ * message. This method does that for you.
+ *
+ * Does not write to the message to the Eclipse log
+ *
+ * @param message A String containing the message to log.
+ */
+ public static final void log(String message) {
+ if (logStream != null) {
+ logStream.println(getCallSiteDescription() + ": " + message); //$NON-NLS-1$
+ logStream.flush();
+ }
+ }
+
+ /**
+ * Writes an error message to the Terminal log and the Eclipse log
+ * @since 5.2
+ */
+ public static final void logError(String message) {
+ logStatus(new Status(IStatus.ERROR, TerminalPlugin.PLUGIN_ID, IStatus.OK, message, null));
+ }
+
+ /**
+ * Writes an exception to the Terminal log and the Eclipse log
+ */
+ public static final void logException(Exception ex) {
+ if (underTest) {
+ throw new RuntimeException("Terminal Under Test - examine cause for real failure", ex); //$NON-NLS-1$
+ }
+ logStatus(new Status(IStatus.ERROR, TerminalPlugin.PLUGIN_ID, IStatus.OK, ex.getMessage(), ex));
+ }
+
+ /**
+ * Writes a Status to the Terminal log and the Eclipse log
+ * @since 5.2
+ */
+ public static final void logStatus(IStatus status) {
+ // log in eclipse error log
+ if (TerminalPlugin.getDefault() != null) {
+ TerminalPlugin.getDefault().getLog().log(status);
+ } else {
+ System.err.println(status);
+ if (status.getException() != null) {
+ status.getException().printStackTrace();
+ }
+ }
+ // Additional Tracing for debug purposes:
+ // Read my own stack to get the class name, method name, and line number
+ // of where this method was called
+ if (logStream != null) {
+ logStream.println(getCallSiteDescription() + ": " + //$NON-NLS-1$
+ status);
+ if (status.getException() != null) {
+ status.getException().printStackTrace(logStream);
+ }
+ }
+ }
+
+ /**
+ * Return a description string of the call site of this logging call for use in logged messages.
+ * This method will walk the stack to find the first method in the call stack not from the Logger
+ * class.
+ */
+ private static String getCallSiteDescription() {
+ Optional stackFrame = walker
+ .walk(stream -> stream.filter(f -> f.getDeclaringClass() != Logger.class).findFirst());
+ int lineNumber = stackFrame.map(StackFrame::getLineNumber).orElse(0);
+ String className = stackFrame.map(StackFrame::getDeclaringClass).map(Class::getName)
+ .map(name -> name.substring(name.lastIndexOf('.') + 1)).orElse("UnknownClass"); //$NON-NLS-1$
+ String methodName = stackFrame.map(StackFrame::getMethodName).orElse("unknownMethod"); //$NON-NLS-1$
+ String locationString = className + "." + methodName + ":" + lineNumber; //$NON-NLS-1$//$NON-NLS-2$
+ return locationString;
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/NullSettingsStore.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/NullSettingsStore.java
new file mode 100644
index 00000000000..dc7d3b668ec
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/NullSettingsStore.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2018 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 2.0 which accompanies this distribution, and is
+ * available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.provisional.api;
+
+/**
+ * A settings store implementation doing nothing.
+ */
+public class NullSettingsStore implements ISettingsStore {
+
+ @Override
+ public String get(String key) {
+ return null;
+ }
+
+ @Override
+ public String get(String key, String defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public void put(String key, String value) {
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/TerminalConnectorExtension.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/TerminalConnectorExtension.java
new file mode 100644
index 00000000000..1db1bbb54eb
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/TerminalConnectorExtension.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ * Uwe Stieber (Wind River) - [282996] [terminal][api] Add "hidden" attribute to terminal connector extension point
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.provisional.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.RegistryFactory;
+import org.eclipse.tm.internal.terminal.connector.TerminalConnector;
+import org.eclipse.tm.internal.terminal.provisional.api.provider.TerminalConnectorImpl;
+
+/**
+ * A factory to get {@link ITerminalConnector} instances.
+ *
+ * @author Michael Scharf
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ * @noinstantiate This class is not intended to be instantiated by clients.
+ *
+ *
+ * EXPERIMENTAL. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will work or
+ * that it will remain the same. Please do not use this API without consulting
+ * with the Target Management
+ * team.
+ *
+ */
+public class TerminalConnectorExtension {
+ static private ITerminalConnector makeConnector(final IConfigurationElement config) {
+ String id = config.getAttribute("id"); //$NON-NLS-1$
+ if (id == null || id.length() == 0)
+ id = config.getAttribute("class"); //$NON-NLS-1$
+ String name = config.getAttribute("name"); //$NON-NLS-1$
+ if (name == null || name.length() == 0) {
+ name = id;
+ }
+ String hidden = config.getAttribute("hidden"); //$NON-NLS-1$
+ boolean isHidden = hidden != null ? Boolean.parseBoolean(hidden) : false;
+ TerminalConnector.Factory factory = () -> (TerminalConnectorImpl) config.createExecutableExtension("class"); //$NON-NLS-1$
+ return new TerminalConnector(factory, id, name, isHidden);
+ }
+
+ /**
+ * Return a specific terminal connector for a given connector id. The
+ * terminal connector is not yet instantiated to any real connection.
+ *
+ * @param id the id of the terminal connector in the
+ * org.eclipse.tm.terminal.control.connectors
+ * extension point
+ * @return a new ITerminalConnector with id or null if there
+ * is no extension with that id.
+ * @since org.eclipse.tm.terminal 2.0
+ */
+ public static ITerminalConnector makeTerminalConnector(String id) {
+ IConfigurationElement[] config = RegistryFactory.getRegistry()
+ .getConfigurationElementsFor("org.eclipse.tm.terminal.control.connectors"); //$NON-NLS-1$
+ for (int i = 0; i < config.length; i++) {
+ if (id.equals(config[i].getAttribute("id"))) { //$NON-NLS-1$
+ return makeConnector(config[i]);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return a list of available terminal connectors (connection types).
+ *
+ * The terminal connectors returned are not yet instantiated to any real
+ * connection. Each terminal connector can connect to one remote system at a
+ * time.
+ *
+ * @return a new list of {@link ITerminalConnector} instances defined in the
+ * org.eclipse.tm.terminal.control.connectors
+ * extension point
+ * @since org.eclipse.tm.terminal 2.0 return value is ITerminalConnector[]
+ */
+ public static ITerminalConnector[] makeTerminalConnectors() {
+ IConfigurationElement[] config = RegistryFactory.getRegistry()
+ .getConfigurationElementsFor("org.eclipse.tm.terminal.control.connectors"); //$NON-NLS-1$
+ List result = new ArrayList<>();
+ for (int i = 0; i < config.length; i++) {
+ result.add(makeConnector(config[i]));
+ }
+ return result.toArray(new ITerminalConnector[result.size()]);
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/TerminalState.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/TerminalState.java
new file mode 100644
index 00000000000..2304d0a6be1
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/TerminalState.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - fixed copyright headers and beautified
+ * Michael Scharf (Wind River) - [262996] get rid of TerminalState.OPENED
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.provisional.api;
+
+/**
+ * Represent the sate of a terminal connection.
+ * In java 1.5 this would be an enum.
+ * @author Michael Scharf
+ *
+ *
+ * EXPERIMENTAL. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will
+ * work or that it will remain the same. Please do not use this API without
+ * consulting with the Target Management team.
+ *
+ */
+public class TerminalState {
+ /**
+ * The terminal is not connected.
+ */
+ public final static TerminalState CLOSED = new TerminalState("CLOSED"); //$NON-NLS-1$
+
+ /**
+ * The terminal is about to connect.
+ */
+ public final static TerminalState CONNECTING = new TerminalState("CONNECTING..."); //$NON-NLS-1$
+
+ /**
+ * The terminal is connected.
+ */
+ public final static TerminalState CONNECTED = new TerminalState("CONNECTED"); //$NON-NLS-1$
+
+ private final String fState;
+
+ public TerminalState(String state) {
+ fState = state;
+ }
+
+ @Override
+ public String toString() {
+ return fState;
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/provider/TerminalConnectorImpl.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/provider/TerminalConnectorImpl.java
new file mode 100644
index 00000000000..c19296bb725
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/provisional/api/provider/TerminalConnectorImpl.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - [225853][api] Provide more default functionality in TerminalConnectorImpl
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.provisional.api.provider;
+
+import java.io.OutputStream;
+import java.util.Optional;
+
+import org.eclipse.tm.internal.terminal.provisional.api.ISettingsStore;
+import org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl;
+import org.eclipse.tm.internal.terminal.provisional.api.Logger;
+import org.eclipse.tm.internal.terminal.provisional.api.TerminalState;
+
+/**
+ * Abstract base class for all terminal connector implementations to be
+ * registered via the org.eclipse.tm.terminal.control.connectors
+ * extension point.
+ *
+ * @since org.eclipse.tm.terminal 2.0
+ */
+public abstract class TerminalConnectorImpl {
+
+ /**
+ * The TerminalControl associated with this connector.
+ * Required for advertising state changes when needed.
+ */
+ protected ITerminalControl fControl;
+
+ /**
+ * Initialize this connector. This is called once after the constructor, in
+ * order to perform any required initializations such as loading required
+ * native libraries. Any work that may lead to runtime exceptions should be
+ * done in this method rather than in the constructor.
+ *
+ * @throws Exception when the connector fails to initialize (due to missing
+ * required libraries, for instance).
+ */
+ public void initialize() throws Exception {
+ }
+
+ /**
+ * Connect using the current state of the settings.
+ *
+ * This method is designed to be overridden by actual implementations, in
+ * order to open the streams required for communicating with the remote
+ * side. Extenders must call super.connect(control) as the
+ * first thing they are doing.
+ *
+ * @param control Used to inform the UI about state changes and messages
+ * from the connection.
+ */
+ public void connect(ITerminalControl control) {
+ Logger.log("entered."); //$NON-NLS-1$
+ fControl = control;
+ }
+
+ /**
+ * Disconnect if connected. Else do nothing. Has to set the state of the
+ * {@link ITerminalControl} when finished disconnecting.
+ */
+ public final void disconnect() {
+ Logger.log("entered."); //$NON-NLS-1$
+ doDisconnect();
+ fControl.setState(TerminalState.CLOSED);
+ }
+
+ /**
+ * Disconnect if connected. Else do nothing. Clients should override to
+ * perform any extra work needed for disconnecting.
+ */
+ protected void doDisconnect() {
+ // Do nothing by default
+ }
+
+ /**
+ * @return the terminal to remote stream (bytes written to this stream will
+ * be sent to the remote site). For the stream in the other direction (remote to
+ * terminal see {@link ITerminalControl#getRemoteToTerminalOutputStream()}
+ */
+ abstract public OutputStream getTerminalToRemoteStream();
+
+ /**
+ * @return A string that represents the settings of the connection. This representation
+ * may be shown in the status line of the terminal view.
+ */
+ abstract public String getSettingsSummary();
+
+ /**
+ * Test if local echo is needed. The default implementation returns
+ * false. Override to modify this behavior.
+ *
+ * @return true if a local echo is needed. TODO:Michael Scharf: this should
+ * be handed within the connection....
+ */
+ public boolean isLocalEcho() {
+ return false;
+ }
+
+ /**
+ * Set or reset the settings store to the default values.
+ */
+ public void setDefaultSettings() {
+ // do nothing by default
+ }
+
+ /**
+ * Load the state or settings of this connection. Is typically called before
+ * {@link #connect(ITerminalControl)}.
+ *
+ * Connectors that have nothing to configure do not need to implement this.
+ * Those terminals that do have configuration need to override this method
+ * to load settings.
+ *
+ * @param store a string based data store. Short keys like "foo" can be used
+ * to store the state of the connection.
+ */
+ public void load(ISettingsStore store) {
+ // do nothing by default
+ }
+
+ /**
+ * When the view or dialog containing the terminal is closed, the state of
+ * the connection is saved into the settings store store.
+ *
+ * Connectors that have no state or settings to persist do not need to
+ * override this. Others should override to persist their settings.
+ *
+ * @param store the store for persisting settings.
+ */
+ public void save(ISettingsStore store) {
+ // do nothing by default
+ }
+
+ /**
+ * Notify the remote site that the size of the terminal has changed.
+ *
+ * Concrete connectors should override this if they have the possibility to
+ * inform the remote about changed terminal size.
+ *
+ * @param newWidth the new width in characters.
+ * @param newHeight the new height in characters.
+ */
+ public void setTerminalSize(int newWidth, int newHeight) {
+ }
+
+ /**
+ * @since 5.2
+ */
+ public Optional getWorkingDirectory() {
+ return Optional.empty();
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/AbstractTextCanvasModel.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/AbstractTextCanvasModel.java
new file mode 100644
index 00000000000..0d0e151a1a8
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/AbstractTextCanvasModel.java
@@ -0,0 +1,511 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - [168197] Fix Terminal for CDC-1.1/Foundation-1.1
+ * Anton Leherbauer (Wind River) - [219589] Copy an entire line selection
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin;
+import org.eclipse.tm.internal.terminal.provisional.api.Logger;
+import org.eclipse.tm.terminal.model.ITerminalTextDataReadOnly;
+import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot;
+import org.eclipse.tm.terminal.model.TextRange;
+
+abstract public class AbstractTextCanvasModel implements ITextCanvasModel {
+ private static final boolean DEBUG_HOVER = TerminalPlugin.isOptionEnabled(Logger.TRACE_DEBUG_LOG_HOVER);
+ protected List fListeners = new ArrayList<>();
+ private int fCursorLine;
+ private int fCursorColumn;
+ private boolean fShowCursor;
+ private long fCursorTime;
+ private boolean fCursorIsEnabled;
+ private final ITerminalTextDataSnapshot fSnapshot;
+ private int fLines;
+
+ private int fSelectionStartLine = -1;
+ private int fSeletionEndLine;
+ private int fSelectionStartCoumn;
+ private int fSelectionEndColumn;
+ private ITerminalTextDataSnapshot fSelectionSnapshot;
+ private String fCurrentSelection = ""; //$NON-NLS-1$
+ private final Point fSelectionAnchor = new Point(0, 0);
+ /**
+ * do not update while update is running
+ */
+ boolean fInUpdate;
+ private int fCols;
+
+ private TextRange fHoverRange = TextRange.EMPTY;
+
+ public AbstractTextCanvasModel(ITerminalTextDataSnapshot snapshot) {
+ fSnapshot = snapshot;
+ fLines = fSnapshot.getHeight();
+ }
+
+ @Override
+ public void addCellCanvasModelListener(ITextCanvasModelListener listener) {
+ fListeners.add(listener);
+ }
+
+ @Override
+ public void removeCellCanvasModelListener(ITextCanvasModelListener listener) {
+ fListeners.remove(listener);
+ }
+
+ protected void fireCellRangeChanged(int x, int y, int width, int height) {
+ for (Iterator iter = fListeners.iterator(); iter.hasNext();) {
+ ITextCanvasModelListener listener = iter.next();
+ listener.rangeChanged(x, y, width, height);
+ }
+ }
+
+ protected void fireDimensionsChanged(int width, int height) {
+ for (Iterator iter = fListeners.iterator(); iter.hasNext();) {
+ ITextCanvasModelListener listener = iter.next();
+ listener.dimensionsChanged(width, height);
+ }
+
+ }
+
+ protected void fireTerminalDataChanged() {
+ for (Iterator iter = fListeners.iterator(); iter.hasNext();) {
+ ITextCanvasModelListener listener = iter.next();
+ listener.terminalDataChanged();
+ }
+
+ }
+
+ @Override
+ public ITerminalTextDataReadOnly getTerminalText() {
+ return fSnapshot;
+ }
+
+ protected ITerminalTextDataSnapshot getSnapshot() {
+ return fSnapshot;
+ }
+
+ protected void updateSnapshot() {
+ if (!fInUpdate && fSnapshot.isOutOfDate()) {
+ fInUpdate = true;
+ try {
+ fSnapshot.updateSnapshot(false);
+ if (fSnapshot.hasTerminalChanged())
+ fireTerminalDataChanged();
+ // TODO why does hasDimensionsChanged not work??????
+ // if(fSnapshot.hasDimensionsChanged())
+ // fireDimensionsChanged();
+ if (fLines != fSnapshot.getHeight() || fCols != fSnapshot.getWidth()) {
+ fireDimensionsChanged(fSnapshot.getWidth(), fSnapshot.getHeight());
+ fLines = fSnapshot.getHeight();
+ fCols = fSnapshot.getWidth();
+ }
+ int y = fSnapshot.getFirstChangedLine();
+ // has any line changed?
+ if (y < Integer.MAX_VALUE) {
+ int height = fSnapshot.getLastChangedLine() - y + 1;
+ fireCellRangeChanged(0, y, fSnapshot.getWidth(), height);
+ }
+
+ } finally {
+ fInUpdate = false;
+ }
+ }
+ }
+
+ /**
+ * must be called from the UI thread
+ */
+ public void update() {
+ // do the poll....
+ updateSnapshot();
+ updateSelection();
+ updateCursor();
+ }
+
+ @Override
+ public int getCursorColumn() {
+ return fCursorColumn;
+ }
+
+ @Override
+ public int getCursorLine() {
+ return fCursorLine;
+ }
+
+ @Override
+ public boolean isCursorOn() {
+ return fShowCursor && fCursorIsEnabled;
+ }
+
+ /**
+ * should be called regularly to draw an update of the
+ * blinking cursor
+ */
+ protected void updateCursor() {
+ if (!fCursorIsEnabled)
+ return;
+ int cursorLine = getSnapshot().getCursorLine();
+ int cursorColumn = getSnapshot().getCursorColumn();
+ // if cursor at the end put it to the end of the
+ // last line...
+ if (cursorLine >= getSnapshot().getHeight()) {
+ cursorLine = getSnapshot().getHeight() - 1;
+ cursorColumn = getSnapshot().getWidth() - 1;
+ }
+ // has the cursor moved?
+ if (fCursorLine != cursorLine || fCursorColumn != cursorColumn) {
+ // hide the old cursor!
+ fShowCursor = false;
+ // clean the previous cursor
+ // bug 206363: paint also the char to the left and right of the cursor - see also below
+ int col = fCursorColumn;
+ int width = 2;
+ if (col > 0) {
+ col--;
+ width++;
+ }
+ fireCellRangeChanged(col, fCursorLine, width, 1);
+ // the cursor is shown when it moves!
+ fShowCursor = true;
+ fCursorTime = System.currentTimeMillis();
+ fCursorLine = cursorLine;
+ fCursorColumn = cursorColumn;
+ // and draw the new cursor
+ fireCellRangeChanged(fCursorColumn, fCursorLine, 1, 1);
+ } else {
+ long t = System.currentTimeMillis();
+ // TODO make the cursor blink time customisable
+ if (t - fCursorTime > 500) {
+ fShowCursor = !fShowCursor;
+ fCursorTime = t;
+ // on some windows machines, there is some left
+ // over when updating the cursor .
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=206363
+ int col = fCursorColumn;
+ int width = 2;
+ if (col > 0) {
+ col--;
+ width++;
+ }
+ fireCellRangeChanged(col, fCursorLine, width, 1);
+ }
+ }
+ }
+
+ @Override
+ public void setVisibleRectangle(int startLine, int startCol, int height, int width) {
+ fSnapshot.setInterestWindow(Math.max(0, startLine), Math.max(1, height));
+ update();
+ }
+
+ protected void showCursor(boolean show) {
+ fShowCursor = true;
+ }
+
+ @Override
+ public void setCursorEnabled(boolean visible) {
+ fCursorTime = System.currentTimeMillis();
+ fShowCursor = visible;
+ fCursorIsEnabled = visible;
+ fireCellRangeChanged(fCursorColumn, fCursorLine, 1, 1);
+ }
+
+ @Override
+ public boolean isCursorEnabled() {
+ return fCursorIsEnabled;
+ }
+
+ @Override
+ public Point getSelectionEnd() {
+ if (fSelectionStartLine < 0)
+ return null;
+ else
+ return new Point(fSelectionEndColumn, fSeletionEndLine);
+ }
+
+ @Override
+ public Point getSelectionStart() {
+ if (fSelectionStartLine < 0)
+ return null;
+ else
+ return new Point(fSelectionStartCoumn, fSelectionStartLine);
+ }
+
+ @Override
+ public Point getSelectionAnchor() {
+ if (fSelectionStartLine < 0)
+ return null;
+ return new Point(fSelectionAnchor.x, fSelectionAnchor.y);
+ }
+
+ @Override
+ public void setSelectionAnchor(Point anchor) {
+ fSelectionAnchor.x = anchor.x;
+ fSelectionAnchor.y = anchor.y;
+ }
+
+ @Override
+ public void setSelection(int startLine, int endLine, int startColumn, int endColumn) {
+ // System.err.println(startLine+","+endLine+","+startColumn+","+endColumn);
+ doSetSelection(startLine, endLine, startColumn, endColumn);
+ fCurrentSelection = extractSelectedText();
+ }
+
+ private void doSetSelection(int startLine, int endLine, int startColumn, int endColumn) {
+ assert (startLine < 0 || startLine <= endLine);
+ if (startLine >= 0) {
+ if (fSelectionSnapshot == null) {
+ fSelectionSnapshot = fSnapshot.getTerminalTextData().makeSnapshot();
+ fSelectionSnapshot.updateSnapshot(true);
+ }
+ } else if (fSelectionSnapshot != null) {
+ fSelectionSnapshot.detach();
+ fSelectionSnapshot = null;
+ }
+ int oldStart = fSelectionStartLine;
+ int oldEnd = fSeletionEndLine;
+ fSelectionStartLine = startLine;
+ fSeletionEndLine = endLine;
+ fSelectionStartCoumn = startColumn;
+ fSelectionEndColumn = endColumn;
+ if (fSelectionSnapshot != null) {
+ fSelectionSnapshot.setInterestWindow(0, fSelectionSnapshot.getHeight());
+ }
+ int changedStart;
+ int changedEnd;
+ if (oldStart < 0) {
+ changedStart = fSelectionStartLine;
+ changedEnd = fSeletionEndLine;
+ } else if (fSelectionStartLine < 0) {
+ changedStart = oldStart;
+ changedEnd = oldEnd;
+ } else {
+ changedStart = Math.min(oldStart, fSelectionStartLine);
+ changedEnd = Math.max(oldEnd, fSeletionEndLine);
+ }
+ if (changedStart >= 0) {
+ fireCellRangeChanged(0, changedStart, fSnapshot.getWidth(), changedEnd - changedStart + 1);
+ }
+ }
+
+ @Override
+ public boolean hasLineSelection(int line) {
+ if (fSelectionStartLine < 0)
+ return false;
+ else
+ return line >= fSelectionStartLine && line <= fSeletionEndLine;
+ }
+
+ @Override
+ public String getSelectedText() {
+ return fCurrentSelection;
+ }
+
+ @Override
+ public boolean hasHoverSelection(int line) {
+ if (fHoverRange.isEmpty()) {
+ return false;
+ }
+ return fHoverRange.contains(line);
+ }
+
+ @Override
+ public Point getHoverSelectionStart() {
+ if (!fHoverRange.isEmpty()) {
+ return fHoverRange.getStart();
+ }
+ return null;
+ }
+
+ @Override
+ public Point getHoverSelectionEnd() {
+ // Note - to match behaviour of getSelectionEnd this method
+ // returns the inclusive end. As the fHoverRange is exclusive
+ // we need to decrement the end positions before returning them.
+ if (!fHoverRange.isEmpty()) {
+ Point end = fHoverRange.getEnd();
+ end.x--;
+ end.y--;
+ return end;
+ }
+ return null;
+ }
+
+ @Override
+ public void expandHoverSelectionAt(final int line, final int col) {
+ if (fHoverRange.contains(col, line)) {
+ // position is inside current hover range -> no change
+ return;
+ }
+ fHoverRange = TextRange.EMPTY;
+ if (line < 0 || line > fSnapshot.getHeight() || col < 0) {
+ return;
+ }
+ int row1 = line;
+ int row2 = line;
+ while (row1 > 0 && fSnapshot.isWrappedLine(row1 - 1))
+ row1--;
+ while (row2 < fSnapshot.getHeight() && fSnapshot.isWrappedLine(row2))
+ row2++;
+ row2++;
+ String lineText = ""; //$NON-NLS-1$
+ for (int l = row1; l < row2; l++) {
+ char[] chars = fSnapshot.getChars(l);
+ if (chars == null)
+ return;
+ lineText += String.valueOf(chars);
+ }
+ int width = fSnapshot.getWidth();
+ int col1 = col + (line - row1) * width;
+ if (lineText.length() <= col1 || isBoundaryChar(lineText.charAt(col1))) {
+ return;
+ }
+ int wordStart = 0;
+ int wordEnd = lineText.length();
+ for (int c = col1; c >= 1; c--) {
+ if (isBoundaryChar(lineText.charAt(c - 1))) {
+ wordStart = c;
+ break;
+ }
+ }
+ for (int c = col1; c < lineText.length(); c++) {
+ if (isBoundaryChar(lineText.charAt(c))) {
+ wordEnd = c;
+ break;
+ }
+ }
+ if (wordStart < wordEnd) {
+ fHoverRange = new TextRange(row1 + wordStart / width, row1 + (wordEnd - 1) / width + 1, (wordStart % width),
+ (wordEnd - 1) % width + 1, lineText.substring(wordStart, wordEnd));
+ if (DEBUG_HOVER) {
+ System.out.format("hover: %s <- [%s,%s][%s,%s]\n", //$NON-NLS-1$
+ fHoverRange, col, line, wordStart, wordEnd);
+ }
+ }
+ }
+
+ @Override
+ public String getHoverSelectionText() {
+ return fHoverRange.text;
+ }
+
+ private boolean isBoundaryChar(char c) {
+ return Character.isWhitespace(c) || (c < '\u0020') || c == '"' || c == '\'';
+ }
+
+ // helper to sanitize text copied out of a snapshot
+ private static String scrubLine(String text) {
+ // get rid of the empty space at the end of the lines
+ // text=text.replaceAll("\000+$",""); //$NON-NLS-1$//$NON-NLS-2$
+ //
+ int i = text.length() - 1;
+ while (i >= 0 && text.charAt(i) == '\000') {
+ i--;
+ }
+ text = text.substring(0, i + 1);
+ //
+ // null means space
+ return text.replace('\000', ' ');
+ }
+
+ /**
+ * Calculates the currently selected text
+ * @return the currently selected text
+ */
+ private String extractSelectedText() {
+ if (fSelectionStartLine < 0 || fSelectionStartCoumn < 0 || fSelectionSnapshot == null)
+ return ""; //$NON-NLS-1$
+ StringBuffer buffer = new StringBuffer();
+ for (int line = fSelectionStartLine; line <= fSeletionEndLine; line++) {
+ String text;
+ char[] chars = fSelectionSnapshot.getChars(line);
+ if (chars != null) {
+ text = new String(chars);
+ if (line == fSeletionEndLine && fSelectionEndColumn >= 0)
+ text = text.substring(0, Math.min(fSelectionEndColumn + 1, text.length()));
+ if (line == fSelectionStartLine)
+ text = text.substring(Math.min(fSelectionStartCoumn, text.length()));
+ text = scrubLine(text);
+ } else {
+ text = ""; //$NON-NLS-1$
+ }
+ buffer.append(text);
+ if (line < fSeletionEndLine && !fSelectionSnapshot.isWrappedLine(line))
+ buffer.append('\n');
+ }
+ return buffer.toString();
+ }
+
+ private void updateSelection() {
+ if (fSelectionSnapshot != null && fSelectionSnapshot.isOutOfDate()) {
+ fSelectionSnapshot.updateSnapshot(true);
+ // has the selection moved?
+ if (fSelectionSnapshot != null && fSelectionStartLine >= 0
+ && fSelectionSnapshot.getScrollWindowSize() > 0) {
+ int start = fSelectionStartLine + fSelectionSnapshot.getScrollWindowShift();
+ int end = fSeletionEndLine + fSelectionSnapshot.getScrollWindowShift();
+ if (start < 0)
+ if (end >= 0)
+ start = 0;
+ else
+ start = -1;
+ doSetSelection(start, end, fSelectionStartCoumn, fSelectionEndColumn);
+ }
+ // check if the content of the selection has changed. If the content has
+ // changed, clear the selection
+ if (fCurrentSelection.length() > 0 && fSelectionSnapshot != null
+ && fSelectionSnapshot.getFirstChangedLine() <= fSeletionEndLine
+ && fSelectionSnapshot.getLastChangedLine() >= fSelectionStartLine) {
+ // has the selected text changed?
+ if (!fCurrentSelection.equals(extractSelectedText())) {
+ setSelection(-1, -1, -1, -1);
+ }
+ }
+ // update the observed window...
+ if (fSelectionSnapshot != null)
+ // todo make -1 to work!
+ fSelectionSnapshot.setInterestWindow(0, fSelectionSnapshot.getHeight());
+ }
+ }
+
+ @Override
+ public String getAllText() {
+
+ // Make a snapshot of the whole text data
+ ITerminalTextDataSnapshot snapshot = fSnapshot.getTerminalTextData().makeSnapshot();
+ snapshot.updateSnapshot(true);
+ snapshot.detach();
+
+ // Extract the data
+ StringBuffer sb = new StringBuffer();
+ for (int line = 0; line < snapshot.getHeight(); line++) {
+ char[] chars = snapshot.getChars(line);
+ String text;
+ if (chars != null) {
+ text = scrubLine(new String(chars)); // take care of NULs
+ } else {
+ text = ""; //$NON-NLS-1$ null arrays represent empty lines
+ }
+ sb.append(text);
+ // terminate lines except (1) the last one and (2) wrapped lines
+ if ((line < snapshot.getHeight() - 1) && !snapshot.isWrappedLine(line)) {
+ sb.append('\n');
+ }
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/GridCanvas.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/GridCanvas.java
new file mode 100644
index 00000000000..c727d9910f4
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/GridCanvas.java
@@ -0,0 +1,233 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [294468] Fix scroller and text line rendering
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.ScrollBar;
+
+/**
+ * A Grid based Canvas. The canvas has rows and columns.
+ * CellPainting is done with the abstract method drawCell
+ */
+abstract public class GridCanvas extends VirtualCanvas {
+ /** width of a cell */
+ private int fCellWidth;
+ /** height of a cell */
+ private int fCellHeight;
+
+ public GridCanvas(Composite parent, int style) {
+ super(parent, style);
+ addListener(SWT.MouseWheel, event -> {
+ if (getVerticalBar().isVisible()) {
+ int delta = -fCellHeight * event.count;
+ scrollYDelta(delta);
+ }
+ event.doit = false;
+ });
+
+ }
+
+ /** template method paint.
+ * iterates over all cells in the clipping rectangle and paints them.
+ */
+ @Override
+ protected void paint(GC gc) {
+ Rectangle clipping = gc.getClipping();
+ if (clipping.width == 0 || clipping.height == 0)
+ return;
+ Rectangle clientArea = getScreenRectInVirtualSpace();
+ // Beginning coordinates
+ int xOffset = clientArea.x;
+ int yOffset = clientArea.y;
+ int colFirst = virtualXToCell(xOffset + clipping.x);
+ if (colFirst > getCols())
+ colFirst = getCols();
+ else if (colFirst < 0) {
+ colFirst = 0;
+ }
+ int rowFirst = virtualYToCell(yOffset + clipping.y);
+ // End coordinates
+ int colLast = virtualXToCell(xOffset + clipping.x + clipping.width + fCellWidth);
+ if (colLast > getCols())
+ colLast = getCols();
+ int rowLast = virtualYToCell(yOffset + clipping.y + clipping.height + fCellHeight);
+ if (rowLast > getRows())
+ rowLast = getRows();
+ // System.out.println(rowFirst+"->"+rowLast+" "+System.currentTimeMillis());
+ // draw the cells
+ for (int row = rowFirst; row <= rowLast; row++) {
+ int cx = colFirst * fCellWidth - xOffset;
+ int cy = row * fCellHeight - yOffset;
+ drawLine(gc, row, cx, cy, colFirst, colLast);
+ }
+ paintUnoccupiedSpace(gc, clipping);
+ }
+
+ /**
+ * @param gc
+ * @param row the line to draw
+ * @param x coordinate on screen
+ * @param y coordinate on screen
+ * @param colFirst first column to draw
+ * @param colLast last column to draw
+ */
+ abstract void drawLine(GC gc, int row, int x, int y, int colFirst, int colLast);
+
+ abstract protected int getRows();
+
+ abstract protected int getCols();
+
+ protected void setCellWidth(int cellWidth) {
+ fCellWidth = cellWidth;
+ getHorizontalBar().setIncrement(fCellWidth);
+ }
+
+ public int getCellWidth() {
+ return fCellWidth;
+ }
+
+ protected void setCellHeight(int cellHeight) {
+ fCellHeight = cellHeight;
+ getVerticalBar().setIncrement(fCellHeight);
+ }
+
+ public int getCellHeight() {
+ return fCellHeight;
+ }
+
+ int virtualXToCell(int x) {
+ return x / fCellWidth;
+ }
+
+ int virtualYToCell(int y) {
+ return y / fCellHeight;
+ }
+
+ protected Point screenPointToCell(int x, int y) {
+ x = screenXtoVirtual(x) / fCellWidth;
+ y = screenYtoVirtual(y) / fCellHeight;
+ return new Point(x, y);
+ }
+
+ Point screenPointToCell(Point point) {
+ return screenPointToCell(point.x, point.y);
+ }
+
+ protected Point cellToOriginOnScreen(int x, int y) {
+ x = virtualXtoScreen(fCellWidth * x);
+ y = virtualYtoScreen(fCellHeight * y);
+ return new Point(x, y);
+ }
+
+ Point cellToOriginOnScreen(Point cell) {
+ return cellToOriginOnScreen(cell.x, cell.y);
+ }
+
+ Rectangle getCellScreenRect(Point cell) {
+ return getCellScreenRect(cell.x, cell.y);
+ }
+
+ Rectangle getCellScreenRect(int x, int y) {
+ x = fCellWidth * virtualXtoScreen(x);
+ y = fCellHeight * virtualYtoScreen(y);
+ return new Rectangle(x, y, fCellWidth, fCellHeight);
+ }
+
+ protected Rectangle getCellVirtualRect(Point cell) {
+ return getCellVirtualRect(cell.x, cell.y);
+ }
+
+ Rectangle getCellVirtualRect(int x, int y) {
+ x = fCellWidth * x;
+ y = fCellHeight * y;
+ return new Rectangle(x, y, fCellWidth, fCellHeight);
+ }
+
+ @Override
+ protected void viewRectangleChanged(int x, int y, int width, int height) {
+ int cellX = virtualXToCell(x);
+ int cellY = virtualYToCell(y);
+ // End coordinates
+ int xE = virtualXToCell(x + width);
+ // if(xE>getCols())
+ // xE=getCols();
+ int yE = virtualYToCell(y + height);
+ // if(yE>getRows())
+ // yE=getRows();
+ visibleCellRectangleChanged(cellX, cellY, xE - cellX, yE - cellY);
+ }
+
+ /**
+ * Called when the viewed part has changed.
+ * Override when you need this information....
+ * Is only called if the values change (well, almost)
+ * @param x origin of visible cells
+ * @param y origin of visible cells
+ * @param width number of cells visible in x direction
+ * @param height number of cells visible in y direction
+ */
+ protected void visibleCellRectangleChanged(int x, int y, int width, int height) {
+ }
+
+ @Override
+ protected void setVirtualExtend(int width, int height) {
+ int cellHeight = getCellHeight();
+ if (cellHeight > 0) {
+ height -= height % cellHeight;
+ }
+ super.setVirtualExtend(width, height);
+ }
+
+ @Override
+ protected void setVirtualOrigin(int x, int y) {
+ int cellHeight = getCellHeight();
+ if (cellHeight > 0) {
+ int remainder = y % cellHeight;
+ if (remainder < 0) {
+ y -= (cellHeight + remainder);
+ } else {
+ y -= remainder;
+ }
+ }
+ super.setVirtualOrigin(x, y);
+ }
+
+ @Override
+ protected void scrollY(ScrollBar vBar) {
+ int vSelection = vBar.getSelection();
+ Rectangle bounds = getVirtualBounds();
+ int y = -vSelection;
+ int cellHeight = getCellHeight();
+ if (cellHeight > 0) {
+ int remainder = y % cellHeight;
+ if (remainder < 0) {
+ y -= (cellHeight + remainder);
+ } else {
+ y -= remainder;
+ }
+ }
+ int deltaY = y - bounds.y;
+ if (deltaY != 0) {
+ scrollSmart(0, deltaY);
+ setVirtualOrigin(bounds.x, bounds.y += deltaY);
+ }
+ if (-bounds.y + getRows() * getCellHeight() >= bounds.height) {
+ // scrolled to bottom - need to redraw bottom area
+ Rectangle clientRect = getClientArea();
+ redraw(0, clientRect.height - fCellHeight, clientRect.width, fCellHeight, false);
+ }
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/ILinelRenderer.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/ILinelRenderer.java
new file mode 100644
index 00000000000..a02b67fcfa9
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/ILinelRenderer.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [294468] Fix scroller and text line rendering
+ * Martin Oberhuber (Wind River) - [265352][api] Allow setting fonts programmatically
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+import java.util.Map;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.tm.terminal.model.TerminalColor;
+
+/**
+ *
+ */
+public interface ILinelRenderer {
+ int getCellWidth();
+
+ int getCellHeight();
+
+ void drawLine(ITextCanvasModel model, GC gc, int line, int x, int y, int colFirst, int colLast);
+
+ /**
+ * Update for a font change from the global JFace Registry.
+ * @deprecated Use {@link #updateFont(String)}
+ */
+ @Deprecated
+ void onFontChange();
+
+ /**
+ * Set a new font
+ * @param fontName Jface name of the new font
+ * @since 3.2
+ */
+ void updateFont(String fontName);
+
+ void updateColors(Map map);
+
+ void setInvertedColors(boolean invert);
+
+ boolean isInvertedColors();
+
+ /**
+ * @deprecated use {@link #getDefaultBackgroundColor(Device)}
+ */
+ @Deprecated
+ Color getDefaultBackgroundColor();
+
+ Color getDefaultBackgroundColor(Device device);
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModel.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModel.java
new file mode 100644
index 00000000000..01beac0789d
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModel.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.tm.terminal.model.ITerminalTextDataReadOnly;
+
+public interface ITextCanvasModel {
+ void addCellCanvasModelListener(ITextCanvasModelListener listener);
+
+ void removeCellCanvasModelListener(ITextCanvasModelListener listener);
+
+ ITerminalTextDataReadOnly getTerminalText();
+
+ /**
+ * This is is
+ * @param startLine
+ * @param startCol
+ * @param height
+ * @param width
+ */
+ void setVisibleRectangle(int startLine, int startCol, int height, int width);
+
+ /**
+ * @return true when the cursor is shown (used for blinking cursors)
+ */
+ boolean isCursorOn();
+
+ /**
+ * Show/Hide the cursor.
+ * @param visible
+ */
+ void setCursorEnabled(boolean visible);
+
+ /**
+ * @return true if the cursor is shown.
+ */
+ boolean isCursorEnabled();
+
+ /**
+ * @return the line of the cursor
+ */
+ int getCursorLine();
+
+ /**
+ * @return the column of the cursor
+ */
+ int getCursorColumn();
+
+ /**
+ * @return the start of the selection or null if nothing is selected
+ * {@link Point#x} is the column and {@link Point#y} is the line.
+ */
+ Point getSelectionStart();
+
+ /**
+ * @return the end of the selection or null if nothing is selected
+ * {@link Point#x} is the column and {@link Point#y} is the line.
+ */
+ Point getSelectionEnd();
+
+ Point getSelectionAnchor();
+
+ void setSelectionAnchor(Point anchor);
+
+ /**
+ * Sets the selection. A negative startLine clears the selection.
+ * @param startLine
+ * @param endLine
+ * @param startColumn
+ * @param endColumn
+ */
+ void setSelection(int startLine, int endLine, int startColumn, int endColumn);
+
+ /**
+ * @param line
+ * @return true if line is part of the selection
+ */
+ boolean hasLineSelection(int line);
+
+ String getSelectedText();
+
+ /**
+ * Expand the hover selection to the word at the given position.
+ *
+ * @param line line
+ * @param col column
+ */
+ void expandHoverSelectionAt(int line, int col);
+
+ /**
+ * @param line
+ * @return true if line is part of the hover selection
+ */
+ boolean hasHoverSelection(int line);
+
+ /**
+ * Get the text of the current hover selection.
+ *
+ * @return the hover selection text, never null.
+ */
+ String getHoverSelectionText();
+
+ /**
+ * Get the start of the hover selection.
+ *
+ * @return the start of the hover selection or null if nothing is selected
+ * {@link Point#x} is the column and {@link Point#y} is the line.
+ * Returns non-null if {@link #hasHoverSelection(int)} returns true
+ */
+ Point getHoverSelectionStart();
+
+ /**
+ * Get the end of the hover selection (inclusive).
+ *
+ * @return the end of the hover selection or null if nothing is selected
+ * {@link Point#x} is the column and {@link Point#y} is the line.
+ * Returns non-null if {@link #hasHoverSelection(int)} returns true
+ */
+ Point getHoverSelectionEnd();
+
+ /**
+ * Collect and return all text present in the model.
+ *
+ * Individual lines of the returned text are separated by '\n'.
+ *
+ *
The method is primarily designed for test automation.
+ *
+ * @since 4.4
+ */
+ String getAllText();
+
+}
\ No newline at end of file
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModelListener.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModelListener.java
new file mode 100644
index 00000000000..4d6251844ca
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModelListener.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+/**
+ */
+public interface ITextCanvasModelListener {
+ void rangeChanged(int col, int line, int width, int height);
+
+ void dimensionsChanged(int cols, int rows);
+
+ /**
+ * Called when any text change happened. Used to scroll to the
+ * end of text in auto scroll mode. This does not get fired
+ * when the window of interest has changed!
+ */
+ void terminalDataChanged();
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/PipedInputStream.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/PipedInputStream.java
new file mode 100644
index 00000000000..29c00356cbf
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/PipedInputStream.java
@@ -0,0 +1,335 @@
+/*******************************************************************************
+ * Copyright (c) 1996, 2011 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Douglas Lea (Addison Wesley) - [cq:1552] BoundedBufferWithStateTracking adapted to BoundedByteBuffer
+ * Martin Oberhuber (Wind River) - the waitForAvailable method
+ * Martin Oberhuber (Wind River) - [208166] Avoid unnecessary arraycopy in BoundedByteBuffer
+ * Pawel Piech (Wind River) - [333613] "Job found still running" after shutdown
+ *******************************************************************************/
+
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * The main purpose of this class is to start a runnable in the
+ * display thread when data is available and to pretend no data
+ * is available after a given amount of time the runnable is running.
+ *
+ */
+public class PipedInputStream extends InputStream {
+ /**
+ * The output stream used by the terminal backend to write to the terminal
+ */
+ protected final OutputStream fOutputStream;
+ /**
+ * A blocking byte queue.
+ */
+ private final BoundedByteBuffer fQueue;
+
+ /**
+ * A byte bounded buffer used to synchronize the input and the output stream.
+ *
+ * Adapted from BoundedBufferWithStateTracking
+ * http://gee.cs.oswego.edu/dl/cpj/allcode.java
+ * http://gee.cs.oswego.edu/dl/cpj/
+ *
+ * BoundedBufferWithStateTracking is part of the examples for the book
+ * Concurrent Programming in Java: Design Principles and Patterns by
+ * Doug Lea (ISBN 0-201-31009-0). Second edition published by
+ * Addison-Wesley, November 1999. The code is
+ * Copyright(c) Douglas Lea 1996, 1999 and released to the public domain
+ * and may be used for any purposes whatsoever.
+ *
+ * For some reasons a solution based on
+ * PipedOutputStream/PipedIntputStream
+ * does work *very* slowly:
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4404700
+ *
+ *
+ */
+ private class BoundedByteBuffer {
+ protected final byte[] fBuffer; // the elements
+ protected int fPutPos = 0; // circular indices
+ protected int fTakePos = 0;
+ protected int fUsedSlots = 0; // the count
+ private boolean fClosed;
+
+ public BoundedByteBuffer(int capacity) throws IllegalArgumentException {
+ // make sure we don't deadlock on too small capacity
+ if (capacity <= 0)
+ throw new IllegalArgumentException();
+ fBuffer = new byte[capacity];
+ }
+
+ /**
+ * @return the bytes available for {@link #read()}
+ * Must be called with a lock on this!
+ */
+ public int available() {
+ return fUsedSlots;
+ }
+
+ /**
+ * Writes a single byte to the buffer. Blocks if the buffer is full.
+ * @param b byte to write to the buffer
+ * @throws InterruptedException when the thread is interrupted while waiting
+ * for the buffer to become ready
+ * Must be called with a lock on this!
+ */
+ public void write(byte b) throws InterruptedException {
+ while (fUsedSlots == fBuffer.length)
+ // wait until not full
+ wait();
+
+ fBuffer[fPutPos] = b;
+ fPutPos = (fPutPos + 1) % fBuffer.length; // cyclically increment
+
+ if (fUsedSlots++ == 0) // signal if was empty
+ notifyAll();
+ }
+
+ public int getFreeSlots() {
+ return fBuffer.length - fUsedSlots;
+ }
+
+ public void write(byte[] b, int off, int len) throws InterruptedException {
+ assert len <= getFreeSlots();
+ while (fUsedSlots == fBuffer.length)
+ // wait until not full
+ wait();
+ int n = Math.min(len, fBuffer.length - fPutPos);
+ System.arraycopy(b, off, fBuffer, fPutPos, n);
+ if (fPutPos + len > fBuffer.length)
+ System.arraycopy(b, off + n, fBuffer, 0, len - n);
+ fPutPos = (fPutPos + len) % fBuffer.length; // cyclically increment
+ boolean wasEmpty = fUsedSlots == 0;
+ fUsedSlots += len;
+ if (wasEmpty) // signal if was empty
+ notifyAll();
+ }
+
+ /**
+ * Read a single byte. Blocks until a byte is available.
+ * @return a byte from the buffer
+ * @throws InterruptedException when the thread is interrupted while waiting
+ * for the buffer to become ready
+ * Must be called with a lock on this!
+ */
+ public int read() throws InterruptedException {
+ while (fUsedSlots == 0) {
+ if (fClosed)
+ return -1;
+ // wait until not empty
+ wait();
+ }
+ byte b = fBuffer[fTakePos];
+ fTakePos = (fTakePos + 1) % fBuffer.length;
+
+ if (fUsedSlots-- == fBuffer.length) // signal if was full
+ notifyAll();
+ return b;
+ }
+
+ public int read(byte[] cbuf, int off, int len) throws InterruptedException {
+ assert len <= available();
+ while (fUsedSlots == 0) {
+ if (fClosed)
+ return 0;
+ // wait until not empty
+ wait();
+ }
+ int n = Math.min(len, fBuffer.length - fTakePos);
+ System.arraycopy(fBuffer, fTakePos, cbuf, off, n);
+ if (fTakePos + len > n)
+ System.arraycopy(fBuffer, 0, cbuf, off + n, len - n);
+ fTakePos = (fTakePos + len) % fBuffer.length;
+ boolean wasFull = fUsedSlots == fBuffer.length;
+ fUsedSlots -= len;
+ if (wasFull)
+ notifyAll();
+
+ return len;
+ }
+
+ public void close() {
+ fClosed = true;
+ notifyAll();
+ }
+
+ public boolean isClosed() {
+ return fClosed;
+ }
+ }
+
+ /**
+ * An output stream that calls {@link PipedInputStream#textAvailable}
+ * every time data is written to the stream. The data is written to
+ * {@link PipedInputStream#fQueue}.
+ *
+ */
+ class PipedOutputStream extends OutputStream {
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ try {
+ synchronized (fQueue) {
+ if (fQueue.isClosed())
+ throw new IOException("Stream is closed!"); //$NON-NLS-1$
+ int written = 0;
+ while (written < len) {
+ if (fQueue.getFreeSlots() == 0) {
+ // if no slots available, write one byte and block
+ // until free slots are available
+ fQueue.write(b[off + written]);
+ written++;
+ } else {
+ // if slots are available, write as much as
+ // we can in one junk
+ int n = Math.min(fQueue.getFreeSlots(), len - written);
+ fQueue.write(b, off + written, n);
+ written += n;
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ try {
+ synchronized (fQueue) {
+ if (fQueue.isClosed())
+ throw new IOException("Stream is closed!"); //$NON-NLS-1$
+ fQueue.write((byte) b);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ synchronized (fQueue) {
+ fQueue.close();
+ }
+ }
+ }
+
+ /**
+ * @param bufferSize the size of the buffer of the output stream
+ */
+ public PipedInputStream(int bufferSize) {
+ fOutputStream = new PipedOutputStream();
+ fQueue = new BoundedByteBuffer(bufferSize);
+ }
+
+ /**
+ * @return the output stream used by the backend to write to the terminal.
+ */
+ public OutputStream getOutputStream() {
+ return fOutputStream;
+ }
+
+ /**
+ * Waits until data is available for reading.
+ * @param millis see {@link Object#wait(long)}
+ * @throws InterruptedException when the thread is interrupted while waiting
+ * for the buffer to become ready
+ */
+ public void waitForAvailable(long millis) throws InterruptedException {
+ synchronized (fQueue) {
+ if (fQueue.available() == 0 && !fQueue.fClosed)
+ fQueue.wait(millis);
+ }
+ }
+
+ /**
+ * Must be called in the Display Thread!
+ * @return number of characters available for reading.
+ */
+ @Override
+ public int available() {
+ synchronized (fQueue) {
+ return fQueue.available();
+ }
+ }
+
+ /**
+ * @return the next available byte. Check with {@link #available}
+ * if characters are available.
+ */
+ @Override
+ public int read() throws IOException {
+ try {
+ synchronized (fQueue) {
+ return fQueue.read();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return -1;
+ }
+ }
+
+ /**
+ * Closing a PipedInputStream is the same as closing the output stream.
+ * The stream will allow reading data that's still in the pipe after which it will
+ * throw an IOException.
+ */
+ @Override
+ public void close() throws IOException {
+ synchronized (fQueue) {
+ fQueue.close();
+ }
+ }
+
+ @Override
+ public int read(byte[] cbuf, int off, int len) throws IOException {
+ int n = 0;
+ if (len == 0)
+ return 0;
+ // read as much as we can using a single synchronized statement
+ try {
+ synchronized (fQueue) {
+ // if nothing available, block and read one byte
+ if (fQueue.available() == 0) {
+ // block now until at least one byte is available
+ int c = fQueue.read();
+ // are we at the end of stream
+ if (c == -1)
+ return -1;
+ cbuf[off] = (byte) c;
+ n++;
+ }
+ // is there more data available?
+ if (n < len && fQueue.available() > 0) {
+ // read at most available()
+ int nn = Math.min(fQueue.available(), len - n);
+ // are we at the end of the stream?
+ if (nn == 0 && fQueue.isClosed()) {
+ // if no byte was read, return -1 to indicate end of stream
+ // else return the bytes we read up to now
+ if (n == 0)
+ n = -1;
+ return n;
+ }
+ fQueue.read(cbuf, off + n, nn);
+ n += nn;
+ }
+
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ return n;
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/PollingTextCanvasModel.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/PollingTextCanvasModel.java
new file mode 100644
index 00000000000..d2fe910e5de
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/PollingTextCanvasModel.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [420928] Terminal widget leaks memory
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot;
+
+/**
+ * @author Michael.Scharf@scharf-software.com
+ *
+ */
+public class PollingTextCanvasModel extends AbstractTextCanvasModel {
+ private static final int DEFAULT_POLL_INTERVAL = 50;
+ int fPollInterval = -1;
+
+ /**
+ *
+ */
+ public PollingTextCanvasModel(ITerminalTextDataSnapshot snapshot) {
+ super(snapshot);
+ startPolling();
+ }
+
+ public void setUpdateInterval(int t) {
+ fPollInterval = t;
+ }
+
+ public void stopPolling() {
+ // timerExec only dispatches if the delay is >=0
+ fPollInterval = -1;
+ }
+
+ public void startPolling() {
+ if (fPollInterval < 0) {
+ fPollInterval = DEFAULT_POLL_INTERVAL;
+ Display.getDefault().timerExec(fPollInterval, new Runnable() {
+ @Override
+ public void run() {
+ update();
+ Display.getDefault().timerExec(fPollInterval, this);
+ }
+ });
+ }
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/StyleMap.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/StyleMap.java
new file mode 100644
index 00000000000..330070cdafb
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/StyleMap.java
@@ -0,0 +1,278 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Michael Scharf (Wind River) - [205260] Terminal does not take the font from the preferences
+ * Michael Scharf (Wind River) - [209746] There are cases where some colors not displayed correctly
+ * Michael Scharf (Wind River) - [206328] Terminal does not draw correctly with proportional fonts
+ * Martin Oberhuber (Wind River) - [247700] Terminal uses ugly fonts in JEE package
+ * Martin Oberhuber (Wind River) - [335358] Fix Terminal color definition
+ * Martin Oberhuber (Wind River) - [265352][api] Allow setting fonts programmatically
+ * Martin Oberhuber (Wind River) - [475422] Fix display on MacOSX Retina
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.tm.internal.terminal.preferences.ITerminalConstants;
+import org.eclipse.tm.internal.terminal.preferences.TerminalColorPresets;
+import org.eclipse.tm.terminal.model.TerminalColor;
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ * The split between responsibilities of StyleMap and TerminalStyle are not always clear. Generally
+ * the style parts that are global for a terminal are here, where as in TerminalStyle is about
+ * a specific range.
+ */
+public class StyleMap {
+
+ String fFontName = ITerminalConstants.FONT_DEFINITION;
+ private Point fCharSize;
+ private final TerminalStyle fDefaultStyle;
+ private boolean fInvertColors;
+ private boolean fProportional;
+ private final int[] fOffsets = new int[256];
+ private final Map fColorMap = new EnumMap<>(TerminalColor.class);
+
+ public StyleMap() {
+ fDefaultStyle = TerminalStyle.getDefaultStyle();
+ initFont();
+ initColors();
+ }
+
+ private void initColors() {
+ Map map = new EnumMap<>(TerminalColor.class);
+ TerminalColor[] values = TerminalColor.values();
+ for (TerminalColor terminalColor : values) {
+ RGB rgb = TerminalColorPresets.INSTANCE.getDefaultPreset().getRGB(terminalColor);
+ map.put(terminalColor, rgb);
+ }
+ updateColors(map);
+ }
+
+ private void initFont() {
+ updateFont(ITerminalConstants.FONT_DEFINITION);
+ }
+
+ private RGB getRGB(TerminalColor color) {
+ return fColorMap.get(color);
+ }
+
+ public RGB getForegrondRGB(TerminalStyle style) {
+ style = defaultIfNull(style);
+ RGB foregroundRGB;
+ if (style.isReverse()) {
+ foregroundRGB = style.getBackgroundRGB();
+ } else {
+ foregroundRGB = style.getForegroundRGB();
+ }
+ if (foregroundRGB != null) {
+ return foregroundRGB;
+ }
+
+ TerminalColor color;
+ if (style.isReverse()) {
+ color = style.getBackgroundTerminalColor();
+ } else {
+ color = style.getForegroundTerminalColor();
+ }
+
+ if (color == null) {
+ color = TerminalColor.FOREGROUND;
+ }
+
+ color = color.convertColor(fInvertColors, style.isBold());
+ return getRGB(color);
+ }
+
+ public RGB getBackgroundRGB(TerminalStyle style) {
+ style = defaultIfNull(style);
+ RGB backgroundRGB;
+ if (style.isReverse()) {
+ backgroundRGB = style.getForegroundRGB();
+ } else {
+ backgroundRGB = style.getBackgroundRGB();
+ }
+ if (backgroundRGB != null) {
+ return backgroundRGB;
+ }
+
+ TerminalColor color;
+ if (style.isReverse()) {
+ color = style.getForegroundTerminalColor();
+ } else {
+ color = style.getBackgroundTerminalColor();
+ }
+
+ if (color == null) {
+ color = TerminalColor.BACKGROUND;
+ }
+
+ color = color.convertColor(fInvertColors, style.isBold());
+ return getRGB(color);
+ }
+
+ private TerminalStyle defaultIfNull(TerminalStyle style) {
+ if (style == null)
+ style = fDefaultStyle;
+ return style;
+ }
+
+ public void setInvertedColors(boolean invert) {
+ fInvertColors = invert;
+ }
+
+ public boolean isInvertedColors() {
+ return fInvertColors;
+ }
+
+ public Font getFont(TerminalStyle style) {
+ style = defaultIfNull(style);
+ if (style.isBold()) {
+ return JFaceResources.getFontRegistry().getBold(fFontName);
+ } else if (style.isUnderline()) {
+ return JFaceResources.getFontRegistry().getItalic(fFontName);
+
+ }
+ return JFaceResources.getFontRegistry().get(fFontName);
+ }
+
+ public Font getFont() {
+ return JFaceResources.getFontRegistry().get(fFontName);
+ }
+
+ public int getFontWidth() {
+ return fCharSize.x;
+ }
+
+ public int getFontHeight() {
+ return fCharSize.y;
+ }
+
+ /**
+ * @deprecated Use {@link #updateFont(String)}
+ */
+ @Deprecated
+ public void updateFont() {
+ updateFont(ITerminalConstants.FONT_DEFINITION);
+ }
+
+ /**
+ * Update the StyleMap for a new font name.
+ * The font name must be a valid name in the Jface font registry.
+ * @param fontName Jface name of the new font to use.
+ * @since 3.2
+ */
+ public void updateFont(String fontName) {
+ Display display = Display.getCurrent();
+ GC gc = new GC(display);
+ if (JFaceResources.getFontRegistry().hasValueFor(fontName)) {
+ fFontName = fontName;
+ } else {
+ //fall back to "basic jface text font"
+ fFontName = "org.eclipse.jface.textfont"; //$NON-NLS-1$
+ }
+ gc.setFont(getFont());
+ fCharSize = gc.textExtent("W"); //$NON-NLS-1$
+ fProportional = false;
+
+ for (char c = ' '; c <= '~'; c++) {
+ // consider only the first 128 chars for deciding if a font
+ // is proportional. Collect char width as a side-effect.
+ if (measureChar(gc, c, true))
+ fProportional = true;
+ }
+ if (fProportional) {
+ // Widest char minus the padding on the left and right:
+ // Looks much better for small fonts
+ fCharSize.x -= 2;
+ // Collect width of the upper characters (for offset calculation)
+ for (char c = '~' + 1; c < fOffsets.length; c++) {
+ measureChar(gc, c, false);
+ }
+ // Calculate offsets based on each character's width and the bounding box
+ for (int i = ' '; i < fOffsets.length; i++) {
+ fOffsets[i] = (fCharSize.x - fOffsets[i]) / 2;
+ }
+ } else {
+ // Non-Proportional: Reset all offsets (eg after font change)
+ for (int i = 0; i < fOffsets.length; i++) {
+ fOffsets[i] = 0;
+ }
+ String t = "The quick brown Fox jumps over the Lazy Dog."; //$NON-NLS-1$
+ Point ext = gc.textExtent(t);
+ if (ext.x != fCharSize.x * t.length()) {
+ //Bug 475422: On OSX with Retina display and due to scaling,
+ //a text many be shorter than the sum of its bounding boxes.
+ //Because even with fixed width font, bounding box size
+ //may not be an integer but a fraction eg 6.75 pixels.
+ //
+ //Painting in proportional mode ensures that each character
+ //is painted individually into its proper bounding box, rather
+ //than using an optimization where Strings would be drawn as
+ //a whole. This fixes the "fractional bounding box" problem.
+ fProportional = true;
+ }
+ //measure font in boldface, too, and if wider then treat like proportional
+ gc.setFont(getFont(fDefaultStyle.setBold(true)));
+ Point charSizeBold = gc.textExtent("W"); //$NON-NLS-1$
+ if (fCharSize.x != charSizeBold.x) {
+ fProportional = true;
+ }
+ }
+ gc.dispose();
+ }
+
+ /**
+ * @param gc
+ * @param c
+ * @param updateMax
+ * @return true if the the font is proportional
+ */
+ private boolean measureChar(GC gc, char c, boolean updateMax) {
+ boolean proportional = false;
+ Point ext = gc.textExtent(String.valueOf(c));
+ if (ext.x > 0 && ext.y > 0 && (fCharSize.x != ext.x || fCharSize.y != ext.y)) {
+ proportional = true;
+ if (updateMax) {
+ fCharSize.x = Math.max(fCharSize.x, ext.x);
+ fCharSize.y = Math.max(fCharSize.y, ext.y);
+ }
+ }
+ fOffsets[c] = ext.x;
+ return proportional;
+ }
+
+ public boolean isFontProportional() {
+ return fProportional;
+ }
+
+ /**
+ * Return the offset in pixels required to center a given character
+ * @param c the character to measure
+ * @return the offset in x direction to center this character
+ */
+ public int getCharOffset(char c) {
+ if (c >= fOffsets.length)
+ return 0;
+ return fOffsets[c];
+ }
+
+ public void updateColors(Map colorMap) {
+ fColorMap.putAll(colorMap);
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/TextCanvas.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/TextCanvas.java
new file mode 100644
index 00000000000..0d7e8bda732
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/TextCanvas.java
@@ -0,0 +1,572 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Michael Scharf (Wind River) - [240098] The cursor should not blink when the terminal is disconnected
+ * Uwe Stieber (Wind River) - [281328] The very first few characters might be missing in the terminal control if opened and connected programmatically
+ * Martin Oberhuber (Wind River) - [294327] After logging in, the remote prompt is hidden
+ * Anton Leherbauer (Wind River) - [294468] Fix scroller and text line rendering
+ * Uwe Stieber (Wind River) - [205486] Fix ScrollLock always moving to line 1
+ * Anton Leherbauer (Wind River) - [219589] Copy an entire line selection
+ * Anton Leherbauer (Wind River) - [196465] Resizing Terminal changes Scroller location
+ * Anton Leherbauer (Wind River) - [324608] Terminal has strange scrolling behaviour
+ * Martin Oberhuber (Wind River) - [265352][api] Allow setting fonts programmatically
+ * Anton Leherbauer (Wind River) - [434749] UnhandledEventLoopException when copying to clipboard while the selection is empty
+ * Davy Landman (CWI) - [475267][api] Allow custom mouse listeners
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.tm.internal.terminal.control.ITerminalMouseListener;
+import org.eclipse.tm.internal.terminal.control.ITerminalMouseListener2;
+import org.eclipse.tm.terminal.model.TerminalColor;
+
+/**
+ * A cell oriented Canvas. Maintains a list of "cells".
+ * It can either be vertically or horizontally scrolled.
+ * The CellRenderer is responsible for painting the cell.
+ */
+public class TextCanvas extends GridCanvas {
+ protected final ITextCanvasModel fCellCanvasModel;
+ /** Renders the cells */
+ private final ILinelRenderer fCellRenderer;
+ private boolean fScrollLock;
+ private Point fDraggingStart;
+ private Point fDraggingEnd;
+ private boolean fHasSelection;
+ private ResizeListener fResizeListener;
+ private final List fMouseListeners;
+
+ // The minSize is meant to determine the minimum size of the backing store
+ // (grid) into which remote data is rendered. If the viewport is smaller
+ // than that minimum size, the backing store size remains at the minSize,
+ // and a scrollbar is shown instead. In reality, this has the following
+ // issues or effects today:
+ // (a) Bug 281328: For very early data coming in before the widget is
+ // realized, the minSize determines into what initial grid that is
+ // rendered. See also @link{#addResizeHandler(ResizeListener)}.
+ // (b) Bug 294468: Since we have redraw and size computation problems
+ // with horizontal scrollers, for now the minColumns must be small
+ // enough to avoid a horizontal scroller appearing in most cases.
+ // (b) Bug 294327: since we have problems with the vertical scroller
+ // showing the correct location, minLines must be small enough
+ // to avoid a vertical scroller or new data may be rendered off-screen.
+ // As a compromise, we have been working with a 20x4 since the Terminal
+ // inception, though many users would want a 80x24 minSize and backing
+ // store. Pros and cons of the small minsize:
+ // + consistent "remote size==viewport size", vi works as expected
+ // - dumb terminals which expect 80x24 render garbled on small viewport.
+ // If bug 294468 were resolved, an 80 wide minSize would be preferrable
+ // since it allows switching the terminal viewport small/large as needed,
+ // without destroying the backing store. For a complete solution,
+ // Bug 196462 tracks the request for a user-defined fixed-widow-size-mode.
+ private int fMinColumns = 80;
+ private int fMinLines = 4;
+ private boolean fCursorEnabled;
+ private boolean fResizing;
+
+ /**
+ * Create a new CellCanvas with the given SWT style bits.
+ * (SWT.H_SCROLL and SWT.V_SCROLL are automatically added).
+ */
+ public TextCanvas(Composite parent, ITextCanvasModel model, int style, ILinelRenderer cellRenderer) {
+ super(parent, style | SWT.H_SCROLL | SWT.V_SCROLL);
+ fCellRenderer = cellRenderer;
+ setCellWidth(fCellRenderer.getCellWidth());
+ setCellHeight(fCellRenderer.getCellHeight());
+ fCellCanvasModel = model;
+ fCellCanvasModel.addCellCanvasModelListener(new ITextCanvasModelListener() {
+ @Override
+ public void rangeChanged(int col, int line, int width, int height) {
+ if (isDisposed())
+ return;
+ repaintRange(col, line, width, height);
+ }
+
+ @Override
+ public void dimensionsChanged(int cols, int rows) {
+ if (isDisposed())
+ return;
+ calculateGrid();
+ }
+
+ @Override
+ public void terminalDataChanged() {
+ if (isDisposed())
+ return;
+
+ // scroll to end (unless scroll lock is active)
+ if (!fResizing) {
+ calculateGrid();
+ scrollToEnd();
+ }
+ }
+ });
+ // let the cursor blink if the text canvas gets the focus...
+ addFocusListener(new FocusListener() {
+ @Override
+ public void focusGained(FocusEvent e) {
+ fCellCanvasModel.setCursorEnabled(fCursorEnabled);
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ fCellCanvasModel.setCursorEnabled(false);
+ }
+ });
+ fMouseListeners = new ArrayList<>();
+ addMouseListener(new MouseListener() {
+ @Override
+ public void mouseDoubleClick(MouseEvent e) {
+ if (fMouseListeners.size() > 0) {
+ Point pt = screenPointToCell(e.x, e.y);
+ if (pt != null) {
+ for (ITerminalMouseListener l : fMouseListeners) {
+ if (l instanceof ITerminalMouseListener2) {
+ ITerminalMouseListener2 l2 = (ITerminalMouseListener2) l;
+ l2.mouseDoubleClick(fCellCanvasModel.getTerminalText(), pt.y, pt.x, e.button,
+ e.stateMask);
+ } else {
+ l.mouseDoubleClick(fCellCanvasModel.getTerminalText(), pt.y, pt.x, e.button);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ if (e.button == 1) { // left button
+ fDraggingStart = screenPointToCell(e.x, e.y);
+ fHasSelection = false;
+ if ((e.stateMask & SWT.SHIFT) != 0) {
+ Point anchor = fCellCanvasModel.getSelectionAnchor();
+ if (anchor != null)
+ fDraggingStart = anchor;
+ } else {
+ fCellCanvasModel.setSelectionAnchor(fDraggingStart);
+ }
+ fDraggingEnd = null;
+ }
+ if (fMouseListeners.size() > 0) {
+ Point pt = screenPointToCell(e.x, e.y);
+ if (pt != null) {
+ for (ITerminalMouseListener l : fMouseListeners) {
+ if (l instanceof ITerminalMouseListener2) {
+ ITerminalMouseListener2 l2 = (ITerminalMouseListener2) l;
+ l2.mouseDown(fCellCanvasModel.getTerminalText(), pt.y, pt.x, e.button, e.stateMask);
+ } else {
+ l.mouseDown(fCellCanvasModel.getTerminalText(), pt.y, pt.x, e.button);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void mouseUp(MouseEvent e) {
+ if (e.button == 1) { // left button
+ updateHasSelection(e);
+ if (fHasSelection)
+ setSelection(screenPointToCell(e.x, e.y));
+ else
+ fCellCanvasModel.setSelection(-1, -1, -1, -1);
+ fDraggingStart = null;
+ }
+ if (fMouseListeners.size() > 0) {
+ Point pt = screenPointToCell(e.x, e.y);
+ if (pt != null) {
+ for (ITerminalMouseListener l : fMouseListeners) {
+ if (l instanceof ITerminalMouseListener2) {
+ ITerminalMouseListener2 l2 = (ITerminalMouseListener2) l;
+ l2.mouseUp(fCellCanvasModel.getTerminalText(), pt.y, pt.x, e.button, e.stateMask);
+ } else {
+ l.mouseUp(fCellCanvasModel.getTerminalText(), pt.y, pt.x, e.button);
+ }
+ }
+ }
+ }
+ }
+ });
+ addMouseMoveListener(e -> {
+ if (fDraggingStart != null) {
+ updateHasSelection(e);
+ setSelection(screenPointToCell(e.x, e.y));
+ fCellCanvasModel.expandHoverSelectionAt(-1, -1);
+ } else if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.MOD1) {
+ // highlight (underline) word that would be used by MOD1 + mouse click
+ Point pt = screenPointToCell(e.x, e.y);
+ fCellCanvasModel.expandHoverSelectionAt(pt.y, pt.x);
+ } else {
+ fCellCanvasModel.expandHoverSelectionAt(-1, -1);
+ }
+ redraw();
+ });
+ serVerticalBarVisible(true);
+ setHorizontalBarVisible(false);
+ }
+
+ /**
+ * The user has to drag the mouse to at least one character to make a selection.
+ * Once this is done, even a one char selection is OK.
+ *
+ * @param e
+ */
+ private void updateHasSelection(MouseEvent e) {
+ if (fDraggingStart != null) {
+ Point p = screenPointToCell(e.x, e.y);
+ if (fDraggingStart.x != p.x || fDraggingStart.y != p.y)
+ fHasSelection = true;
+ }
+ }
+
+ void setSelection(Point p) {
+ if (fDraggingStart != null && !p.equals(fDraggingEnd)) {
+ fDraggingEnd = p;
+ if (compare(p, fDraggingStart) < 0) {
+ // bug 219589 - make sure selection start coordinates are non-negative
+ int startColumn = Math.max(0, p.x);
+ int startRow = Math.max(p.y, 0);
+ fCellCanvasModel.setSelection(startRow, fDraggingStart.y, startColumn, fDraggingStart.x);
+ } else {
+ fCellCanvasModel.setSelection(fDraggingStart.y, p.y, fDraggingStart.x, p.x);
+
+ }
+ }
+ }
+
+ int compare(Point p1, Point p2) {
+ if (p1.equals(p2))
+ return 0;
+ if (p1.y == p2.y) {
+ if (p1.x > p2.x)
+ return 1;
+ else
+ return -1;
+ }
+ if (p1.y > p2.y) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ public ILinelRenderer getCellRenderer() {
+ return fCellRenderer;
+ }
+
+ public int getMinColumns() {
+ return fMinColumns;
+ }
+
+ public void setMinColumns(int minColumns) {
+ fMinColumns = minColumns;
+ }
+
+ public int getMinLines() {
+ return fMinLines;
+ }
+
+ public void setMinLines(int minLines) {
+ fMinLines = minLines;
+ }
+
+ protected void onResize(boolean init) {
+ if (fResizeListener != null) {
+ Rectangle bonds = getClientArea();
+ int cellHeight = getCellHeight();
+ int cellWidth = getCellWidth();
+ int lines = bonds.height / cellHeight;
+ int columns = bonds.width / cellWidth;
+ // when the view is minimised, its size is set to 0
+ // we don't sent this to the terminal!
+ if ((lines > 0 && columns > 0) || init) {
+ if (columns < fMinColumns) {
+ if (!isHorizontalBarVisble()) {
+ setHorizontalBarVisible(true);
+ bonds = getClientArea();
+ lines = bonds.height / cellHeight;
+ }
+ columns = fMinColumns;
+ } else if (columns >= fMinColumns && isHorizontalBarVisble()) {
+ setHorizontalBarVisible(false);
+ bonds = getClientArea();
+ lines = bonds.height / cellHeight;
+ columns = bonds.width / cellWidth;
+ }
+ if (lines < fMinLines)
+ lines = fMinLines;
+ fResizeListener.sizeChanged(lines, columns);
+ }
+ }
+ super.onResize();
+ calculateGrid();
+ }
+
+ @Override
+ protected void onResize() {
+ fResizing = true;
+ try {
+ onResize(false);
+ } finally {
+ fResizing = false;
+ }
+ }
+
+ private void calculateGrid() {
+ Rectangle virtualBounds = getVirtualBounds();
+ setRedraw(false);
+ try {
+ setVirtualExtend(getCols() * getCellWidth(), getRows() * getCellHeight());
+ getParent().layout();
+ if (fResizing) {
+ // scroll to end if view port was near last line
+ Rectangle viewRect = getViewRectangle();
+ if (virtualBounds.height - (viewRect.y + viewRect.height) < getCellHeight() * 2)
+ scrollToEnd();
+ }
+ } finally {
+ setRedraw(true);
+ }
+ }
+
+ void scrollToEnd() {
+ if (!fScrollLock) {
+ int y = -(getRows() * getCellHeight() - getClientArea().height);
+ if (y > 0) {
+ y = 0;
+ }
+ Rectangle v = getViewRectangle();
+ if (v.y != -y) {
+ setVirtualOrigin(v.x, y);
+ }
+ // make sure the scroll area is correct:
+ scrollY(getVerticalBar());
+ scrollX(getHorizontalBar());
+ }
+ }
+
+ /**
+ *
+ * @return true if the cursor should be shown on output....
+ */
+ public boolean isScrollLock() {
+ return fScrollLock;
+ }
+
+ /**
+ * If set then if the size changes
+ */
+ public void setScrollLock(boolean scrollLock) {
+ fScrollLock = scrollLock;
+ }
+
+ protected void repaintRange(int col, int line, int width, int height) {
+ Point origin = cellToOriginOnScreen(col, line);
+ Rectangle r = new Rectangle(origin.x, origin.y, width * getCellWidth(), height * getCellHeight());
+ repaint(r);
+ }
+
+ @Override
+ protected void drawLine(GC gc, int line, int x, int y, int colFirst, int colLast) {
+ fCellRenderer.drawLine(fCellCanvasModel, gc, line, x, y, colFirst, colLast);
+ }
+
+ @Override
+ protected Color getTerminalBackgroundColor(Device device) {
+ return fCellRenderer.getDefaultBackgroundColor(device);
+ }
+
+ @Override
+ @Deprecated
+ protected Color getTerminalBackgroundColor() {
+ return fCellRenderer.getDefaultBackgroundColor();
+ }
+
+ @Override
+ protected void visibleCellRectangleChanged(int x, int y, int width, int height) {
+ fCellCanvasModel.setVisibleRectangle(y, x, height, width);
+ update();
+ }
+
+ @Override
+ protected int getCols() {
+ return fCellCanvasModel.getTerminalText().getWidth();
+ }
+
+ @Override
+ protected int getRows() {
+ return fCellCanvasModel.getTerminalText().getHeight();
+ }
+
+ public String getSelectionText() {
+ // TODO -- create a hasSelectionMethod!
+ return fCellCanvasModel.getSelectedText();
+ }
+
+ public void copy() {
+ String selectionText = getSelectionText();
+ if (selectionText != null && selectionText.length() > 0) {
+ Clipboard clipboard = new Clipboard(getDisplay());
+ clipboard.setContents(new Object[] { selectionText }, new Transfer[] { TextTransfer.getInstance() });
+ clipboard.dispose();
+ }
+ }
+
+ public void selectAll() {
+ fCellCanvasModel.setSelection(0, fCellCanvasModel.getTerminalText().getHeight(), 0,
+ fCellCanvasModel.getTerminalText().getWidth());
+ fCellCanvasModel.setSelectionAnchor(new Point(0, 0));
+ }
+
+ /**
+ * @since 4.1
+ */
+ public void clearSelection() {
+ fCellCanvasModel.setSelection(-1, -1, -1, -1);
+ }
+
+ /**
+ * Collect and return all text present in the widget.
+ *
+ * Individual lines of the returned text are separated by '\n'.
+ *
+ *
The method is primarily designed for test automation. Tests need
+ * to check what happens in a terminal (e.g. if and how a CDT Debugger
+ * Console reacts in a GDB session) and this method allows to read the
+ * text present in the terminal.
+ *
+ * @since 4.4
+ */
+ public String getAllText() {
+ return fCellCanvasModel.getAllText();
+ }
+
+ public boolean isEmpty() {
+ return false;
+ }
+
+ /**
+ * Gets notified when the visible size of the terminal changes.
+ * This should update the model!
+ *
+ */
+ public interface ResizeListener {
+ void sizeChanged(int lines, int columns);
+ }
+
+ /**
+ * @param listener this listener gets notified, when the size of
+ * the widget changed. It should change the dimensions of the underlying
+ * terminaldata
+ */
+ public void addResizeHandler(ResizeListener listener) {
+ if (fResizeListener != null)
+ throw new IllegalArgumentException("There can be at most one listener at the moment!"); //$NON-NLS-1$
+ fResizeListener = listener;
+
+ // Bug 281328: [terminal] The very first few characters might be missing in
+ // the terminal control if opened and connected programmatically
+ //
+ // In case the terminal had not been visible yet or is too small (less than one
+ // line visible), the terminal should have a minimum size to avoid RuntimeExceptions.
+ Rectangle bonds = getClientArea();
+ if (bonds.height < getCellHeight() || bonds.width < getCellWidth()) {
+ //Widget not realized yet, or minimized to < 1 item:
+ //Just tell the listener our min size
+ fResizeListener.sizeChanged(getMinLines(), getMinColumns());
+ } else {
+ //Widget realized: compute actual size and force telling the listener
+ onResize(true);
+ }
+ }
+
+ /**
+ * Notify about a change of the global Font Preference.
+ * @deprecated Use {@link #updateFont(String)}
+ */
+ @Deprecated
+ public void onFontChange() {
+ fCellRenderer.onFontChange();
+ setCellWidth(fCellRenderer.getCellWidth());
+ setCellHeight(fCellRenderer.getCellHeight());
+ calculateGrid();
+ }
+
+ public void updateFont(String fontName) {
+ fCellRenderer.updateFont(fontName);
+ setCellWidth(fCellRenderer.getCellWidth());
+ setCellHeight(fCellRenderer.getCellHeight());
+ calculateGrid();
+ }
+
+ public void updateColors(Map map) {
+ fCellRenderer.updateColors(map);
+ redraw();
+ }
+
+ public void setInvertedColors(boolean invert) {
+ fCellRenderer.setInvertedColors(invert);
+ redraw();
+ }
+
+ public boolean isInvertedColors() {
+ return fCellRenderer.isInvertedColors();
+ }
+
+ /**
+ * @return true if the cursor is enabled (blinking). By default the cursor is not enabled.
+ */
+ public boolean isCursorEnabled() {
+ return fCursorEnabled;
+ }
+
+ /**
+ * @param enabled enabling means that the cursor blinks
+ */
+ public void setCursorEnabled(boolean enabled) {
+ if (enabled != fCursorEnabled) {
+ fCursorEnabled = enabled;
+ fCellCanvasModel.setCursorEnabled(fCursorEnabled);
+ }
+
+ }
+
+ public void addTerminalMouseListener(final ITerminalMouseListener listener) {
+ fMouseListeners.add(listener);
+ }
+
+ public void removeTerminalMouseListener(ITerminalMouseListener listener) {
+ fMouseListeners.remove(listener);
+ }
+
+ public String getHoverSelection() {
+ return fCellCanvasModel.getHoverSelectionText();
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/TextLineRenderer.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/TextLineRenderer.java
new file mode 100644
index 00000000000..845ee499581
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/TextLineRenderer.java
@@ -0,0 +1,248 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Michael Scharf (Wind River) - [205260] Terminal does not take the font from the preferences
+ * Michael Scharf (Wind River) - [206328] Terminal does not draw correctly with proportional fonts
+ * Anton Leherbauer (Wind River) - [294468] Fix scroller and text line rendering
+ * Martin Oberhuber (Wind River) - [265352][api] Allow setting fonts programmatically
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+import java.util.Map;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin;
+import org.eclipse.tm.internal.terminal.provisional.api.Logger;
+import org.eclipse.tm.terminal.model.ITerminalTextDataReadOnly;
+import org.eclipse.tm.terminal.model.LineSegment;
+import org.eclipse.tm.terminal.model.TerminalColor;
+import org.eclipse.tm.terminal.model.TerminalStyle;
+
+/**
+ *
+ */
+public class TextLineRenderer implements ILinelRenderer {
+ private static final boolean DEBUG_HOVER = TerminalPlugin.isOptionEnabled(Logger.TRACE_DEBUG_LOG_HOVER);
+ private final ITextCanvasModel fModel;
+ private final StyleMap fStyleMap;
+
+ public TextLineRenderer(TextCanvas c, ITextCanvasModel model) {
+ fModel = model;
+ fStyleMap = new StyleMap();
+ }
+
+ @Override
+ public int getCellWidth() {
+ return fStyleMap.getFontWidth();
+ }
+
+ @Override
+ public int getCellHeight() {
+ return fStyleMap.getFontHeight();
+ }
+
+ @Override
+ public void drawLine(ITextCanvasModel model, GC gc, int line, int x, int y, int colFirst, int colLast) {
+ int width = getCellWidth() * (colLast - colFirst);
+ int height = getCellHeight();
+ if (width <= 0 || height <= 0) {
+ return;
+ }
+ Image buffer = new Image(gc.getDevice(), width, height);
+ GC doubleBufferGC = new GC(buffer);
+ if (line < 0 || line >= getTerminalText().getHeight() || colFirst >= getTerminalText().getWidth()
+ || colFirst - colLast == 0) {
+ fillBackground(doubleBufferGC, 0, 0, width, height);
+ } else {
+ colLast = Math.min(colLast, getTerminalText().getWidth());
+ LineSegment[] segments = getTerminalText().getLineSegments(line, colFirst, colLast - colFirst);
+ for (int i = 0; i < segments.length; i++) {
+ LineSegment segment = segments[i];
+ TerminalStyle style = segment.getStyle();
+ setupGC(doubleBufferGC, style);
+ String text = segment.getText();
+ drawText(doubleBufferGC, 0, 0, colFirst, segment.getColumn(), text);
+ drawCursor(model, doubleBufferGC, line, 0, 0, colFirst);
+ }
+ if (fModel.hasHoverSelection(line)) {
+ if (DEBUG_HOVER) {
+ System.out.format("hover: %s contains hover selection\n", line); //$NON-NLS-1$
+ }
+ Point hsStart = fModel.getHoverSelectionStart();
+ Point hsEnd = fModel.getHoverSelectionEnd();
+ int colStart = line == hsStart.y ? hsStart.x : 0;
+ int colEnd = line == hsEnd.y ? hsEnd.x : getTerminalText().getWidth();
+ if (colStart < colEnd) {
+ RGB defaultFg = fStyleMap.getForegrondRGB(null);
+ doubleBufferGC.setForeground(new Color(doubleBufferGC.getDevice(), defaultFg));
+ drawUnderline(doubleBufferGC, colStart, colEnd);
+ }
+ }
+ if (fModel.hasLineSelection(line)) {
+ TerminalStyle style = TerminalStyle.getStyle(TerminalColor.SELECTION_FOREGROUND,
+ TerminalColor.SELECTION_BACKGROUND);
+ setupGC(doubleBufferGC, style);
+ Point start = model.getSelectionStart();
+ Point end = model.getSelectionEnd();
+ char[] chars = model.getTerminalText().getChars(line);
+ if (chars != null) {
+ int offset = 0;
+ if (start.y == line)
+ offset = start.x;
+ offset = Math.max(offset, colFirst);
+ int len;
+ if (end.y == line)
+ len = end.x - offset + 1;
+ else
+ len = chars.length - offset + 1;
+ len = Math.min(len, chars.length - offset);
+ if (len > 0) {
+ String text = new String(chars, offset, len);
+ drawText(doubleBufferGC, 0, 0, colFirst, offset, text);
+ }
+ }
+ }
+ }
+ gc.drawImage(buffer, x, y);
+ doubleBufferGC.dispose();
+ buffer.dispose();
+ }
+
+ private void fillBackground(GC gc, int x, int y, int width, int height) {
+ Color bg = gc.getBackground();
+ gc.setBackground(getDefaultBackgroundColor(gc.getDevice()));
+ gc.fillRectangle(x, y, width, height);
+ gc.setBackground(bg);
+
+ }
+
+ @Override
+ public Color getDefaultBackgroundColor() {
+ return getDefaultBackgroundColor(Display.getDefault());
+ }
+
+ @Override
+ public Color getDefaultBackgroundColor(Device device) {
+ // null == default style
+ RGB backgroundRGB = fStyleMap.getBackgroundRGB(null);
+ return new Color(device, backgroundRGB);
+ }
+
+ private void drawCursor(ITextCanvasModel model, GC gc, int row, int x, int y, int colFirst) {
+ if (!model.isCursorOn())
+ return;
+ int cursorLine = model.getCursorLine();
+
+ if (row == cursorLine) {
+ int cursorColumn = model.getCursorColumn();
+ if (cursorColumn < getTerminalText().getWidth()) {
+ TerminalStyle style = getTerminalText().getStyle(row, cursorColumn);
+ if (style == null) {
+ // TODO make the cursor color customizable
+ style = TerminalStyle.getStyle(TerminalColor.FOREGROUND, TerminalColor.BACKGROUND);
+ }
+ style = style.setReverse(!style.isReverse());
+ setupGC(gc, style);
+ String text = String.valueOf(getTerminalText().getChar(row, cursorColumn));
+ drawText(gc, x, y, colFirst, cursorColumn, text);
+ }
+ }
+ }
+
+ private void drawText(GC gc, int x, int y, int colFirst, int col, String text) {
+ int offset = (col - colFirst) * getCellWidth();
+ if (fStyleMap.isFontProportional()) {
+ // draw the background
+ // TODO why does this not work???????
+ // gc.fillRectangle(x,y,fStyleMap.getFontWidth()*text.length(),fStyleMap.getFontHeight());
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ int xx = x + offset + i * fStyleMap.getFontWidth();
+ // TODO why do I have to draw the background character by character??????
+ gc.fillRectangle(xx, y, fStyleMap.getFontWidth(), fStyleMap.getFontHeight());
+ if (c != ' ' && c != '\000') {
+ gc.drawString(String.valueOf(c), fStyleMap.getCharOffset(c) + xx, y, false);
+ }
+ }
+ } else {
+ text = text.replace('\000', ' ');
+ gc.drawString(text, x + offset, y, false);
+ }
+ }
+
+ /**
+ *
+ * @param gc
+ * @param colStart Starting text column to underline (inclusive)
+ * @param colEnd Ending text column to underline (inclusive)
+ */
+ private void drawUnderline(GC gc, int colStart, int colEnd) {
+ int y = getCellHeight() - 1;
+ int x = getCellWidth() * colStart;
+
+ // x2 is the right side of last column being underlined.
+ int x2 = (colEnd + 1) * getCellWidth() - 1;
+ gc.drawLine(x, y, x2, y);
+ }
+
+ private void setupGC(GC gc, TerminalStyle style) {
+ RGB foregrondColor = fStyleMap.getForegrondRGB(style);
+ gc.setForeground(new Color(gc.getDevice(), foregrondColor));
+ RGB backgroundColor = fStyleMap.getBackgroundRGB(style);
+ gc.setBackground(new Color(gc.getDevice(), backgroundColor));
+
+ Font f = fStyleMap.getFont(style);
+ if (f != gc.getFont()) {
+ gc.setFont(f);
+ }
+ }
+
+ ITerminalTextDataReadOnly getTerminalText() {
+ return fModel.getTerminalText();
+ }
+
+ /**
+ * @deprecated Use {@link #updateFont(String)}
+ */
+ @Deprecated
+ @Override
+ public void onFontChange() {
+ fStyleMap.updateFont();
+ }
+
+ @Override
+ public void updateFont(String fontName) {
+ fStyleMap.updateFont(fontName);
+ }
+
+ @Override
+ public void updateColors(Map map) {
+ fStyleMap.updateColors(map);
+ }
+
+ @Override
+ public void setInvertedColors(boolean invert) {
+ fStyleMap.setInvertedColors(invert);
+
+ }
+
+ @Override
+ public boolean isInvertedColors() {
+ return fStyleMap.isInvertedColors();
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/VirtualCanvas.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/VirtualCanvas.java
new file mode 100644
index 00000000000..2fc846e7dbc
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/internal/terminal/textcanvas/VirtualCanvas.java
@@ -0,0 +1,357 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Anton Leherbauer (Wind River) - [294468] Fix scroller and text line rendering
+ *******************************************************************************/
+package org.eclipse.tm.internal.terminal.textcanvas;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin;
+
+/**
+ * A Canvas showing a virtual object.
+ * Virtual: the extent of the total canvas.
+ * Screen: the visible client area in the screen.
+ */
+public abstract class VirtualCanvas extends Canvas {
+
+ private final Rectangle fVirtualBounds = new Rectangle(0, 0, 0, 0);
+ private Rectangle fClientArea;
+ /**
+ * prevent infinite loop in {@link #updateScrollbars()}
+ */
+ private boolean fInUpdateScrollbars;
+ private static boolean fInUpdateScrollbarsLogged;
+
+ public VirtualCanvas(Composite parent, int style) {
+ super(parent, style | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE);
+ fClientArea = getClientArea();
+ addListener(SWT.Paint, event -> paint(event.gc));
+ addListener(SWT.Resize, event -> {
+ fClientArea = getClientArea();
+ onResize();
+ });
+ getVerticalBar().addListener(SWT.Selection, e -> scrollY((ScrollBar) e.widget));
+ getHorizontalBar().addListener(SWT.Selection, e -> scrollX((ScrollBar) e.widget));
+ }
+
+ protected void onResize() {
+ updateViewRectangle();
+ }
+
+ protected void scrollX(ScrollBar hBar) {
+ int hSelection = hBar.getSelection();
+ int destX = -hSelection - fVirtualBounds.x;
+ fVirtualBounds.x = -hSelection;
+ scrollSmart(destX, 0);
+ updateViewRectangle();
+ }
+
+ protected void scrollXDelta(int delta) {
+ getHorizontalBar().setSelection(-fVirtualBounds.x + delta);
+ scrollX(getHorizontalBar());
+ }
+
+ protected void scrollY(ScrollBar vBar) {
+ int vSelection = vBar.getSelection();
+ int destY = -vSelection - fVirtualBounds.y;
+ if (destY != 0) {
+ fVirtualBounds.y = -vSelection;
+ scrollSmart(0, destY);
+ updateViewRectangle();
+ }
+
+ }
+
+ protected void scrollYDelta(int delta) {
+ getVerticalBar().setSelection(-fVirtualBounds.y + delta);
+ scrollY(getVerticalBar());
+ }
+
+ protected void scrollSmart(int deltaX, int deltaY) {
+ if (deltaX != 0 || deltaY != 0) {
+ Rectangle rect = getBounds();
+ scroll(deltaX, deltaY, 0, 0, rect.width, rect.height, false);
+ }
+ }
+
+ /**
+ * @param rect in virtual space
+ */
+ protected void revealRect(Rectangle rect) {
+ Rectangle visibleRect = getScreenRectInVirtualSpace();
+ // scroll the X part
+ int deltaX = 0;
+ if (rect.x < visibleRect.x) {
+ deltaX = rect.x - visibleRect.x;
+ } else if (visibleRect.x + visibleRect.width < rect.x + rect.width) {
+ deltaX = (rect.x + rect.width) - (visibleRect.x + visibleRect.width);
+ }
+ if (deltaX != 0) {
+ getHorizontalBar().setSelection(-fVirtualBounds.x + deltaX);
+ scrollX(getHorizontalBar());
+ }
+
+ // scroll the Y part
+ int deltaY = 0;
+ if (rect.y < visibleRect.y) {
+ deltaY = rect.y - visibleRect.y;
+ } else if (visibleRect.y + visibleRect.height < rect.y + rect.height) {
+ deltaY = (rect.y + rect.height) - (visibleRect.y + visibleRect.height);
+
+ }
+ if (deltaY != 0) {
+ getVerticalBar().setSelection(-fVirtualBounds.y + deltaY);
+ scrollY(getVerticalBar());
+ }
+ }
+
+ protected void repaint(Rectangle r) {
+ if (isDisposed())
+ return;
+ if (inClipping(r, fClientArea)) {
+ redraw(r.x, r.y, r.width, r.height, true);
+ update();
+ }
+ }
+
+ /**
+ * Paint the virtual canvas.
+ * Override to implement actual paint method.
+ * @param gc graphics context to paint in
+ */
+ abstract protected void paint(GC gc);
+
+ abstract protected Color getTerminalBackgroundColor(Device device);
+
+ /**
+ * @deprecated Use {@link #getTerminalBackgroundColor(Device)}
+ */
+ @Deprecated
+ protected Color getTerminalBackgroundColor() {
+ // return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
+ return getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+ }
+
+ protected void paintUnoccupiedSpace(GC gc, Rectangle clipping) {
+ int width = fVirtualBounds.width + fVirtualBounds.x;
+ int height = fVirtualBounds.height + fVirtualBounds.y;
+ int marginWidth = (clipping.x + clipping.width) - width;
+ int marginHeight = (clipping.y + clipping.height) - height;
+ if (marginWidth > 0 || marginHeight > 0) {
+ Color bg = getBackground();
+ gc.setBackground(getTerminalBackgroundColor());
+ if (marginWidth > 0) {
+ gc.fillRectangle(width, clipping.y, marginWidth, clipping.height);
+ }
+ if (marginHeight > 0) {
+ gc.fillRectangle(clipping.x, height, clipping.width, marginHeight);
+ }
+ gc.setBackground(bg);
+ }
+ }
+
+ /**
+ * @private
+ */
+ protected boolean inClipping(Rectangle clipping, Rectangle r) {
+ // TODO check if this is OK in all cases (the <=!)
+ //
+ if (r.x + r.width <= clipping.x)
+ return false;
+ if (clipping.x + clipping.width <= r.x)
+ return false;
+ if (r.y + r.height <= clipping.y)
+ return false;
+ if (clipping.y + clipping.height <= r.y)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * @return the screen rect in virtual space (starting with (0,0))
+ * of the visible screen. (x,y>=0)
+ */
+ protected Rectangle getScreenRectInVirtualSpace() {
+ Rectangle r = new Rectangle(fClientArea.x - fVirtualBounds.x, fClientArea.y - fVirtualBounds.y,
+ fClientArea.width, fClientArea.height);
+ return r;
+ }
+
+ /**
+ * @return the rect in virtual space (starting with (0,0))
+ * of the visible screen. (x,y>=0)
+ */
+ protected Rectangle getRectInVirtualSpace(Rectangle r) {
+ return new Rectangle(r.x - fVirtualBounds.x, r.y - fVirtualBounds.y, r.width, r.height);
+ }
+
+ /**
+ * Sets the extent of the virtual display area
+ * @param width width of the display area
+ * @param height height of the display area
+ */
+ protected void setVirtualExtend(int width, int height) {
+ fVirtualBounds.width = width;
+ fVirtualBounds.height = height;
+ updateScrollbars();
+ updateViewRectangle();
+ }
+
+ /**
+ * sets the scrolling origin. Also sets the scrollbars.
+ * Does NOT redraw!
+ * Use negative values (move the virtual origin to the top left
+ * to see something in the screen (which is located at (0,0))
+ * @param x
+ * @param y
+ */
+ protected void setVirtualOrigin(int x, int y) {
+ if (fVirtualBounds.x != x || fVirtualBounds.y != y) {
+ fVirtualBounds.x = x;
+ fVirtualBounds.y = y;
+ getHorizontalBar().setSelection(-x);
+ getVerticalBar().setSelection(-y);
+ updateViewRectangle();
+ }
+ }
+
+ protected Rectangle getVirtualBounds() {
+ return cloneRectangle(fVirtualBounds);
+ }
+
+ /**
+ * @param x
+ * @return the virtual coordinate in screen space
+ */
+ protected int virtualXtoScreen(int x) {
+ return x + fVirtualBounds.x;
+ }
+
+ protected int virtualYtoScreen(int y) {
+ return y + fVirtualBounds.y;
+ }
+
+ protected int screenXtoVirtual(int x) {
+ return x - fVirtualBounds.x;
+ }
+
+ protected int screenYtoVirtual(int y) {
+ return y - fVirtualBounds.y;
+ }
+
+ /** called when the viewed part is changing */
+ private final Rectangle fViewRectangle = new Rectangle(0, 0, 0, 0);
+
+ protected void updateViewRectangle() {
+ if (fViewRectangle.x == -fVirtualBounds.x && fViewRectangle.y == -fVirtualBounds.y
+ && fViewRectangle.width == fClientArea.width && fViewRectangle.height == fClientArea.height)
+ return;
+ fViewRectangle.x = -fVirtualBounds.x;
+ fViewRectangle.y = -fVirtualBounds.y;
+ fViewRectangle.width = fClientArea.width;
+ fViewRectangle.height = fClientArea.height;
+ viewRectangleChanged(fViewRectangle.x, fViewRectangle.y, fViewRectangle.width, fViewRectangle.height);
+ }
+
+ protected Rectangle getViewRectangle() {
+ return cloneRectangle(fViewRectangle);
+ }
+
+ private Rectangle cloneRectangle(Rectangle r) {
+ return new Rectangle(r.x, r.y, r.width, r.height);
+ }
+
+ /**
+ * Called when the viewed part has changed.
+ * Override when you need this information....
+ * Is only called if the values change!
+ * @param x visible in virtual space
+ * @param y visible in virtual space
+ * @param width
+ * @param height
+ */
+ protected void viewRectangleChanged(int x, int y, int width, int height) {
+ }
+
+ /**
+ * @private
+ */
+ private void updateScrollbars() {
+ // don't get into infinite loops....
+ if (!fInUpdateScrollbars) {
+ fInUpdateScrollbars = true;
+ try {
+ doUpdateScrollbar();
+ } finally {
+ fInUpdateScrollbars = false;
+ }
+ } else {
+ if (!fInUpdateScrollbarsLogged) {
+ fInUpdateScrollbarsLogged = true;
+ TerminalPlugin.getDefault().getLog()
+ .log(new Status(IStatus.WARNING, TerminalPlugin.PLUGIN_ID, IStatus.OK,
+ "Unexpected Recursion in terminal", //$NON-NLS-1$
+ new RuntimeException()));
+ }
+ }
+ }
+
+ private void doUpdateScrollbar() {
+ Rectangle clientArea = getClientArea();
+ ScrollBar horizontal = getHorizontalBar();
+ // even if setVisible was called on the scrollbar, isVisible
+ // returns false if its parent is not visible.
+ if (!isVisible() || horizontal.isVisible()) {
+ horizontal.setPageIncrement(clientArea.width - horizontal.getIncrement());
+ int max = fVirtualBounds.width;
+ horizontal.setMaximum(max);
+ horizontal.setThumb(clientArea.width);
+ }
+ ScrollBar vertical = getVerticalBar();
+ // even if setVisible was called on the scrollbar, isVisible
+ // returns false if its parent is not visible.
+ if (!isVisible() || vertical.isVisible()) {
+ vertical.setPageIncrement(clientArea.height - vertical.getIncrement());
+ int max = fVirtualBounds.height;
+ vertical.setMaximum(max);
+ vertical.setThumb(clientArea.height);
+ }
+ }
+
+ protected boolean isVertialBarVisible() {
+ return getVerticalBar().isVisible();
+ }
+
+ protected void serVerticalBarVisible(boolean showVScrollBar) {
+ ScrollBar vertical = getVerticalBar();
+ vertical.setVisible(showVScrollBar);
+ vertical.setSelection(0);
+ }
+
+ protected boolean isHorizontalBarVisble() {
+ return getHorizontalBar().isVisible();
+ }
+
+ protected void setHorizontalBarVisible(boolean showHScrollBar) {
+ ScrollBar horizontal = getHorizontalBar();
+ horizontal.setVisible(showHScrollBar);
+ horizontal.setSelection(0);
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/ITerminalTextData.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/ITerminalTextData.java
new file mode 100644
index 00000000000..37a563284b2
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/ITerminalTextData.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - [261486][api][cleanup] Mark @noimplement interfaces as @noextend
+ * Anton Leherbauer (Wind River) - [453393] Add support for copying wrapped lines without line break
+ *******************************************************************************/
+package org.eclipse.tm.terminal.model;
+
+/**
+ * A writable matrix of characters and {@link TerminalStyle}. This is intended to be the
+ * low level representation of the text of a Terminal. Higher layers are
+ * responsible to fill the text and styles into this representation.
+ *
+ * Note: Implementations of this interface has to be thread safe.
+ *
+ *
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ */
+public interface ITerminalTextData extends ITerminalTextDataReadOnly {
+
+ /**
+ * Sets the dimensions of the data. If the dimensions are smaller than the current
+ * dimensions, the lines will be chopped. If the dimensions are bigger, then
+ * the new elements will be filled with 0 chars and null Style.
+ * @param height
+ * @param width
+ */
+ void setDimensions(int height, int width);
+
+ void setMaxHeight(int height);
+
+ int getMaxHeight();
+
+ /**
+ * Set a single character and the associated {@link TerminalStyle}.
+ * @param line line must be >=0 and < height
+ * @param column column must be >=0 and < width
+ * @param c the new character at this position
+ * @param style the style or null
+ * @since 5.0
+ */
+ void setChar(int line, int column, char c, TerminalStyle style);
+
+ /**
+ * Set an array of characters showing in the same {@link TerminalStyle}.
+ * @param line line must be >=0 and < height
+ * @param column column must be >=0 and < width
+ * @param chars the new characters at this position
+ * @param style the style or null
+ * @since 5.0
+ */
+ void setChars(int line, int column, char[] chars, TerminalStyle style);
+
+ /**
+ * Set a subrange of an array of characters showing in the same {@link TerminalStyle}.
+ * @param line line must be >=0 and < height
+ * @param column column must be >=0 and < width
+ * @param chars the new characters at this position
+ * @param start the start index in the chars array
+ * @param len the number of characters to insert. Characters beyond width are not inserted.
+ * @param style the style or null
+ * @since 5.0
+ */
+ void setChars(int line, int column, char[] chars, int start, int len, TerminalStyle style);
+
+ /**
+ * Cleans the entire line.
+ * @param line
+ */
+ void cleanLine(int line);
+
+ /**
+ * Shifts some lines up or down. The "empty" space is filled with '\000' chars
+ * and null {@link TerminalStyle}
+ * To illustrate shift, here is some sample data:
+ *
+ * 0 aaaa
+ * 1 bbbb
+ * 2 cccc
+ * 3 dddd
+ * 4 eeee
+ *
+ *
+ * Shift a region of 3 lines up by one line shift(1,3,-1)
+ *
+ * 0 aaaa
+ * 1 cccc
+ * 2 dddd
+ * 3
+ * 4 eeee
+ *
+ *
+ *
+ * Shift a region of 3 lines down by one line shift(1,3,1)
+ *
+ * 0 aaaa
+ * 1
+ * 2 bbbb
+ * 3 cccc
+ * 4 eeee
+ *
+ * @param startLine the start line of the shift
+ * @param size the number of lines to shift
+ * @param shift how much scrolling is done. New scrolled area is filled with '\000'.
+ * Negative number means scroll down, positive scroll up (see example above).
+ */
+ void scroll(int startLine, int size, int shift);
+
+ /**Adds a new line to the terminal. If maxHeigth is reached, the entire terminal
+ * will be scrolled. Else a line will be added.
+ */
+ void addLine();
+
+ /**
+ * Copies the entire source into this and changes the size accordingly
+ * @param source
+ */
+ void copy(ITerminalTextData source);
+
+ /**
+ * Copy a sourceLine from source to this at destLine.
+ * @param source
+ * @param sourceLine
+ * @param destLine
+ */
+ void copyLine(ITerminalTextData source, int sourceLine, int destLine);
+
+ /**
+ * Copy length lines from source starting at sourceLine into this starting at
+ * destLine.
+ * @param source
+ * @param sourceStartLine
+ * @param destStartLine
+ * @param length
+ */
+ void copyRange(ITerminalTextData source, int sourceStartLine, int destStartLine, int length);
+
+ void setCursorLine(int line);
+
+ void setCursorColumn(int column);
+
+ /**
+ * Makes this line a wrapped line which logically continues on next line.
+ *
+ * @param line
+ * @since 3.3
+ */
+ void setWrappedLine(int line);
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/ITerminalTextDataReadOnly.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/ITerminalTextDataReadOnly.java
new file mode 100644
index 00000000000..af30864e56f
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/ITerminalTextDataReadOnly.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - [261486][api][cleanup] Mark @noimplement interfaces as @noextend
+ * Anton Leherbauer (Wind River) - [453393] Add support for copying wrapped lines without line break
+ *******************************************************************************/
+package org.eclipse.tm.terminal.model;
+
+/**
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ */
+public interface ITerminalTextDataReadOnly {
+
+ /**
+ * @return the width of the terminal
+ */
+ int getWidth();
+
+ /**
+ * @return the height of the terminal
+ */
+ int getHeight();
+
+ /**
+ * @param line be >=0 and < height
+ * @param startCol must be >=0 and < width
+ * @param numberOfCols must be > 0
+ * @return a the line segments of the specified range
+ */
+ LineSegment[] getLineSegments(int line, int startCol, int numberOfCols);
+
+ /**
+ * @param line must be >=0 and < height
+ * @param column must be >=0 and < width
+ * @return the character at column,line
+ */
+ char getChar(int line, int column);
+
+ /**
+ * @param line must be >=0 and < height
+ * @param column must be >=0 and < width
+ * @return style at column,line or null
+ * @since 5.0
+ */
+ TerminalStyle getStyle(int line, int column);
+
+ /**
+ * Creates a new instance of {@link ITerminalTextDataSnapshot} that
+ * can be used to track changes. Make sure to call {@link ITerminalTextDataSnapshot#detach()}
+ * if you don't need the snapshots anymore.
+ * Note: A new snapshot is empty and needs a call to {@link ITerminalTextDataSnapshot#updateSnapshot(boolean)} to
+ * get its initial values. You might want to setup the snapshot to your needs by calling
+ * {@link ITerminalTextDataSnapshot#setInterestWindow(int, int)}.
+ *
+ * @return a new instance of {@link ITerminalTextDataSnapshot} that "listens" to changes of
+ * this.
+ */
+ public ITerminalTextDataSnapshot makeSnapshot();
+
+ char[] getChars(int line);
+
+ /**
+ * @since 5.0
+ */
+ TerminalStyle[] getStyles(int line);
+
+ /**
+ * @return the line in which the cursor is at the moment
+ */
+ int getCursorLine();
+
+ /**
+ * @return the column at which the cursor is at the moment
+ */
+ int getCursorColumn();
+
+ /**
+ * @param line
+ * @return true if this line got wrapped, ie. logically continues on next line
+ * @since 3.3
+ */
+ boolean isWrappedLine(int line);
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/ITerminalTextDataSnapshot.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/ITerminalTextDataSnapshot.java
new file mode 100644
index 00000000000..24752f8fc32
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/ITerminalTextDataSnapshot.java
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ * Martin Oberhuber (Wind River) - [261486][api][cleanup] Mark @noimplement interfaces as @noextend
+ *******************************************************************************/
+package org.eclipse.tm.terminal.model;
+
+/**
+ * This class maintains a snapshot of an instance of {@link ITerminalTextData}.
+ * While the {@link ITerminalTextData} continues changing, the snapshot remains
+ * unchanged until the next snapshot is taken by calling
+ * {@link #updateSnapshot(boolean)}. This is important, because the
+ * {@link ITerminalTextData} might get modified by another thread. Suppose you
+ * would want to draw the content of the {@link ITerminalTextData} using the
+ * following loop:
+ *
+ *
+ * for (int line = 0; line < term.getHeight(); line++)
+ * for (int column = 0; column < term.getWidth(); column++)
+ * drawCharacter(column, line, term.getChar(column, line), term.getStyle(column, line));
+ *
+ *
+ * This might fail because the background thread could change the dimensions of
+ * the {@link ITerminalTextData} while you iterate the loop. One solution would
+ * be to put a synchronized(term){} statement around the code. This
+ * has two problems: 1. you would have to know about the internals of the
+ * synchronisation of {@link ITerminalTextData}. 2. The other thread that
+ * changes {@link ITerminalTextData} is blocked while the potentially slow
+ * drawing is done.
+ *
+ * Solution: Take a snapshot of the terminal and use the snapshot to draw
+ * the content. There is no danger that the data structure get changed while you
+ * draw. There are also methods to find out what has changed to minimize the
+ * number of lines that get redrawn.
+ *
+ *
+ *
+ * Drawing optimization: To optimize redrawing of changed lines, this
+ * class keeps track of lines that have changed since the previous snapshot.
+ *
+ *
+ *
+ * // iterate over the potentially changed lines
+ * for (int line = snap.getFirstChangedLine(); line <= snap.getLastChangedLine(); line++)
+ * // redraw only if the line has changed
+ * if (snap.hasLineChanged(line))
+ * for (int column = 0; column < snap.getWidth(); column++)
+ * drawCharacter(column, line, snap.getChar(column, line), snap.getStyle(column, line));
+ *
+ *
+ *
+ * Scroll optimization: Often new lines are appended at the bottom of the
+ * terminal and the rest of the lines are scrolled up. In this case all lines
+ * would be marked as changed. To optimize for this case,
+ * {@link #updateSnapshot(boolean)} can be called with true for the
+ * detectScrolling parameter. The object will keep track of
+ * scrolling. The UI must first handle the scrolling and then use the
+ * {@link #hasLineChanged(int)} method to determine scrolling:
+ *
+ *
+ * // scroll the visible region of the UI <b>before</b> drawing the changed lines.
+ * doUIScrolling(snap.getScrollChangeY(), snap.getScrollChangeN(), snap.getScrollChangeShift());
+ * // iterate over the potentially changed lines
+ * for (int line = snap.getFirstChangedLine(); line <= snap.getFirstChangedLine(); line++)
+ * // redraw only if the line has changed
+ * if (snap.hasLineChanged(line))
+ * for (int column = 0; column < snap.getWidth(); column++)
+ * drawCharacter(column, line, snap.getChar(column, line), snap.getStyle(column, line));
+ *
+ *
+ *
+ *
+ * Threading Note: This class is not thread safe! All methods have to be
+ * called by the a same thread, that created the instance by calling
+ * {@link ITerminalTextDataReadOnly#makeSnapshot()}.
+ *
+ *
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ */
+public interface ITerminalTextDataSnapshot extends ITerminalTextDataReadOnly {
+ /**
+ * This listener gets called when the current snapshot
+ * is out of date. Calling {@link ITerminalTextDataSnapshot#updateSnapshot(boolean)}
+ * will have an effect. Once the {@link #snapshotOutOfDate(ITerminalTextDataSnapshot)} method is called,
+ * it will not be called until {@link ITerminalTextDataSnapshot#updateSnapshot(boolean)}
+ * is called and a new snapshot needs to be updated again.
+ *
+ * A typical terminal view would not update the snapshot immediately
+ * after the {@link #snapshotOutOfDate(ITerminalTextDataSnapshot)} has been called. It would introduce a
+ * delay to update the UI (and the snapshot} 10 or 20 times per second.
+ *
+ *
Make sure you don't spend too much time in this method.
+ */
+ interface SnapshotOutOfDateListener {
+ /**
+ * Gets called when the snapshot is out of date. To get the snapshot up to date,
+ * call {@link ITerminalTextDataSnapshot#updateSnapshot(boolean)}.
+ * @param snapshot The snapshot that is out of date
+ */
+ void snapshotOutOfDate(ITerminalTextDataSnapshot snapshot);
+ }
+
+ void addListener(SnapshotOutOfDateListener listener);
+
+ void removeListener(SnapshotOutOfDateListener listener);
+
+ /**
+ * Ends the listening to the {@link ITerminalTextData}. After this
+ * has been called no new snapshot data is collected.
+ */
+ void detach();
+
+ /**
+ * @return true if the data has changed since the previous snapshot.
+ */
+ boolean isOutOfDate();
+
+ /**
+ * The window of interest is the region the snapshot should track.
+ * Changes outside this region are ignored. The change takes effect after
+ * an update!
+ * @param startLine -1 means track the end of the data
+ * @param size number of lines to track. A size of -1 means track all.
+ */
+ void setInterestWindow(int startLine, int size);
+
+ int getInterestWindowStartLine();
+
+ int getInterestWindowSize();
+
+ /**
+ * Create a new snapshot of the {@link ITerminalTextData}. It will efficiently
+ * copy the data of the {@link ITerminalTextData} into an internal representation.
+ * The snapshot also keeps track of the changes since the previous snapshot.
+ *
With the methods {@link #getFirstChangedLine()}, {@link #getLastChangedLine()} and
+ * {@link #hasLineChanged(int)}
+ * you can find out what has changed in the current snapshot since the previous snapshot.
+ * @param detectScrolling if true the snapshot tries to identify scroll
+ * changes since the last snapshot. In this case the information about scrolling
+ * can be retrieved using the following methods:
+ * {@link #getScrollWindowStartLine()}, {@link #getScrollWindowSize()} and {@link #getScrollWindowShift()}
+ *
Note: The method {@link #hasLineChanged(int)} returns changes after the
+ * scrolling has been applied.
+ */
+ void updateSnapshot(boolean detectScrolling);
+
+ /**
+ * @return The first line changed in this snapshot compared
+ * to the previous snapshot.
+ *
+ *
Note: If no line has changed, this
+ * returns {@link Integer#MAX_VALUE}
+ *
+ *
Note: if {@link #updateSnapshot(boolean)} has been called with true,
+ * then this does not include lines that only have been scrolled. This is the
+ * first line that has changed after the scroll has been applied.
+ */
+ int getFirstChangedLine();
+
+ /**
+ * @return The last line changed in this snapshot compared
+ * to the previous snapshot. If the height has changed since the
+ * last update of the snapshot, then the returned value is within
+ * the new dimensions.
+ *
+ *
Note: If no line has changed, this returns -1
+ *
+ *
Note: if {@link #updateSnapshot(boolean)} has been called with true,
+ * then this does not include lines that only have been scrolled. This is the
+ * last line that has changed after the scroll has been applied.
+ *
+ *
A typical for loop using this method would look like this (note the <= in the for loop):
+ *
+ * for(int line=snap.{@link #getFirstChangedLine()}; line <= snap.getLastChangedLine(); line++)
+ * if(snap.{@link #hasLineChanged(int) hasLineChanged(line)})
+ * doSomething(line);
+ *
+ */
+ int getLastChangedLine();
+
+ /**
+ * @param line
+ * @return true if the line has changed since the previous snapshot
+ */
+ boolean hasLineChanged(int line);
+
+ boolean hasDimensionsChanged();
+
+ /**
+ * @return true if the terminal has changed (and not just the
+ * window of interest)
+ */
+ boolean hasTerminalChanged();
+
+ /**
+ * If {@link #updateSnapshot(boolean)} was called with true, then this method
+ * returns the top of the scroll region.
+ * @return The first line scrolled in this snapshot compared
+ * to the previous snapshot. See also {@link ITerminalTextData#scroll(int, int, int)}.
+ */
+ int getScrollWindowStartLine();
+
+ /**
+ * If {@link #updateSnapshot(boolean)} was called with true, then this method
+ * returns the size of the scroll region.
+ * @return The number of lines scrolled in this snapshot compared
+ * to the previous snapshot. See also {@link ITerminalTextData#scroll(int, int, int)}
+ * If nothing has changed, 0 is returned.
+ */
+ int getScrollWindowSize();
+
+ /**
+ * If {@link #updateSnapshot(boolean)} was called with true, then this method
+ * returns number of lines moved by the scroll region.
+ * @return The the scroll shift of this snapshot compared
+ * to the previous snapshot. See also {@link ITerminalTextData#scroll(int, int, int)}
+ */
+ int getScrollWindowShift();
+
+ /**
+ * @return The {@link ITerminalTextData} on that this instance is observing.
+ */
+ ITerminalTextData getTerminalTextData();
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/LineSegment.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/LineSegment.java
new file mode 100644
index 00000000000..548dc705891
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/LineSegment.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tm.terminal.model;
+
+public class LineSegment {
+ private final String fText;
+ private final int fCol;
+ private final TerminalStyle fStyle;
+
+ /**
+ * @since 5.0
+ */
+ public LineSegment(int col, String text, TerminalStyle style) {
+ fCol = col;
+ fText = text;
+ fStyle = style;
+ }
+
+ /**
+ * @since 5.0
+ */
+ public TerminalStyle getStyle() {
+ return fStyle;
+ }
+
+ public String getText() {
+ return fText;
+ }
+
+ public int getColumn() {
+ return fCol;
+ }
+
+ @Override
+ public String toString() {
+ return "LineSegment(" + fCol + ", \"" + fText + "\"," + fStyle + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+}
\ No newline at end of file
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TerminalColor.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TerminalColor.java
new file mode 100644
index 00000000000..0bc12ebabdb
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TerminalColor.java
@@ -0,0 +1,203 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Kichwa Coders Canada Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.tm.terminal.model;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jface.resource.ColorDescriptor;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Colors that can be used in the Terminal are represented by this class. The enum contains
+ * the colors with well known names defined by the ANSI Escape Sequences, plus other colors needed
+ * to render a display (such as Background color).
+ *
+ * Rather than name all the colors when using ANSI 8-bit indexed colors, the indexed colors
+ * can be accessed via the {@link #getIndexedRGBColor(int)} or {@link #getIndexedRGBColor(int)}
+ * (use {@link #isIndexedTerminalColor(int)} to determine which one is appropriate.
+ *
+ * The {@link TerminalStyle} supports any arbitrary color by using {@link RGB} defined colors.
+ * This class provides the connection between the names exposed to the user in preferences
+ * and their use in the terminal, along with how colors change when other attributes (such as
+ * bright and invertColors) are applied to them.
+ *
+ * @since 5.0
+ */
+public enum TerminalColor {
+ BLACK, //
+ RED, //
+ GREEN, //
+ YELLOW, //
+ BLUE, //
+ MAGENTA, //
+ CYAN, //
+ WHITE, //
+
+ BRIGHT_BLACK, //
+ BRIGHT_RED, //
+ BRIGHT_GREEN, //
+ BRIGHT_YELLOW, //
+ BRIGHT_BLUE, //
+ BRIGHT_MAGENTA, //
+ BRIGHT_CYAN, //
+ BRIGHT_WHITE, //
+
+ FOREGROUND, //
+ BACKGROUND, //
+ SELECTION_FOREGROUND, //
+ SELECTION_BACKGROUND;
+
+ /**
+ * The first 16-items in the 8-bit lookup table map to the user changeable colors
+ * above, so this array handles that mapping.
+ */
+ private final static TerminalColor table8bitIndexedTerminalColors[] = new TerminalColor[16];
+
+ /**
+ * The rest of the colors in the lookup table (240 colors) are pre-defined by
+ * the standard. The colors that fill this table were derived from
+ * https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit which was more
+ * digestible and accessible than the underlying ITU and ISO standards.
+ */
+ private final static RGB table8bitIndexedRGB[] = new RGB[256 - 16];
+
+ /**
+ * Color to use instead when inverted color is selected
+ */
+ private TerminalColor invertColor;
+
+ /**
+ * Color to use instead when bright color is selected
+ */
+ private TerminalColor brightColor;
+
+ /**
+ * Pre-calculate the lookup tables for 8-bit colors, inverses and equivalent brights.
+ */
+ static {
+ TerminalColor[] values = TerminalColor.values();
+
+ // 8-bit color lookup tables
+ {
+ int index = 0;
+ for (; index < 16; index++) {
+ TerminalColor c = values[index];
+ table8bitIndexedTerminalColors[index] = c;
+ }
+
+ int vals[] = { 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff };
+ Assert.isTrue(index == 16);
+ for (int r = 0; r < 6; r++) {
+ for (int g = 0; g < 6; g++) {
+ for (int b = 0; b < 6; b++) {
+ table8bitIndexedRGB[index++ - 16] = new RGB(vals[r], vals[g], vals[b]);
+ }
+ }
+ }
+
+ int greys[] = { 0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62, 0x6c, 0x76, 0x80, 0x8a, 0x94,
+ 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee };
+
+ Assert.isTrue(index == 232);
+ for (int g : greys) {
+ table8bitIndexedRGB[index++ - 16] = new RGB(g, g, g);
+ }
+ Assert.isTrue(index == 256);
+ }
+
+ // bright equivalents
+ {
+ // The second set of 8 colors are the bright of the first 8.
+ for (int i = 0; i < 8; i++) {
+ values[i].brightColor = values[i + 8];
+ }
+ // The rest of the colors are not brightened
+ for (int i = 8; i < values.length; i++) {
+ values[i].brightColor = values[i];
+ }
+ }
+
+ // inverses
+ {
+ // by default make all colors invert of themself
+ for (int i = 0; i < values.length; i++) {
+ values[i].invertColor = values[i];
+ }
+ // and then mark the colors that are actual inverts
+ inverts(BLACK, WHITE);
+ inverts(BRIGHT_BLACK, BRIGHT_WHITE);
+ inverts(BACKGROUND, FOREGROUND);
+ }
+
+ }
+
+ private static void inverts(TerminalColor a, TerminalColor b) {
+ a.invertColor = b;
+ b.invertColor = a;
+ }
+
+ /**
+ * Return a new color for the given color with inversions or brightness attributes applied.
+ *
+ * @param invert For invertible colors, return the inverse (typically white <-> black)
+ * @param bright returns the brighter version of the color if one is available
+ * @return {@link ColorDescriptor} that a {@link Color} can be made from
+ * using {@link ColorDescriptor#createColor(org.eclipse.swt.graphics.Device)}
+ * @throws NullPointerException if there is no current {@link Display}
+ */
+ public TerminalColor convertColor(boolean invert, boolean bright) {
+ TerminalColor selected = this;
+ // it doesn't matter which order you apply bright and invert, you get to
+ // the same color when both are set
+ if (invert) {
+ selected = selected.invertColor;
+ }
+ if (bright) {
+ selected = selected.brightColor;
+ }
+ return selected;
+ }
+
+ /**
+ * Query for whether the 8-bit color index will return a named color, in which case
+ * {@link #getIndexedTerminalColor(int)} must be called to get the named color. Use
+ * {@link #convertColor(boolean, boolean)} if this method returns false.
+ *
+ * @param index 8-bit index.
+ * @return true for named colors, false for RGB colors
+ */
+ public static boolean isIndexedTerminalColor(int index) {
+ Assert.isLegal(index >= 0 && index < 256, "Invalid 8-bit table index out of range 0-255"); //$NON-NLS-1$
+ return index < table8bitIndexedTerminalColors.length && index >= 0;
+ }
+
+ /**
+ * Return the named color for the given 8-bit index.
+ *
+ * @param index 8-bit index in 0-15 range.
+ * @return named color
+ */
+ public static TerminalColor getIndexedTerminalColor(int index) {
+ Assert.isLegal(isIndexedTerminalColor(index), "Invalid table index used for ANSI Color"); //$NON-NLS-1$
+ return table8bitIndexedTerminalColors[index];
+ }
+
+ /**
+ * Return the RGB color for the given 8-bit index.
+ *
+ * @param index 8-bit index in 16-255 range.
+ * @return RGB color
+ */
+ public static RGB getIndexedRGBColor(int index) {
+ Assert.isLegal(index >= 16 && index < 256, "Invalid table index used for RGB Color"); //$NON-NLS-1$
+ return table8bitIndexedRGB[index - 16];
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TerminalStyle.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TerminalStyle.java
new file mode 100644
index 00000000000..d81d4b8466e
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TerminalStyle.java
@@ -0,0 +1,285 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2020 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tm.terminal.model;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin;
+import org.eclipse.tm.internal.terminal.provisional.api.Logger;
+
+/**
+ * @author scharf
+ * Flyweight
+ * Threadsafe.
+ * @since 5.0
+ *
+ */
+// TODO add an Object for user data, use weak map to keep track of styles with associated
+// user data
+public class TerminalStyle {
+ private final TerminalColor fForegroundTerminalColor;
+ private final TerminalColor fBackgroundTerminalColor;
+ private final RGB fForegroundRGB;
+ private final RGB fBackgroundRGB;
+ private final boolean fBold;
+ private final boolean fBlink;
+ private final boolean fUnderline;
+ private final boolean fReverse;
+ private final static Map fgStyles = Collections
+ .synchronizedMap(new LinkedHashMap() {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ int size = size();
+ boolean removeEldest = size >= 1000;
+ if (TerminalPlugin.isOptionEnabled(Logger.TRACE_DEBUG_LOG_VT100BACKEND)) {
+ if (removeEldest) {
+ Logger.log("Removing eldest Style from style cache, size = " + size); //$NON-NLS-1$
+ } else {
+ Logger.log("Leaving eldest Style in style cache, size = " + size); //$NON-NLS-1$
+ }
+ }
+ return removeEldest;
+ }
+ });
+
+ private TerminalStyle(TerminalColor foregroundTerminalColor, TerminalColor backgroundTerminalColor,
+ RGB foregroundRGB, RGB backgroundRGB, boolean bold, boolean blink, boolean underline, boolean reverse) {
+ Assert.isLegal(foregroundTerminalColor == null || foregroundRGB == null,
+ "Only one of ANSI or RGB colors can be specified as a foreground color"); //$NON-NLS-1$
+ Assert.isLegal(backgroundTerminalColor == null || backgroundRGB == null,
+ "Only one of ANSI or RGB colors can be specified as a background color"); //$NON-NLS-1$
+ fForegroundTerminalColor = foregroundTerminalColor;
+ fBackgroundTerminalColor = backgroundTerminalColor;
+ fForegroundRGB = foregroundRGB;
+ fBackgroundRGB = backgroundRGB;
+ fBold = bold;
+ fBlink = blink;
+ fUnderline = underline;
+ fReverse = reverse;
+ }
+
+ public static TerminalStyle getStyle(TerminalColor foregroundTerminalColor, TerminalColor backgroundTerminalColor,
+ RGB foregroundRGB, RGB backgroundRGB, boolean bold, boolean blink, boolean underline, boolean reverse) {
+ TerminalStyle style = new TerminalStyle(foregroundTerminalColor, backgroundTerminalColor, foregroundRGB,
+ backgroundRGB, bold, blink, underline, reverse);
+ // If set had a computeIfAbsent we would use a set, instead just store 1-2-1 mapping
+ return fgStyles.computeIfAbsent(style, (s) -> style);
+ }
+
+ public static TerminalStyle getStyle(TerminalColor foregroundTerminalColor, TerminalColor backgroundTerminalColor,
+ boolean bold, boolean blink, boolean underline, boolean reverse) {
+ return getStyle(foregroundTerminalColor, backgroundTerminalColor, null, null, bold, blink, underline, reverse);
+ }
+
+ public static TerminalStyle getStyle(RGB foregroundRGB, RGB backgroundRGB, boolean bold, boolean blink,
+ boolean underline, boolean reverse) {
+ return getStyle(null, null, foregroundRGB, backgroundRGB, bold, blink, underline, reverse);
+ }
+
+ public static TerminalStyle getDefaultStyle() {
+ return getStyle(TerminalColor.FOREGROUND, TerminalColor.BACKGROUND);
+ }
+
+ public static TerminalStyle getStyle(TerminalColor foregroundTerminalColor, TerminalColor backgroundTerminalColor) {
+ return getStyle(foregroundTerminalColor, backgroundTerminalColor, null, null, false, false, false, false);
+ }
+
+ public TerminalStyle setForeground(TerminalColor foregroundTerminalColor) {
+ return getStyle(foregroundTerminalColor, fBackgroundTerminalColor, null, fBackgroundRGB, fBold, fBlink,
+ fUnderline, fReverse);
+ }
+
+ public TerminalStyle setBackground(TerminalColor backgroundTerminalColor) {
+ return getStyle(fForegroundTerminalColor, backgroundTerminalColor, fForegroundRGB, null, fBold, fBlink,
+ fUnderline, fReverse);
+ }
+
+ public TerminalStyle setForeground(RGB foregroundRGB) {
+ return getStyle(null, fBackgroundTerminalColor, foregroundRGB, fBackgroundRGB, fBold, fBlink, fUnderline,
+ fReverse);
+ }
+
+ public TerminalStyle setBackground(RGB backgroundRGB) {
+ return getStyle(fForegroundTerminalColor, null, fForegroundRGB, backgroundRGB, fBold, fBlink, fUnderline,
+ fReverse);
+ }
+
+ public TerminalStyle setForeground(TerminalStyle other) {
+ return getStyle(other.fForegroundTerminalColor, fBackgroundTerminalColor, other.fForegroundRGB, fBackgroundRGB,
+ fBold, fBlink, fUnderline, fReverse);
+ }
+
+ public TerminalStyle setBackground(TerminalStyle other) {
+ return getStyle(fForegroundTerminalColor, other.fBackgroundTerminalColor, fForegroundRGB, other.fBackgroundRGB,
+ fBold, fBlink, fUnderline, fReverse);
+ }
+
+ public TerminalStyle setForeground(int eightBitindexedColor) {
+ boolean isIndexTerminalColor = TerminalColor.isIndexedTerminalColor(eightBitindexedColor);
+ if (isIndexTerminalColor) {
+ TerminalColor foregroundTerminalColor = TerminalColor.getIndexedTerminalColor(eightBitindexedColor);
+ return getStyle(foregroundTerminalColor, fBackgroundTerminalColor, null, fBackgroundRGB, fBold, fBlink,
+ fUnderline, fReverse);
+ } else {
+ RGB foregroundRGB = TerminalColor.getIndexedRGBColor(eightBitindexedColor);
+ return getStyle(null, fBackgroundTerminalColor, foregroundRGB, fBackgroundRGB, fBold, fBlink, fUnderline,
+ fReverse);
+ }
+ }
+
+ public TerminalStyle setBackground(int eightBitindexedColor) {
+ boolean isIndexTerminalColor = TerminalColor.isIndexedTerminalColor(eightBitindexedColor);
+ if (isIndexTerminalColor) {
+ TerminalColor backgroundTerminalColor = TerminalColor.getIndexedTerminalColor(eightBitindexedColor);
+ return getStyle(fForegroundTerminalColor, backgroundTerminalColor, fForegroundRGB, null, fBold, fBlink,
+ fUnderline, fReverse);
+ } else {
+ RGB backgroundRGB = TerminalColor.getIndexedRGBColor(eightBitindexedColor);
+ return getStyle(fForegroundTerminalColor, null, fForegroundRGB, backgroundRGB, fBold, fBlink, fUnderline,
+ fReverse);
+ }
+ }
+
+ public TerminalStyle setBold(boolean bold) {
+ return getStyle(fForegroundTerminalColor, fBackgroundTerminalColor, fForegroundRGB, fBackgroundRGB, bold,
+ fBlink, fUnderline, fReverse);
+ }
+
+ public TerminalStyle setBlink(boolean blink) {
+ return getStyle(fForegroundTerminalColor, fBackgroundTerminalColor, fForegroundRGB, fBackgroundRGB, fBold,
+ blink, fUnderline, fReverse);
+ }
+
+ public TerminalStyle setUnderline(boolean underline) {
+ return getStyle(fForegroundTerminalColor, fBackgroundTerminalColor, fForegroundRGB, fBackgroundRGB, fBold,
+ fBlink, underline, fReverse);
+ }
+
+ public TerminalStyle setReverse(boolean reverse) {
+ return getStyle(fForegroundTerminalColor, fBackgroundTerminalColor, fForegroundRGB, fBackgroundRGB, fBold,
+ fBlink, fUnderline, reverse);
+ }
+
+ public TerminalColor getForegroundTerminalColor() {
+ return fForegroundTerminalColor;
+ }
+
+ public TerminalColor getBackgroundTerminalColor() {
+ return fBackgroundTerminalColor;
+ }
+
+ public RGB getForegroundRGB() {
+ return fForegroundRGB;
+ }
+
+ public RGB getBackgroundRGB() {
+ return fBackgroundRGB;
+ }
+
+ public boolean isBlink() {
+ return fBlink;
+ }
+
+ public boolean isBold() {
+ return fBold;
+ }
+
+ public boolean isReverse() {
+ return fReverse;
+ }
+
+ public boolean isUnderline() {
+ return fUnderline;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((fBackgroundTerminalColor == null) ? 0 : fBackgroundTerminalColor.hashCode());
+ result = prime * result + ((fBackgroundRGB == null) ? 0 : fBackgroundRGB.hashCode());
+ result = prime * result + (fBlink ? 1231 : 1237);
+ result = prime * result + (fBold ? 1231 : 1237);
+ result = prime * result + ((fForegroundTerminalColor == null) ? 0 : fForegroundTerminalColor.hashCode());
+ result = prime * result + ((fForegroundRGB == null) ? 0 : fForegroundRGB.hashCode());
+ result = prime * result + (fReverse ? 1231 : 1237);
+ result = prime * result + (fUnderline ? 1231 : 1237);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ TerminalStyle other = (TerminalStyle) obj;
+ if (fBackgroundTerminalColor != other.fBackgroundTerminalColor)
+ return false;
+ if (fBackgroundRGB == null) {
+ if (other.fBackgroundRGB != null)
+ return false;
+ } else if (!fBackgroundRGB.equals(other.fBackgroundRGB))
+ return false;
+ if (fBlink != other.fBlink)
+ return false;
+ if (fBold != other.fBold)
+ return false;
+ if (fForegroundTerminalColor != other.fForegroundTerminalColor)
+ return false;
+ if (fForegroundRGB == null) {
+ if (other.fForegroundRGB != null)
+ return false;
+ } else if (!fForegroundRGB.equals(other.fForegroundRGB))
+ return false;
+ if (fReverse != other.fReverse)
+ return false;
+ if (fUnderline != other.fUnderline)
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer result = new StringBuffer();
+ result.append("Style(foreground="); //$NON-NLS-1$
+ if (fForegroundTerminalColor != null) {
+ result.append(fForegroundTerminalColor);
+ } else {
+ result.append(fForegroundRGB);
+ }
+ result.append(", background="); //$NON-NLS-1$
+ if (fForegroundTerminalColor != null) {
+ result.append(fBackgroundTerminalColor);
+ } else {
+ result.append(fBackgroundRGB);
+ }
+ if (fBlink)
+ result.append(", blink"); //$NON-NLS-1$
+ if (fBold)
+ result.append(", bold"); //$NON-NLS-1$
+ if (fBlink)
+ result.append(", blink"); //$NON-NLS-1$
+ if (fReverse)
+ result.append(", reverse"); //$NON-NLS-1$
+ if (fUnderline)
+ result.append(", underline"); //$NON-NLS-1$
+ result.append(")"); //$NON-NLS-1$
+ return result.toString();
+ }
+
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TerminalTextDataFactory.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TerminalTextDataFactory.java
new file mode 100644
index 00000000000..25729aa3426
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TerminalTextDataFactory.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ * Michael Scharf (Wind River) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tm.terminal.model;
+
+import org.eclipse.tm.internal.terminal.model.SynchronizedTerminalTextData;
+import org.eclipse.tm.internal.terminal.model.TerminalTextData;
+
+public class TerminalTextDataFactory {
+ static public ITerminalTextData makeTerminalTextData() {
+ return new SynchronizedTerminalTextData(new TerminalTextData());
+ }
+}
diff --git a/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TextRange.java b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TextRange.java
new file mode 100644
index 00000000000..b73576f4c3c
--- /dev/null
+++ b/terminal/plugins/org.eclipse.tm.terminal.control/src/org/eclipse/tm/terminal/model/TextRange.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Fabrizio Iannetti.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+package org.eclipse.tm.terminal.model;
+
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Represents a range of text in the terminal.
+ *
+ * Used, for example, to store location of active hover
+ *
+ * @since 5.2
+ */
+public final class TextRange {
+ public final int colStart;
+ public final int colEnd;
+ public final int rowStart;
+ public final int rowEnd;
+ public final String text;
+
+ public static final TextRange EMPTY = new TextRange(0, 0, 0, 0, ""); //$NON-NLS-1$
+
+ /**
+ * Constructor.
+ *
+ * @param rowStart start row
+ * @param rowEnd end row
+ * @param colStart start column (exclusive)
+ * @param colEnd end column (exclusive)
+ * @param text text in the range
+ */
+ public TextRange(int rowStart, int rowEnd, int colStart, int colEnd, String text) {
+ super();
+ this.colStart = colStart;
+ this.colEnd = colEnd;
+ this.rowStart = rowStart;
+ this.rowEnd = rowEnd;
+ this.text = text;
+ }
+
+ public boolean contains(int col, int row) {
+ int colStartInrow = row == rowStart ? colStart : 0;
+ int colEndInRow = row == rowEnd - 1 ? colEnd : col + 1;
+ return col >= colStartInrow && col < colEndInRow && row >= rowStart && row < rowEnd;
+ }
+
+ public boolean contains(int line) {
+ return line >= rowStart && line < rowEnd;
+ }
+
+ /**
+ * Whether the range represents a non-empty (non-zero) amount of text
+ */
+ public boolean isEmpty() {
+ return !(colEnd > colStart || rowEnd > rowStart);
+ }
+
+ public Point getStart() {
+ return new Point(colStart, rowStart);
+ }
+
+ public Point getEnd() {
+ return new Point(colEnd, rowEnd);
+ }
+
+ public int getColStart() {
+ return colStart;
+ }
+
+ public int getColEnd() {
+ return colEnd;
+ }
+
+ public int getRowStart() {
+ return rowStart;
+ }
+
+ public int getRowEnd() {
+ return rowEnd;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextRange (%s,%s)-(%s,%s)-'%s'", //$NON-NLS-1$
+ colStart, rowStart, colEnd, rowEnd, text);
+ }
+}
diff --git a/terminal/pom.xml b/terminal/pom.xml
new file mode 100644
index 00000000000..61ee14a8c4a
--- /dev/null
+++ b/terminal/pom.xml
@@ -0,0 +1,28 @@
+
+
+
+ 4.0.0
+
+
+ org.eclipse.platform
+ eclipse.platform
+ 4.36.0-SNAPSHOT
+
+
+ eclipse.platform.terminal
+ pom
+
+
+ plugins
+
+
+