/***************************************************************************** * Copyright (c) 2006, 2007 g-Eclipse Consortium * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Initial development of the original code was made for the * g-Eclipse project founded by European Union * project number: FP6-IST-034327 http://www.geclipse.eu/ * * Contributors: * Thomas Koeckerbauer GUP, JKU - initial API and implementation *****************************************************************************/ package eu.geclipse.terminal.internal; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PushbackInputStream; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TreeSet; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; 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.MenuAdapter; import org.eclipse.swt.events.MenuEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import eu.geclipse.terminal.ITerminalListener; /** * VT100 terminal emulator widget. */ public class Terminal extends Canvas implements ISelectionProvider { private static final int BEL = 7; private static final int FF = 12; private static final int SO = 14; private static final int SI = 15; private static final int TAB_WIDTH = 8; OutputStream output; KeyPadMode keyPadMode; CursorKeyMode cursorKeyMode; ScrollMode scrollMode; boolean newLineMode; TerminalPainter terminalPainter; int topMargin; int bottomMargin; int historySize; private PushbackInputStream input; private Char[][] screenBuffer; private LineHeightMode[] lineHeightMode; private LineWidthMode[] lineWidthMode; private int fontHeight; private int fontWidth; private int numCols; private int numLines; private final boolean[] leds = new boolean[4]; private Cursor cursor; private Cursor savedCursor; private Color[] systemColors; private Color defaultBgColor; private Color defaultFgColor; private OriginMode originMode; private final CharSet[] charSetG = new CharSet[4]; private boolean reverseScreenMode; private String windowTitle; private final SortedSet<Integer> tabulatorPositons = new TreeSet<Integer>(); private List<ITerminalListener> terminalListeners; private boolean wraparound; private boolean reverseWraparound; private boolean running; private Clipboard clipboard; private List<ISelectionChangedListener> selectionListeners; private TerminalSelection terminalSelection; /** * Creates a new terminal widget. * * @param parent parent widget. * @param style widget style. * @param initFgColor default foreground color. * @param initBgColor default background color. * @param historySize size of history in lines. */ public Terminal(final Composite parent, final int style, final Color initFgColor, final Color initBgColor, final int historySize ) { super(parent, style | SWT.NO_BACKGROUND | SWT.V_SCROLL ); this.clipboard = new Clipboard( getDisplay() ); this.historySize = historySize; initMenu(); initSystemColorTable(); if ( initBgColor != null ) this.defaultBgColor = initBgColor; if ( initFgColor != null ) this.defaultFgColor = initFgColor; this.cursor = new Cursor( this.defaultFgColor, this.defaultBgColor ); this.savedCursor = new Cursor( this.defaultFgColor, this.defaultBgColor ); this.screenBuffer = new Char[0][]; this.terminalListeners = new LinkedList<ITerminalListener>(); this.selectionListeners = new LinkedList<ISelectionChangedListener>(); changeScreenSize(); this.terminalPainter = new TerminalPainter( this ); this.terminalSelection = new TerminalSelection( this ); addMouseListener( this.terminalSelection ); addMouseMoveListener( this.terminalSelection ); addPaintListener( this.terminalPainter ); addListener(SWT.Resize, new Listener() { public void handleEvent( final Event event ) { changeScreenSize(); } }); getVerticalBar().addSelectionListener( new SelectionAdapter() { int prevValue = -1; @Override public void widgetSelected( final SelectionEvent event ) { if (prevValue != getVerticalBar().getSelection()) { prevValue = getVerticalBar().getSelection(); triggerRedraw(); } } } ); addMouseListener( new MouseAdapter() { @Override public void mouseDown( final MouseEvent event ) { if ( event.button == 2 ) { paste(); } } } ); addListener(SWT.KeyDown, new Listener() { public void handleEvent( final Event event ) { // index 0 = keypad numeric mode, index 1 = keypad application mode // vt100 final byte[][] arrowUp = { { SWT.ESC, '[', 'A' }, { SWT.ESC, 'O', 'A' } }; final byte[][] arrowDown = { { SWT.ESC, '[', 'B' }, { SWT.ESC, 'O', 'B' } }; final byte[][] arrowRight = { { SWT.ESC, '[', 'C' }, { SWT.ESC, 'O', 'C' } }; final byte[][] arrowLeft = { { SWT.ESC, '[', 'D' }, { SWT.ESC, 'O', 'D' } }; // xterm final byte[] backspace = { SWT.DEL }; final byte[] home = { SWT.ESC, '[', 'H' }; final byte[] end = { SWT.ESC, '[', 'F' }; final byte[] insert = { SWT.ESC, '[', '2', '~' }; final byte[] delete = { SWT.ESC, '[', '3', '~' }; final byte[] pageUp = { SWT.ESC, '[', '5', '~' }; final byte[] pageDown = { SWT.ESC, '[', '6', '~' }; final byte[] F1 = { SWT.ESC, 'O', 'P' }; final byte[] F2 = { SWT.ESC, 'O', 'Q' }; final byte[] F3 = { SWT.ESC, 'O', 'R' }; final byte[] F4 = { SWT.ESC, 'O', 'S' }; final byte[] F5 = { SWT.ESC, '[', '1', '5', '~' }; final byte[] F6 = { SWT.ESC, '[', '1', '7', '~' }; final byte[] F7 = { SWT.ESC, '[', '1', '8', '~' }; final byte[] F8 = { SWT.ESC, '[', '1', '9', '~' }; final byte[] F9 = { SWT.ESC, '[', '2', '0', '~' }; final byte[] F10 = { SWT.ESC, '[', '2', '1', '~' }; final byte[] F11 = { SWT.ESC, '[', '2', '3', '~' }; final byte[] F12 = { SWT.ESC, '[', '2', '4', '~' }; final byte[] F13 = { SWT.ESC, '[', '2', '5', '~' }; final byte[] F14 = { SWT.ESC, '[', '2', '6', '~' }; final byte[] F15 = { SWT.ESC, '[', '2', '8', '~' }; final int[] unhandledKeycodes = { // sorted by keycode SWT.ALT, SWT.SHIFT, SWT.CTRL, SWT.CAPS_LOCK, SWT.NUM_LOCK, SWT.SCROLL_LOCK, SWT.PAUSE }; try { OutputStream out = Terminal.this.output; if ( out != null ) { if ( event.keyCode == SWT.ARROW_UP) { out.write( arrowUp[ Terminal.this.keyPadMode.getIndex() ] ); } else if ( event.keyCode == SWT.ARROW_DOWN ) { out.write( arrowDown[ Terminal.this.keyPadMode.getIndex() ] ); } else if ( event.keyCode == SWT.ARROW_RIGHT ) { out.write( arrowRight[ Terminal.this.keyPadMode.getIndex() ] ); } else if ( event.keyCode == SWT.ARROW_LEFT ) { out.write( arrowLeft[ Terminal.this.keyPadMode.getIndex() ] ); } else if ( event.keyCode == SWT.HOME ) out.write( home ); else if ( event.keyCode == SWT.END ) out.write( end ); else if ( event.keyCode == SWT.INSERT ) out.write( insert ); else if ( event.character == SWT.DEL ) out.write( delete ); else if ( event.character == SWT.BS ) out.write( backspace ); else if ( event.keyCode == SWT.PAGE_UP ) { if ( ( event.stateMask & SWT.SHIFT ) != 0 ) scrollHistoryPageUp(); else out.write( pageUp ); } else if ( event.keyCode == SWT.PAGE_DOWN ) { if ( ( event.stateMask & SWT.SHIFT ) != 0 ) scrollHistoryPageDown(); else out.write( pageDown ); } else if ( event.keyCode == SWT.F1 ) out.write( F1 ); else if ( event.keyCode == SWT.F2 ) out.write( F2 ); else if ( event.keyCode == SWT.F3 ) out.write( F3 ); else if ( event.keyCode == SWT.F4 ) out.write( F4 ); else if ( event.keyCode == SWT.F5 ) out.write( F5 ); else if ( event.keyCode == SWT.F6 ) out.write( F6 ); else if ( event.keyCode == SWT.F7 ) out.write( F7 ); else if ( event.keyCode == SWT.F8 ) out.write( F8 ); else if ( event.keyCode == SWT.F9 ) out.write( F9 ); else if ( event.keyCode == SWT.F10 ) out.write( F10 ); else if ( event.keyCode == SWT.F11 ) out.write( F11 ); else if ( event.keyCode == SWT.F12 ) out.write( F12 ); else if ( event.keyCode == SWT.F13 ) out.write( F13 ); else if ( event.keyCode == SWT.F14 ) out.write( F14 ); else if ( event.keyCode == SWT.F15 ) out.write( F15 ); else if ( event.character == SWT.CR ) { out.write( SWT.CR ); if ( Terminal.this.newLineMode ) out.write( SWT.LF ); } else if ( event.character != 0 ) out.write( event.character ); else if ( Arrays.binarySearch( unhandledKeycodes, event.keyCode ) < 0 ) { Activator.logMessage( IStatus.WARNING, Messages.formatMessage( "Terminal.unhandledKeycode", //$NON-NLS-1$ Integer.valueOf( event.keyCode ), Integer.valueOf( event.character) ) ); } out.flush(); } } catch( IOException ioException ) { Activator.logException( ioException ); } } } ); reset(); } void scrollHistoryPageUp() { int oldPos = getScrollbarPosLine(); int newPos = oldPos - this.numLines; if ( newPos < 0 ) newPos = 0; if ( newPos != oldPos ) { getVerticalBar().setSelection( newPos ); triggerRedraw(); } } void scrollHistoryPageDown() { int oldPos = getScrollbarPosLine(); int newPos = oldPos + this.numLines; if ( newPos > this.historySize ) newPos = this.historySize; if ( newPos != oldPos ) { getVerticalBar().setSelection( newPos ); triggerRedraw(); } } private void initMenu() { ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); Menu popUpMenu = new Menu( getShell(), SWT.POP_UP ); final MenuItem copyItem = new MenuItem( popUpMenu, SWT.PUSH ); copyItem.setText( Messages.getString("Terminal.copy") ); //$NON-NLS-1$ // TODO disable menu if selection is empty ImageDescriptor copyImage = sharedImages.getImageDescriptor( ISharedImages.IMG_TOOL_COPY ); copyItem.setImage( copyImage.createImage() ); copyItem.addSelectionListener( new SelectionAdapter() { @Override public void widgetSelected( final SelectionEvent event ) { copy(); } } ); MenuItem pasteItem = new MenuItem( popUpMenu, SWT.PUSH ); pasteItem.setText( Messages.getString( "Terminal.paste" ) ); //$NON-NLS-1$ ImageDescriptor pasteImage = sharedImages.getImageDescriptor( ISharedImages.IMG_TOOL_PASTE ); pasteItem.setImage( pasteImage.createImage() ); pasteItem.addSelectionListener( new SelectionAdapter() { @Override public void widgetSelected( final SelectionEvent event ) { paste(); } } ); popUpMenu.addMenuListener( new MenuAdapter() { @Override public void menuShown( final MenuEvent event ) { copyItem.setEnabled( !getSelection().isEmpty() ); } } ); setMenu( popUpMenu ); } /** * Triggers copy from the terminal into the clipboard. */ void copy() { String text = ((ITextSelection) getSelection()).getText(); TextTransfer plainTextTransfer = TextTransfer.getInstance(); this.clipboard.setContents( new String[] { text }, new Transfer[] { plainTextTransfer } ); } private Object getClipboardContent( final int clipboardType ) { TextTransfer plainTextTransfer = TextTransfer.getInstance(); return this.clipboard.getContents(plainTextTransfer, clipboardType); } /** * Triggers paste from the clipboard into the terminal. */ public void paste() { checkWidget(); String text = (String) getClipboardContent( DND.CLIPBOARD ); if ( text != null && text.length() > 0 ) { for ( int i = 0; i < text.length(); i++ ) { Event event = new Event(); event.character = text.charAt( i ); notifyListeners( SWT.KeyDown, event ); } } } /** * Registers a terminal listener for tracking terminal size and title changes. * @param termListener the listener. */ public void addTerminalListener( final ITerminalListener termListener ) { this.terminalListeners.add( termListener ); } /** * Removes a terminal listener for tracking terminal size and title changes. * @param termListener the listener. */ public void removeTerminalListener( final ITerminalListener termListener ) { this.terminalListeners.remove( termListener ); } private void bell() { getDisplay().syncExec(new Runnable() { public void run () { getDisplay().beep(); } }); // TODO visible bell? } private void carriageReturn() { if ( this.cursor.col != 0) { int oldCol = this.cursor.col; this.cursor.col = 0; triggerRedraw( oldCol, this.cursor.line, 1, 1 ); triggerRedraw( 0, this.cursor.line, 1, 1 ); } } private void backspace() { if ( this.cursor.col > 0 ) { this.cursor.col--; triggerRedraw( this.cursor.col + 1, this.cursor.line, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } } private void startReaderThread() { Thread thread = new Thread( new Runnable(){ public void run() { readerMainLoop(); } }, "Terminal" ); //$NON-NLS-1$ thread.start(); } void readerMainLoop() { try { this.running = true; while( this.running ) { int ch = read(); if ( ch == BEL ) bell(); else if ( ch == SWT.CR ) carriageReturn(); else if ( ch == SWT.LF ) nextLine( false ); else if ( ch == SI ) this.cursor.charSet = this.charSetG[0]; else if ( ch == SO ) this.cursor.charSet = this.charSetG[1]; else if ( ch == SWT.TAB ) tabulator(); else if ( ch == FF ) eraseScreen(); else if ( ch == SWT.BS ) backspace(); else if ( ch == SWT.ESC ) parseEscSequence(); else if ( ch == -1 ) continue; else if ( ch < ' ' ) { Activator.logMessage( IStatus.WARNING, Messages.formatMessage( "Terminal.unhandledCtrlChar", //$NON-NLS-1$ Character.valueOf( (char) ch ), Integer.valueOf( ch ) ) ); } else { this.input.unread( ch ); readText(); } } } catch( IOException ioException ) { Activator.logException( ioException ); } for ( ITerminalListener listener : this.terminalListeners ) { listener.terminated(); } } private void tabulator() { int oldCol = this.cursor.col; Integer cursorCol = Integer.valueOf( this.cursor.col + 1 ); Integer[] tabArray = this.tabulatorPositons.toArray( new Integer[this.tabulatorPositons.size()] ); int pos = Arrays.binarySearch( tabArray, cursorCol ); if ( pos < 0 ) { pos = (-pos) - 1; // get insertion point } if (tabArray.length > pos) { int nextTabStop = tabArray[pos].intValue(); if ( nextTabStop < numColsInLine( this.cursor.line ) ) { this.cursor.col = nextTabStop; } else { Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.tabStopOutsideDisplay" ) ); //$NON-NLS-1$ } } else { // next tabstop (tab width 8) this.cursor.col = ( ( this.cursor.col + TAB_WIDTH + 1 ) / TAB_WIDTH ) * TAB_WIDTH; if (this.cursor.col >= numColsInLine( this.cursor.line )) { this.cursor.col = numColsInLine( this.cursor.line ) - 1; } } triggerRedraw(oldCol, this.cursor.line, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } private void readText() throws IOException { int ch = read(); int line = this.cursor.line; int startCol = this.cursor.col; int colsToUpdate = 0; while ( ch >= ' ' ) { if ( this.cursor.col >= numColsInLine( this.cursor.line ) ) { if ( this.wraparound ) { this.cursor.col = 0; triggerRedraw( startCol, line, colsToUpdate, 1 ); startCol = 0; colsToUpdate = 0; if ( this.cursor.line < this.numLines - 1 ) { this.cursor.line++; line = this.cursor.line; } else { // triggerRedraw( numColsInLine( this.cursor.line ) - 1, this.cursor.line, 1, 1 ); scrollUp(); } } else { this.cursor.col = numColsInLine( this.cursor.line ) - 1; } } Char scrCh = this.screenBuffer[ this.cursor.line + this.historySize ][ this.cursor.col ]; scrCh.ch = (char)ch; scrCh.setToCursorFormat( this.cursor ); this.cursor.col++; colsToUpdate++; if ( this.input.available() == 0 ) { if (colsToUpdate != 0) { triggerRedraw( startCol, line, colsToUpdate, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1); startCol += colsToUpdate; colsToUpdate = 0; } } ch = read(); } triggerRedraw( startCol, line, colsToUpdate, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1); this.input.unread( ch ); } private int numColsInLine( final int line ) { int numColsInLine = this.numCols; if ( this.lineWidthMode[line] == LineWidthMode.DOUBLE ) { numColsInLine /= 2; } return numColsInLine; } void triggerRedraw( final int col, final int line, final int cols, final int lines ) { int fontWidthMult = 1; for ( int i = line; i < line + lines && i + Terminal.this.historySize < this.lineWidthMode.length; i++ ) { if ( this.lineWidthMode[ i + Terminal.this.historySize ] == LineWidthMode.DOUBLE ) { fontWidthMult = 2; break; } } final int finalFontWidthMult = fontWidthMult; getDisplay().syncExec( new Runnable() { public void run () { if ( !isDisposed() ) { redraw( col * getFontWidth() * finalFontWidthMult, ( line + Terminal.this.historySize - getScrollbarPosLine() ) * getFontHeigth(), cols * getFontWidth() * finalFontWidthMult, lines * getFontHeigth(), false ); } } } ); } void triggerRedraw() { getDisplay().syncExec( new Runnable() { public void run () { redraw(); } } ); } private void parseEscSequence() throws IOException { int ch = read(); List<Integer> params = new LinkedList<Integer>(); switch(ch) { case '[': do { params.add( Integer.valueOf(readNumber()) ); ch = read(); }while(ch == ';'); executeControlSequence( listToArray(params), ch ); break; case ']': executeOperatingSystemCommand(); break; case '#': executeDecCommand(); break; case '(': // SCS - Select Character Set (G0) selectCharacterSet(0); break; case ')': // SCS - Select Character Set (G1) selectCharacterSet(1); break; case '*': // SCS - Select Character Set (G2) selectCharacterSet(2); break; case '+': // SCS - Select Character Set (G3) selectCharacterSet(3); break; case 'Z': // DECID - Identify Terminal deviceAttributes( new int[0] ); break; case '=': // DECKPAM - Keypad Application Mode this.keyPadMode = KeyPadMode.APPLICATION; break; case '>': // DECKPNM - Keypad Numeric Mode this.keyPadMode = KeyPadMode.NUMERIC; break; case '7': // DECSC - Save Cursor saveCursor(); break; case '8': // DECRC - Restore Cursor restoreCursor(); break; case 'H': // HTS - Horizontal Tabulation Set horizontalTabulationSet(); break; case 'D': // IND - Index index(); break; case 'E': // NEL - Next Line nextLine( true ); break; case 'M': // RI - Reverse Index reverseIndex(); break; case 'c': // RIS - Reset To Initial State; reset(); break; // ------------------ VT52 commands ------------------ // TODO implement a VT52 mode /* case 'A': // Cursor Up Activator.logMessage( IStatus.WARNING, "Cursor Up not implemented" ); break; case 'B': // Cursor Down Activator.logMessage( IStatus.WARNING, "Cursor Down not implemented" ); break; case 'C': // Cursor Right Activator.logMessage( IStatus.WARNING, "Cursor Right not implemented" ); break; //case 'D': // Cursor Left //Activator.logMessage( IStatus.WARNING, "Cursor Left not implemented" ); //break; case 'F': // Enter Graphics Mode Activator.logMessage( IStatus.WARNING, "Enter Graphics Mode not implemented" ); break; case 'G': // Exit Graphics Mode Activator.logMessage( IStatus.WARNING, "Exit Graphics Mode not implemented" ); break; //case 'H': // Cursor to Home //Activator.logMessage( IStatus.WARNING, "Cursor to Home not implemented" ); //break; case 'I': // Reverse Line Feed Activator.logMessage( IStatus.WARNING, "Reverse Line Feed not implemented" ); break; case 'J': // Erase to End of Screen Activator.logMessage( IStatus.WARNING, "Erase to End of Screen not implemented" ); break; case 'K': // Erase to End of Line Activator.logMessage( IStatus.WARNING, "Erase to End of Line not implemented" ); break; case 'Y': // Direct Cursor Address Activator.logMessage( IStatus.WARNING, "Direct Cursor Address not implemented" ); break; case '<': // Enter ANSI Mode Activator.logMessage( IStatus.WARNING, "Enter ANSI Mode not implemented" ); break;*/ // ---------------- end VT52 commands ---------------- default: Activator.logMessage( IStatus.WARNING, Messages.formatMessage( "Terminal.unknownEscSeq", //$NON-NLS-1$ Character.valueOf( (char) ch ) ) ); break; } } private void horizontalTabulationSet() { Integer cursorCol = Integer.valueOf( this.cursor.col ); if ( !this.tabulatorPositons.contains( cursorCol ) ) { this.tabulatorPositons.add( cursorCol ); } } private void selectCharacterSet( final int charSetNr ) throws IOException { int ch = read(); CharSet charSet = CharSet.USASCII; switch (ch) { case 'A': Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.ukCharSetNotSupported" ) ); //$NON-NLS-1$ break; case 'B': charSet = CharSet.USASCII; break; case '0': charSet = CharSet.SPECIAL; break; case '1': Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.alternateStdCharSetNotSupported" ) ); //$NON-NLS-1$ break; case '2': Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.alternateSpecialCharSetNotSupported" ) ); //$NON-NLS-1$ break; default: Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.unknownCharSet" ) ); //$NON-NLS-1$ break; } this.charSetG[charSetNr] = charSet; } private void nextLine( final boolean returnToColZero) { int oldCol = this.cursor.col; if (returnToColZero) this.cursor.col = 0; triggerRedraw( oldCol, this.cursor.line, 1, 1 ); index(); } private void index() { if ( this.cursor.line < this.bottomMargin ) { int oldLine = this.cursor.line; this.cursor.line++; triggerRedraw( this.cursor.col, oldLine, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } else scrollUp(); } private void reverseIndex() { if ( this.cursor.line > this.topMargin ) { int oldLine = this.cursor.line; this.cursor.line--; triggerRedraw( this.cursor.col, oldLine, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } else scrollDown(); } private void saveCursor() { this.savedCursor = new Cursor( this.cursor ); } private void restoreCursor() { int oldCol = this.cursor.col; int oldLine = this.cursor.line; this.cursor = this.savedCursor; triggerRedraw( oldCol, oldLine, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } private static int[] listToArray( final List<Integer> list ) { int[] array = new int[list.size()]; for( int i = 0; i < list.size(); i++ ) { array[i] = list.get( i ).intValue(); } return array; } private void executeDecCommand() throws IOException { int ch = read(); switch (ch) { case '3': // DECDHL - Double Height Line (Top Half) doubleHeightLine( LineHeightMode.DOUBLE_TOP ); break; case '4': // DECDHL - Double Height Line (Bottom Half) doubleHeightLine( LineHeightMode.DOUBLE_BOTTOM ); break; case '5': // DECSWL - Single-width Line doubleWidthLine( LineWidthMode.NORMAL ); break; case '6': // DECDWL - Double-Width Line doubleWidthLine( LineWidthMode.DOUBLE ); break; case '8': // DECALN - Screen Alignment Display screenAlignmentDisplay(); break; default: Activator.logMessage( IStatus.WARNING, Messages.formatMessage( "Terminal.unknownDecCommand", //$NON-NLS-1$ Character.valueOf( (char) ch ) ) ); } } private void doubleWidthLine( final LineWidthMode mode ) { this.lineHeightMode[ this.cursor.line + this.historySize ] = LineHeightMode.NORMAL; this.lineWidthMode[ this.cursor.line + this.historySize ] = mode; if (mode == LineWidthMode.DOUBLE && this.cursor.col > this.numCols / 2) { this.cursor.col = this.numCols / 2; } triggerRedraw( 0, this.cursor.line, this.numCols, 1 ); } private void doubleHeightLine( final LineHeightMode mode ) { if ( mode == LineHeightMode.DOUBLE_TOP || mode == LineHeightMode.DOUBLE_BOTTOM ) { this.lineWidthMode[ this.cursor.line + this.historySize ] = LineWidthMode.DOUBLE; } this.lineHeightMode[ this.cursor.line + this.historySize ] = mode; triggerRedraw( 0, this.cursor.line, this.numCols, 1 ); } private void screenAlignmentDisplay() { for( int i = this.historySize; i < this.numLines + this.historySize; i++ ) { this.lineHeightMode[ i ] = LineHeightMode.NORMAL; this.lineWidthMode[ i ] = LineWidthMode.NORMAL; } for( int i = this.historySize; i < this.numLines + this.historySize; i++ ) { for( Char character : this.screenBuffer[ i ] ) { character.ch = 'E'; } } triggerRedraw(); } private void executeOperatingSystemCommand() throws IOException { int commandNr = readNumber(); int colorNr; Color color; if (read() != ';') { Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.invalidOsCommand" ) ); //$NON-NLS-1$ } else switch (commandNr) { case 0: // Set Window Title this.windowTitle = readString(); for ( ITerminalListener listener : this.terminalListeners ) { listener.windowTitleChanged( this.windowTitle ); } break; case 4: colorNr = readNumber(); if (read() != ';') { Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.invalidOsCommand" ) ); //$NON-NLS-1$ } else { color = parseColorString( readString() ); this.systemColors[ colorNr ] = color; } break; default: Activator.logMessage( IStatus.WARNING, Messages.formatMessage( "Terminal.unknownOsCommand", //$NON-NLS-1$ Integer.valueOf( commandNr ) ) ); break; } } private Color parseColorString( final String colorString ) { Color color = null; if (colorString.startsWith( "rgb:" )) { //$NON-NLS-1$ StringTokenizer tokenizer = new StringTokenizer(colorString.substring( 4 ),"/"); //$NON-NLS-1$ if (tokenizer.countTokens() == 3) { int r = Integer.parseInt( tokenizer.nextToken(), 16 ); int g = Integer.parseInt( tokenizer.nextToken(), 16 ); int b = Integer.parseInt( tokenizer.nextToken(), 16 ); color = new Color( getDisplay(), r, g, b ); } } if (color == null) { color = this.defaultFgColor; Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.unknownColorString" ) ); //$NON-NLS-1$ } return color; } private String readString( /*final char[] terminatingChars*/ ) throws IOException { final char[] terminatingChars = new char[] { BEL, SWT.ESC }; // sorted array StringBuilder buffer = new StringBuilder(); char ch = (char) read(); while( Arrays.binarySearch( terminatingChars, ch ) < 0 ) { buffer.append( ch ); ch = (char) read(); } if ( ch == SWT.ESC ) { if ( read() != '\\' ) { Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.stringTerminatorExpected" ) ); //$NON-NLS-1$ } } return buffer.toString(); } private void executeControlSequence(final int[] params, final int command) throws IOException { switch (command) { case 'D': // CUB - Cursor Backward cursorBackward( params ); break; case 'B': // CUD - Cursor Down cursorDown( params ); break; case 'C': // CUF - Cursor Forward cursorForward( params ); break; case 'H': // CUP - Cursor Position case 'f': // HVP - Horizontal and Vertical Position cursorPosition( params ); break; case 'A': // CUU - Cursor Up cursorUp( params ); break; case 'c': // DA - Device Attributes deviceAttributes( params ); break; case 'q': // DECLL - Load LEDS loadLeds( params ); break; case 'x': // DECREPTPARM - Report Terminal Parameters / DECREQTPARM - Request Terminal Parameters requestTerminalParameters( params ); break; case 'r': // DECSTBM - Set Top and Bottom Margins setTopAndBottomMargins( params, false ); break; case 'y': // DECTST - Invoke Confidence Test Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.invokeConfidenceTestNotImplemented" ) ); //$NON-NLS-1$ break; case 'n': // DSR - Device Status Report deviceStatusReport( params ); break; case 'J': // ED - Erase In Display eraseInDisplay( params ); break; case 'K': // EL - Erase In Line eraseInLine( params ); break; case 'l': // RM - Reset Mode resetMode( params ); break; case 'm': // SGR - Select Graphic Rendition selectGraphicRendition( params ); break; case 'h': // SM - Set Mode setMode( params ); break; case 'g': // TBC - Tabulation Clear tabulationClear( params ); break; // Xterm (and some VTs >100) case 'd': // VPA - Vertical Line Position Absolute verticalLinePositionAbsolute( params ); break; case 'X': // ECH - Erase Character eraseCharacter( params ); break; case 'P': // DCH - Delete Character deleteCharacter( params ); break; case 'G': // CHA - Cursor Horizontal Absolute cursorHorizontalAbsolute( params ); break; default: Activator.logMessage( IStatus.WARNING, Messages.formatMessage("Terminal.unknownCtrlSeq", //$NON-NLS-1$ Character.valueOf( (char) command ) ) ); break; } } private void deviceStatusReport( final int[] params ) throws IOException { final byte[] response = { SWT.ESC, '[', '0', 'n' }; // terminal ok int val = 0; if (params.length != 0) val = params[0]; switch ( val ) { case 5: this.output.write( response ); this.output.flush(); break; case 6: reportCursorPosition(); break; default: Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.invalidDeviceStatusReportParam" ) ); //$NON-NLS-1$ break; } } private void reportCursorPosition() throws IOException { this.output.write( SWT.ESC ); this.output.write( '[' ); this.output.write( Integer.toString( this.cursor.line + 1 ).getBytes( "ASCII" ) ); //$NON-NLS-1$ this.output.write( ';' ); this.output.write( Integer.toString( this.cursor.col + 1 ).getBytes( "ASCII" ) ); //$NON-NLS-1$ this.output.write( 'R' ); this.output.flush(); } private void requestTerminalParameters( final int[] params ) throws IOException { // Parity none, 8 bits, xmitspeed 38400, recvspeed 38400, // clock multiplier 1, STP option flags 0 byte[] response = { SWT.ESC, '[', '2', ';', '1', ';', '1', ';', '1', '2', '8', ';', '1', '2', '8', ';', '1', ';', '0', 'x' }; if (params.length > 0 && params[0] != 0 && params[0] != 1 ) { Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.unknownTermParamReq" ) ); //$NON-NLS-1$ } else { if ( params[0] == 1 ) response[2] = '3'; this.output.write( response ); this.output.flush(); } } private void eraseCharacter( final int[] params ) { int val = 1; int colsInLine = numColsInLine( this.cursor.line ); if (params.length != 0) val = params[0]; if (val == 0) val = 1; for ( int i = this.cursor.col; ( i < ( this.cursor.col + val ) ) && ( i < colsInLine ); i++ ) { this.screenBuffer[ this.cursor.line + this.historySize ][ i ].erase( this.defaultFgColor, this.defaultBgColor ); this.screenBuffer[ this.cursor.line + this.historySize ][ i ].bgColor = this.cursor.bgColor; } triggerRedraw( this.cursor.col, this.cursor.line, val, 1 ); } private void deleteCharacter( final int[] params ) { int val = 1; int colsInLine = numColsInLine( this.cursor.line ); if ( params.length != 0 ) val = params[ 0 ]; if ( val == 0 ) val = 1; if ( val > ( colsInLine - this.cursor.col ) ) val = colsInLine - this.cursor.col; int colsToMove = colsInLine - this.cursor.col - val; for ( int i = this.cursor.col; i < ( this.cursor.col + colsToMove ); i++ ) { this.screenBuffer[ this.cursor.line + this.historySize ][ i ] = this.screenBuffer[ this.cursor.line + this.historySize ][ i + val ]; } for ( int i = ( this.cursor.col + colsToMove ); i < colsInLine; i++ ) { this.screenBuffer[ this.cursor.line + this.historySize ][ i ] = new Char( this.defaultFgColor, this.defaultBgColor ); } triggerRedraw( this.cursor.col, this.cursor.line, colsInLine - this.cursor.col, 1 ); } private void setMode( final int[] params ) { for( int param : params ) { switch(param) { case 1: // Application Cursor Key Mode this.cursorKeyMode = CursorKeyMode.APPLICATION; break; case 3: // 132 Column Mode this.cursor.reset( this.defaultFgColor, this.defaultBgColor ); this.tabulatorPositons.clear(); eraseScreen(); getDisplay().syncExec(new Runnable() { public void run () { setSize( 132 * getFontWidth() + getVerticalBar().getSize().x , 24 * getFontHeigth() ); } }); break; case 4: // Smooth Scrolling Mode // TODO implement smooth scrolling this.scrollMode = ScrollMode.SMOOTH; break; case 5: // Reverse Screen Mode this.reverseScreenMode = true; triggerRedraw(); break; case 6: // Relative Origin Mode setOriginMode( OriginMode.RELATIVE ); break; case 7: // Wraparound On this.wraparound = true; break; case 8: // Auto repeat On // XXX check if auto repeat keyboard events in SWT are possible? Activator.logMessage( IStatus.WARNING, Messages.getString("Terminal.autoRepeatOnNotSupported") ); //$NON-NLS-1$ break; case 9: // Interlace On Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.interlaceOnNotSupported" ) ); //$NON-NLS-1$ break; case 20: // New Line Mode this.newLineMode = true; break; case 45: // Reverse Wraparound On this.reverseWraparound = true; break; default: Activator.logMessage( IStatus.WARNING, Messages.formatMessage("Terminal.unknownSetModeParam", //$NON-NLS-1$ Integer.valueOf( param ) ) ); break; } } } private void resetMode( final int[] params ) { for ( int param : params ) { switch(param) { case 1: // Cursor Cursor Key Mode this.cursorKeyMode = CursorKeyMode.CURSOR; break; case 2: // XXX VT52 Mode not supported Activator.logMessage( IStatus.WARNING, Messages.getString("Terminal.vt52ModeNotSupported") ); //$NON-NLS-1$ break; case 3: // 80 Column Mode getDisplay().syncExec(new Runnable() { public void run () { setSize( 80 * getFontWidth() + getVerticalBar().getSize().x, 24 * getFontHeigth() ); } }); this.cursor.reset( this.defaultFgColor, this.defaultBgColor ); this.tabulatorPositons.clear(); eraseScreen(); break; case 4: this.scrollMode = ScrollMode.JUMP; break; case 5: // Normal Screen Mode this.reverseScreenMode = false; triggerRedraw(); break; case 6: // Absolute Origin Mode setOriginMode( OriginMode.ABSOLUTE ); break; case 7: // Wraparound Off this.wraparound = false; break; case 8: // Auto repeat Off // XXX check if auto repeat keyboard events in SWT are possible? Activator.logMessage( IStatus.WARNING, Messages.getString("Terminal.autoRepeatOffNotSupported") ); //$NON-NLS-1$ break; case 9: // Interlace Off Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.interlaceOffNotSupported" ) ); //$NON-NLS-1$ break; case 20: // Line Feed Mode this.newLineMode = false; break; case 45: // Reverse Wraparound Off this.reverseWraparound = false; break; default: Activator.logMessage( IStatus.WARNING, Messages.formatMessage( "Terminal.unknownResetModeParam", //$NON-NLS-1$ Integer.valueOf( param ) ) ); break; } } } private void tabulationClear( final int[] params ) { Integer cursorCol = Integer.valueOf( this.cursor.col ); int val = 0; if ( params.length > 0 ) val = params[ 0 ]; switch (val) { case 0: this.tabulatorPositons.remove( cursorCol ); break; case 3: this.tabulatorPositons.clear(); break; default: Activator.logMessage( IStatus.WARNING, Messages.formatMessage( "Terminal.unknownTabClearParam", //$NON-NLS-1$ Integer.valueOf( val ) ) ); break; } } private void reset() { this.cursor.reset( this.defaultFgColor, this.defaultBgColor ); this.tabulatorPositons.clear(); this.wraparound = true; // <-- XXX is this really the right value for VT100? this.reverseWraparound = false; this.reverseScreenMode = false; this.newLineMode = false; this.keyPadMode = KeyPadMode.NUMERIC; for( int i = 0; i < this.charSetG.length; i++ ) this.charSetG[i] = CharSet.USASCII; this.topMargin = 0; this.bottomMargin = this.numLines - 1; for ( int i = 0; i < this.leds.length; i++ ) this.leds[i] = false; setOriginMode( OriginMode.ABSOLUTE ); eraseScreen(); } private void setOriginMode( final OriginMode mode ) { int oldLine = this.cursor.line; int oldCol = this.cursor.col; if ( mode == OriginMode.ABSOLUTE ) { this.cursor.col = 0; this.cursor.line = 0; } else { this.cursor.col = 0; this.cursor.line = this.topMargin; } this.originMode = mode; triggerRedraw( oldCol, oldLine, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } void scrollUpCopy() { Char[] tmp; if ( this.topMargin == 0 ) { tmp = scrollHistory(); } else { tmp = this.screenBuffer[ this.topMargin + this.historySize ]; } for( int i = this.topMargin; i < this.bottomMargin; i++ ) { this.lineHeightMode[ i + this.historySize ] = this.lineHeightMode[ i + this.historySize + 1 ]; this.lineWidthMode[ i + this.historySize ] = this.lineWidthMode[ i + this.historySize + 1 ]; this.screenBuffer[ i + this.historySize ] = this.screenBuffer[ i + this.historySize + 1 ]; } this.screenBuffer[ this.bottomMargin + this.historySize ] = tmp; eraseLine( this.bottomMargin ); this.lineWidthMode[ this.bottomMargin + this.historySize ] = LineWidthMode.NORMAL; this.lineHeightMode[ this.bottomMargin + this.historySize ] = LineHeightMode.NORMAL; } private void scrollUp() { getDisplay().syncExec(new Runnable() { public void run () { scrollUpCopy(); Terminal.this.terminalPainter.scrollUp( Terminal.this.topMargin, Terminal.this.bottomMargin ); } }); triggerRedraw( 0, this.bottomMargin, this.numCols, 1 ); // repaint old cursor position triggerRedraw( this.cursor.col, this.cursor.line - 1, 1, 1 ); } private Char[] scrollHistory() { Char[] tmp = this.screenBuffer[0]; for( int i = 0; i < this.historySize; i++ ) { this.lineHeightMode[ i ] = this.lineHeightMode[ i + 1 ]; this.lineWidthMode[ i ] = this.lineWidthMode[ i + 1 ]; this.screenBuffer[ i ] = this.screenBuffer[ i + 1 ]; } return tmp; } void scrollDownCopy() { Char[] tmp = this.screenBuffer[ this.bottomMargin + this.historySize ]; for( int i = this.bottomMargin; i > this.topMargin; i-- ) { this.lineHeightMode[ i + this.historySize ] = this.lineHeightMode[ i + this.historySize - 1 ]; this.lineWidthMode[ i + this.historySize ] = this.lineWidthMode[ i + this.historySize - 1 ]; this.screenBuffer[ i + this.historySize ] = this.screenBuffer[ i + this.historySize - 1 ]; } this.screenBuffer[ this.topMargin + this.historySize ] = tmp; eraseLine( this.topMargin ); this.lineWidthMode[ this.topMargin + this.historySize ] = LineWidthMode.NORMAL; this.lineHeightMode[ this.topMargin + this.historySize ] = LineHeightMode.NORMAL; } private void scrollDown() { getDisplay().syncExec(new Runnable() { public void run () { scrollDownCopy(); Terminal.this.terminalPainter.scrollDown( Terminal.this.topMargin, Terminal.this.bottomMargin ); } }); triggerRedraw( 0, this.topMargin, this.numCols, 1 ); // repaint old cursor position triggerRedraw( this.cursor.col, this.cursor.line + 1, 1, 1 ); } private void setTopAndBottomMargins( final int[] params, final boolean skipCursorReset ) { int top = 0; int bottom = this.numLines; if ( params.length > 0 ) top = params[ 0 ]; if ( params.length > 1 ) bottom = params[ 1 ]; if ( top > 0 ) top--; if ( bottom > 0 ) bottom--; if ( top >= bottom ) { Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.topLargerEqualBottom" ) ); //$NON-NLS-1$ } else if ( bottom >= this.numLines ) { Activator.logMessage( IStatus.WARNING, Messages.formatMessage( "Terminal.bottomOutOfScreen", //$NON-NLS-1$ Integer.valueOf( bottom ), Integer.valueOf( this.numLines ) ) ); } else { this.topMargin = top; this.bottomMargin = bottom; if (!skipCursorReset) cursorPosition( new int[0] ); } } private void selectGraphicRendition( final int[] params ) { int val = 0; int idx = 0; do { if (params.length > idx) val = params[idx]; if (val >= 30 && val <= 37) { // foreground colors // TODO change color when bold is changed if ( this.cursor.bold ) this.cursor.fgColor = this.systemColors[ val - 30 + 8 ]; else this.cursor.fgColor = this.systemColors[ val - 30 ]; } else if ( val >= 40 && val <= 47 ) { // background colors this.cursor.bgColor = this.systemColors[ val - 40 ]; } else switch( val ) { // VT100 case 0: this.cursor.bold = false; this.cursor.underscore = false; this.cursor.blink = false; this.cursor.negative = false; this.cursor.italics = false; this.cursor.strikethrough = false; this.cursor.fgColor = this.defaultFgColor; this.cursor.bgColor = this.defaultBgColor; break; case 1: this.cursor.bold = true; break; case 4: this.cursor.underscore = true; break; case 5: this.cursor.blink = true; break; case 7: this.cursor.negative = true; break; // ANSI case 3: this.cursor.italics = true; break; case 9: this.cursor.strikethrough = true; break; case 22: this.cursor.bold = false; break; case 23: this.cursor.italics = false; break; case 24: this.cursor.underscore = false; break; case 27: this.cursor.negative = false; break; case 29: this.cursor.strikethrough = false; break; case 38: if ( params.length >= idx + 3 && params[ idx + 1 ] == 5 && params[ idx + 2 ] < 256 ) { this.cursor.fgColor = this.systemColors[ params[ idx + 2 ] ]; idx += 2; } else { Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.invalidColorParams" ) ); //$NON-NLS-1$ } break; case 39: this.cursor.fgColor = this.defaultFgColor; break; case 48: if (params.length >= idx + 3 && params[idx + 1] == 5 && params[idx + 2] < 256 ) { this.cursor.bgColor = this.systemColors[ params[ idx + 2 ] ]; idx += 2; } else { Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.invalidColorParams" ) ); //$NON-NLS-1$ } break; case 49: this.cursor.bgColor = this.defaultBgColor; break; default: Activator.logMessage( IStatus.WARNING, Messages.formatMessage( "Terminal.unknownGraphRendParam", //$NON-NLS-1$ Integer.valueOf( val ) )); break; } idx++; } while ( idx < params.length ); } private void eraseScreen() { final int[] eraseScreen = { 2 }; eraseInDisplay( eraseScreen ); resetLineModes(); } private void resetLineModes() { for( int i = 0; i < this.lineHeightMode.length; i++ ) { this.lineHeightMode[ i ] = LineHeightMode.NORMAL; this.lineWidthMode[ i ] = LineWidthMode.NORMAL; } } private void eraseInDisplay( final int[] params ) { int val = 0; if ( params.length > 0 ) val = params[ 0 ]; switch ( val ) { case 0: // From cursor to end of screen eraseInLine( params ); for( int i = this.cursor.line+1; i < this.numLines; i++ ) { eraseLine( i ); } triggerRedraw( 0, this.cursor.line+1, this.numCols, this.numLines - this.cursor.line-1 ); break; case 1: // From beginning of screen to cursor eraseInLine( params ); for( int i = 0; i < this.cursor.line; i++) { eraseLine( i ); } triggerRedraw( 0, 0, this.numCols, this.cursor.line ); break; case 2: // Entire screen for( int i = 0; i < this.numLines; i++ ) { eraseLine( i ); } triggerRedraw(); break; default: Activator.logMessage( IStatus.WARNING, Messages.formatMessage("Terminal.unknownEraseInDispParam", //$NON-NLS-1$ Integer.valueOf( val ) ) ); break; } } private void eraseInLine( final int[] params ) { int val = 0; if (params.length > 0) val = params[0]; switch (val) { case 0: // From cursor to end of line for( int i = this.cursor.col; i < this.screenBuffer[ this.cursor.line + this.historySize ].length; i++ ) { this.screenBuffer[ this.cursor.line + this.historySize ][ i ].erase( this.defaultFgColor, this.defaultBgColor ); } triggerRedraw( this.cursor.col, this.cursor.line, this.numCols-this.cursor.col, 1 ); break; case 1: // From beginning of line to cursor for( int i = 0; i <= this.cursor.col; i++ ) { this.screenBuffer[ this.cursor.line + this.historySize ][ i ].erase( this.defaultFgColor, this.defaultBgColor ); } triggerRedraw( 0, this.cursor.line, this.cursor.col+1, 1 ); break; case 2: // Entire line containing cursor eraseLine( this.cursor.line ); triggerRedraw( 0, this.cursor.line, this.numCols, 1 ); break; default: Activator.logMessage( IStatus.WARNING, Messages.formatMessage( "Terminal.unknownEraseInLineParam", //$NON-NLS-1$ Integer.valueOf( val ) ) ); break; } } private void eraseLine( final int lineNr ) { for( Char character : this.screenBuffer[ lineNr + this.historySize ] ) { character.erase( this.defaultFgColor, this.defaultBgColor ); } } private void loadLeds( final int[] params ) { int val = 0; if ( params.length > 0 ) val = params[ 0 ]; if ( val == 0 ) { for ( int i = 0; i < this.leds.length; i++ ) this.leds[i] = false; } else if ( val < this.leds.length ) { this.leds[ val-1 ] = true; } else { Activator.logMessage( IStatus.WARNING, Messages.formatMessage("Terminal.invalidLoadLedsParam", //$NON-NLS-1$ Integer.valueOf( val ) ) ); } } private void cursorBackward(final int[] params) { int val = 1; int oldCol = this.cursor.col; int oldLine = this.cursor.line; if ( params.length != 0 ) val = params[ 0 ]; if ( val == 0 ) val = 1; if ( this.reverseWraparound /*|| cursor.col == 0*/ ) { // XXX I'm not so sure if this is correct behavior while ( this.cursor.col - val < 0 ) { if (this.cursor.line != 0 ) { val -= this.cursor.col + 1; this.cursor.line--; this.cursor.col = numColsInLine( this.cursor.line ) - 1; } else { this.cursor.col = 0; val = 0; break; } } this.cursor.col -= val; } else { if ( this.cursor.col - val < 0 ) this.cursor.col = 0; else this.cursor.col -= val; } triggerRedraw( oldCol, oldLine, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } private void cursorDown(final int[] params) { int val = 1; int oldLine = this.cursor.line; if ( params.length != 0 ) val = params[ 0 ]; if ( val == 0 ) val = 1; if ( this.cursor.line + val >= this.bottomMargin ) this.cursor.line = this.bottomMargin - 1; else this.cursor.line += val; triggerRedraw( this.cursor.col, oldLine, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } private void cursorForward(final int[] params) { int val = 1; int oldCol = this.cursor.col; if ( params.length != 0 ) val = params[ 0 ]; if ( val == 0 ) val = 1; if ( this.cursor.col + val >= this.numCols ) this.cursor.col = this.numCols - 1; else this.cursor.col += val; triggerRedraw( oldCol, this.cursor.line, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } private void cursorUp(final int[] params) { int val = 1; int oldLine = this.cursor.line; if ( params.length != 0 ) val = params[ 0 ]; if ( val == 0 ) val = 1; if ( this.cursor.line - val < this.topMargin ) this.cursor.line = this.topMargin; else this.cursor.line -= val; triggerRedraw( this.cursor.col, oldLine, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } private void verticalLinePositionAbsolute(final int[] params) { int val = 1; int oldLine = this.cursor.line; if ( params.length != 0 ) val = params[ 0 ]; if ( val == 0 ) val = 1; if ( val >= this.numLines ) val = this.numLines - 1; this.cursor.line = val; triggerRedraw( this.cursor.col, oldLine, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } private void cursorHorizontalAbsolute( final int[] params ) { int val = 1; int oldCol = this.cursor.col; if ( params.length != 0 ) val = params[ 0 ]; if ( val == 0 ) val = 1; if ( val >= this.numCols ) val = this.numCols - 1; this.cursor.col = val; triggerRedraw( oldCol, this.cursor.col, 1, 1 ); triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } private void cursorPosition(final int[] params) { int col = 0; int line = 0; int lines = this.numLines; if ( params.length > 0 ) line = params[ 0 ]; if ( params.length > 1 ) col = params[ 1 ]; if ( line > 0 ) line--; if ( col > 0 ) col--; if ( this.originMode == OriginMode.RELATIVE ) { line += this.topMargin; lines = this.bottomMargin; } if ( line >= lines ) { line = lines - 1; Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.cursorPosOutOfRangeLine" ) ); //$NON-NLS-1$ } if ( col >= this.numCols ) { col = this.numCols - 1; Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.cursorPosOutOfRangeCol" ) ); //$NON-NLS-1$ } int oldLine = this.cursor.line; int oldCol = this.cursor.col; this.cursor.line = line; this.cursor.col = col; if (oldLine < this.numLines && oldCol < this.numCols) { triggerRedraw( oldCol, oldLine, 1, 1 ); } triggerRedraw( this.cursor.col, this.cursor.line, 1, 1 ); } private void deviceAttributes( final int[] params) throws IOException { final byte[] response = { SWT.ESC, '[', '?', '1', ';', '2', 'c' }; // 2 = VT100 with AVO if ( params.length > 0 && params[ 0 ] != 0 ) { Activator.logMessage( IStatus.WARNING, Messages.getString( "Terminal.unknownDevAttribReq" ) ); //$NON-NLS-1$ } else { this.output.write( response ); this.output.flush(); } } private int readNumber() throws IOException { int ch = read(); int val = 0; while ( ( ch >= '0' && ch <= '9' ) || ch =='?' ) { if ( ch != '?' ) { // XXX meaning of '?' character ?!? val *= 10; val += ch - '0'; } ch = read(); } this.input.unread( ch ); return val; } private int read() throws IOException { int val = this.input.read(); if ( val == -1 ) this.running = false; this.terminalSelection.clearSelection(); return val; } private void initSystemColorTable() { Display dpy = getDisplay(); this.defaultBgColor = dpy.getSystemColor( SWT.COLOR_BLACK ); this.defaultFgColor = dpy.getSystemColor( SWT.COLOR_GRAY ); this.systemColors = new Color[256]; this.systemColors[ 0 ] = dpy.getSystemColor( SWT.COLOR_BLACK ); this.systemColors[ 1 ] = dpy.getSystemColor( SWT.COLOR_DARK_RED ); this.systemColors[ 2 ] = dpy.getSystemColor( SWT.COLOR_DARK_GREEN ); this.systemColors[ 3 ] = dpy.getSystemColor( SWT.COLOR_DARK_YELLOW ); this.systemColors[ 4 ] = dpy.getSystemColor( SWT.COLOR_DARK_BLUE ); this.systemColors[ 5 ] = dpy.getSystemColor( SWT.COLOR_DARK_MAGENTA ); this.systemColors[ 6 ] = dpy.getSystemColor( SWT.COLOR_DARK_CYAN ); this.systemColors[ 7 ] = dpy.getSystemColor( SWT.COLOR_GRAY ); this.systemColors[ 8 ] = dpy.getSystemColor( SWT.COLOR_DARK_GRAY ); this.systemColors[ 9 ] = dpy.getSystemColor( SWT.COLOR_RED ); this.systemColors[ 10 ] = dpy.getSystemColor( SWT.COLOR_GREEN ); this.systemColors[ 11 ] = dpy.getSystemColor( SWT.COLOR_YELLOW ); this.systemColors[ 12 ] = dpy.getSystemColor( SWT.COLOR_BLUE ); this.systemColors[ 13 ] = dpy.getSystemColor( SWT.COLOR_MAGENTA ); this.systemColors[ 14 ] = dpy.getSystemColor( SWT.COLOR_CYAN ); this.systemColors[ 15 ] = dpy.getSystemColor( SWT.COLOR_WHITE ); for( int i = 16; i < 256; i++ ) { this.systemColors[ i ] = this.defaultBgColor; } } void changeScreenSize() { Char[][] newScreenBuffer; LineHeightMode[] newLineHeightMode; LineWidthMode[] newLineWidthMode; Font font = getFont(); Point widgetSize = getSize(); GC gc = new GC(this); gc.setFont(font); this.fontWidth = gc.getFontMetrics().getAverageCharWidth(); this.fontHeight = gc.getFontMetrics().getHeight(); gc.dispose(); this.numCols = ( widgetSize.x - getVerticalBar().getSize().x ) / this.fontWidth; this.numLines = widgetSize.y / this.fontHeight; if ( this.numCols < 2 ) this.numCols = 80; if ( this.numLines < 2 ) this.numLines = 24; newScreenBuffer = new Char[ this.numLines + this.historySize ][]; newLineHeightMode = new LineHeightMode[ this.numLines + this.historySize ]; newLineWidthMode = new LineWidthMode[ this.numLines + this.historySize ]; int linesDiff = newScreenBuffer.length - this.screenBuffer.length; if ( linesDiff < 0 && this.cursor.line + this.historySize >= newScreenBuffer.length ) { linesDiff = newScreenBuffer.length - (this.cursor.line + this.historySize) - 1; } else if ( linesDiff < 0 ) { linesDiff = 0; } for( int i = 0; i < newScreenBuffer.length; i++ ) { newScreenBuffer[ i ] = new Char[ this.numCols ]; // copy contents of old screenbuffer int oldIndex = i - linesDiff; if ( oldIndex >= 0 && oldIndex < this.screenBuffer.length ) { int len = newScreenBuffer[ i ].length; if ( this.screenBuffer[ oldIndex ].length < len ) len = this.screenBuffer[ oldIndex ].length; for( int j = 0; j < len; j++ ) newScreenBuffer[ i ][ j ] = this.screenBuffer[ oldIndex ][ j ]; newLineHeightMode[ i ] = this.lineHeightMode[ oldIndex ]; newLineWidthMode[ i ] = this.lineWidthMode[ oldIndex ]; } for( int j = 0; j < newScreenBuffer[ i ].length; j++ ) { if ( newScreenBuffer[ i ][ j ] == null ) { newScreenBuffer[ i ][ j ] = new Char( this.defaultFgColor, this.defaultBgColor ); } } if ( newLineHeightMode[ i ] == null ) newLineHeightMode[ i ] = LineHeightMode.NORMAL; if ( newLineWidthMode[ i ] == null ) newLineWidthMode[ i ] = LineWidthMode.NORMAL; } this.screenBuffer = newScreenBuffer; this.lineHeightMode = newLineHeightMode; this.lineWidthMode = newLineWidthMode; setTopAndBottomMargins( new int[0], true ); this.cursor.line += linesDiff; if ( this.cursor.col >= this.numCols ) this.cursor.col = this.numCols - 1; if ( this.numCols != 0 && this.numLines !=0 ) { for ( ITerminalListener listener : this.terminalListeners ) { listener.windowSizeChanged( this.numCols, this.numLines, this.numCols * this.fontWidth, this.numLines * this.fontHeight ); } } this.getVerticalBar().setValues( this.historySize, 0, this.screenBuffer.length, this.numLines, 1, this.numLines ); } /** * Sets the InputStream from which the data to display should be read. * @param in stream to display. */ public void setInputStream( final InputStream in ) { this.input = new PushbackInputStream( in ); startReaderThread(); } /** * Sets the OuputStream to which the terminal input sould be sent to. * @param out stream to send input to. */ public void setOutputStream( final OutputStream out ) { this.output = out; } @Override public void setFont( final Font font ) { this.terminalPainter.setFont( font ); super.setFont( font ); } @Override public Color getForeground() { return this.defaultFgColor; } @Override public Color getBackground() { return this.defaultBgColor; } boolean isInReverseScreenMode() { return this.reverseScreenMode; } int getFontHeigth() { return this.fontHeight; } int getFontWidth() { return this.fontWidth; } Char[][] getScreenBuffer() { return this.screenBuffer; } LineHeightMode[] getLineHeightMode() { return this.lineHeightMode; } LineWidthMode[] getLineWidthMode() { return this.lineWidthMode; } int getCursorLine() { return this.cursor.line; } int getCursorCol() { return this.cursor.col; } int getScrollbarPosLine() { return getVerticalBar().getSelection(); } int getNumLines() { return this.numLines; } int getNumCols() { return this.numCols; } int getHistorySize() { return this.historySize; } public void addSelectionChangedListener( final ISelectionChangedListener listener ) { this.selectionListeners.add( listener ); } public ISelection getSelection() { return this.terminalSelection; } public void removeSelectionChangedListener( final ISelectionChangedListener listener ) { this.selectionListeners.remove( listener ); } public void setSelection( final ISelection selection ) { // TODO Auto-generated method stub } void fireSelectionChanged() { SelectionChangedEvent event = new SelectionChangedEvent( this, this.terminalSelection ); for ( ISelectionChangedListener listener : this.selectionListeners ) { listener.selectionChanged( event ); } } }