Bug 458402 - [terminal] Add support for scroll up/down and scroll region

Change-Id: If3c955663f664d34d01ada0763de2eec7b36b7d4
Signed-off-by: Anton Leherbauer <anton.leherbauer@windriver.com>
diff --git a/terminal/plugins/org.eclipse.tm.terminal.test/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackendTest.java b/terminal/plugins/org.eclipse.tm.terminal.test/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackendTest.java
index 9ed477f..0a65fd7 100644
--- a/terminal/plugins/org.eclipse.tm.terminal.test/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackendTest.java
+++ b/terminal/plugins/org.eclipse.tm.terminal.test/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackendTest.java
@@ -10,6 +10,7 @@
  * 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
  * 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;
 
@@ -1307,4 +1308,74 @@
 		assertEquals("abc123", new String(term.getChars(0)));
 	}
 
+	public void testScrollRegion() {
+		ITerminalTextData term=makeITerminalTextData();
+		IVT100EmulatorBackend vt100=makeBakend(term);
+		term.setMaxHeight(10);
+		vt100.setDimensions(8, 6);
+		vt100.appendString("123");
+		vt100.setCursorColumn(0);
+		vt100.processNewline();
+		vt100.appendString("456");
+		vt100.setCursorColumn(0);
+		vt100.processNewline();
+		vt100.appendString("789");
+		vt100.setCursorColumn(0);
+		vt100.processNewline();
+		vt100.appendString("abc");
+		vt100.setCursorColumn(0);
+		vt100.processNewline();
+		vt100.appendString("def");
+		vt100.setCursorColumn(0);
+		vt100.processNewline();
+		vt100.appendString("ghi");
+		
+		// test scroll within region
+		vt100.setCursorLine(1);
+		vt100.setScrollRegion(1, 4);
+		vt100.scrollUp(1);
+		assertEquals("123", new String(term.getChars(0)));
+		assertEquals("789", new String(term.getChars(1)));
+		assertEquals("abc", new String(term.getChars(2)));
+		assertEquals("def", new String(term.getChars(3)));
+		assertNull(term.getChars(4));
+		assertEquals("ghi", new String(term.getChars(5)));
+		vt100.scrollDown(1);
+		assertEquals("123", new String(term.getChars(0)));
+		assertNull(term.getChars(1));
+		assertEquals("789", new String(term.getChars(2)));
+		assertEquals("abc", new String(term.getChars(3)));
+		assertEquals("def", new String(term.getChars(4)));
+		assertEquals("ghi", new String(term.getChars(5)));
+
+		// test scroll without region
+		vt100.setScrollRegion(-1, -1);
+		vt100.scrollDown(1);
+		assertNull(term.getChars(0));
+		assertEquals("123", new String(term.getChars(1)));
+		assertNull(term.getChars(2));
+		assertEquals("789", new String(term.getChars(3)));
+		assertEquals("abc", new String(term.getChars(4)));
+		assertEquals("def", new String(term.getChars(5)));
+		assertEquals("ghi", new String(term.getChars(6)));
+		vt100.scrollUp(1);
+		assertEquals("123", new String(term.getChars(0)));
+		assertNull(term.getChars(1));
+		assertEquals("789", new String(term.getChars(2)));
+		assertEquals("abc", new String(term.getChars(3)));
+		assertEquals("def", new String(term.getChars(4)));
+		assertEquals("ghi", new String(term.getChars(5)));
+		
+		// test scroll by newline
+		vt100.setScrollRegion(1, 4);
+		vt100.setCursorLine(4);
+		vt100.processNewline();
+		assertEquals("123", new String(term.getChars(0)));
+		assertEquals("789", new String(term.getChars(1)));
+		assertEquals("abc", new String(term.getChars(2)));
+		assertEquals("def", new String(term.getChars(3)));
+		assertNull(term.getChars(4));
+		assertEquals("ghi", new String(term.getChars(5)));
+	}
+
 }
diff --git a/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/IVT100EmulatorBackend.java b/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/IVT100EmulatorBackend.java
index ef38f82..aff9089 100644
--- a/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/IVT100EmulatorBackend.java
+++ b/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/IVT100EmulatorBackend.java
@@ -9,13 +9,13 @@
  * 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.Style;
 
 /**
- * @author toni
  *
  */
 public interface IVT100EmulatorBackend {
@@ -197,4 +197,26 @@
 	 * @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);
 }
diff --git a/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100BackendTraceDecorator.java b/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100BackendTraceDecorator.java
index c108986..7853143 100644
--- a/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100BackendTraceDecorator.java
+++ b/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100BackendTraceDecorator.java
@@ -9,6 +9,7 @@
  * 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;
 
@@ -157,4 +158,19 @@
 		fBackend.setInsertMode(enable);
 	}
 
+	public void setScrollRegion(int top, int bottom) {
+		fWriter.println("setScrollRegion("+top+','+bottom+")"); //$NON-NLS-1$ //$NON-NLS-2$
+		fBackend.setScrollRegion(top, bottom);
+	}
+
+	public void scrollUp(int lines) {
+		fWriter.println("scrollUp("+lines+")"); //$NON-NLS-1$ //$NON-NLS-2$
+		fBackend.scrollUp(lines);
+	}
+
+	public void scrollDown(int lines) {
+		fWriter.println("scrollDown("+lines+")"); //$NON-NLS-1$ //$NON-NLS-2$
+		fBackend.scrollDown(lines);
+	}
+
 }
diff --git a/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100Emulator.java b/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100Emulator.java
index cc9e6cf..eba0339 100644
--- a/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100Emulator.java
+++ b/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100Emulator.java
@@ -23,6 +23,7 @@
  * 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;
 
@@ -77,6 +78,12 @@
 	 */
 	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;
+	
 
 	/**
 	 * This field holds the current state of the Finite TerminalState Automaton (FSA)
@@ -341,6 +348,16 @@
 					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
 
@@ -413,6 +430,12 @@
 				}
 				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
@@ -483,6 +506,11 @@
 			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();
@@ -548,16 +576,19 @@
 			processAnsiCommand_P();
 			break;
 
+		case 'r':
+			// Set Scrolling Region.
+			processAnsiCommand_r();
+			break;
+
 		case 'S':
 			// Scroll up.
-			// Emacs, vi, and GNU readline don't seem to use this command, so we ignore
-			// it for now.
+			processAnsiCommand_S();
 			break;
 
 		case 'T':
 			// Scroll down.
-			// Emacs, vi, and GNU readline don't seem to use this command, so we ignore
-			// it for now.
+			processAnsiCommand_T();
 			break;
 
 		case 'X':
@@ -647,6 +678,14 @@
 	}
 
 	/**
+	 * 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).
 	 */
@@ -967,17 +1006,63 @@
 		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));
+	}
+	
 	private void processDecPrivateCommand_h() {
-		if (getAnsiParameter(0) == 1) {
+		int param = getAnsiParameter(0);
+		switch (param) {
+		case 1:
 			// Enable Application Cursor Keys (DECCKM)
 			terminal.enableApplicationCursorKeys(true);
+			break;
+		case 47:
+			// Use Alternate Screen Buffer (ignored).
+			break;
+		default:
+			Logger.log("Unsupported command parameter: CSI ?" + param + 'h'); //$NON-NLS-1$
+			break;
 		}
 	}
 
 	private void processDecPrivateCommand_l() {
-		if (getAnsiParameter(0) == 1) {
+		int param = getAnsiParameter(0);
+		switch (param) {
+		case 1:
 			// Enable Normal Cursor Keys (DECCKM)
 			terminal.enableApplicationCursorKeys(false);
+			break;
+		case 47:
+			// 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;
 		}
 	}
 	
diff --git a/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackend.java b/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackend.java
index 2387d20..36140d6 100644
--- a/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackend.java
+++ b/terminal/plugins/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackend.java
@@ -10,6 +10,7 @@
  * 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;
 
@@ -21,6 +22,28 @@
  */
 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
@@ -62,6 +85,8 @@
 	int fColumns;
 	final private ITerminalTextData fTerminal;
 	private boolean fVT100LineWrapping;
+	private ScrollRegion fScrollRegion = ScrollRegion.FULL_WINDOW;
+
 	public VT100EmulatorBackend(ITerminalTextData terminal) {
 		fTerminal=terminal;
 	}
@@ -210,7 +235,7 @@
 				return;
 			assert n>0;
 			int line=toAbsoluteLine(fCursorLine);
-			int nLines=fTerminal.getHeight()-line;
+			int nLines=Math.min(fTerminal.getHeight()-line, fScrollRegion.getBottomLine()-fCursorLine+1);
 			fTerminal.scroll(line, nLines, n);
 		}
 	}
@@ -240,13 +265,12 @@
 				return;
 			assert n>0;
 			int line=toAbsoluteLine(fCursorLine);
-			int nLines=fTerminal.getHeight()-line;
+			int nLines=Math.min(fTerminal.getHeight()-line, fScrollRegion.getBottomLine()-fCursorLine+1);
 			fTerminal.scroll(line, nLines, -n);
 		}
 	}
 	private boolean isCusorInScrollingRegion() {
-		// TODO Auto-generated method stub
-		return true;
+		return fScrollRegion.contains(fCursorLine);
 	}
 
 	/* (non-Javadoc)
@@ -333,7 +357,9 @@
 	 * MUST be called from a synchronized block!
 	 */
 	private void doNewline() {
-		if(fCursorLine+1>=fLines) {
+		if (fCursorLine == fScrollRegion.getBottomLine())
+			scrollUp(1);
+		else if (fCursorLine+1>=fLines) {
 			int h=fTerminal.getHeight();
 			fTerminal.addLine();
 			if(h!=fTerminal.getHeight())
@@ -440,4 +466,29 @@
 	public void setInsertMode(boolean enable) {
 		fInsertMode = enable;
 	}
+
+	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);
+	}
+
+	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);
+		}
+	}
+
+	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);
+		}
+	}
 }