eu.geclipse.terminal.internal.Terminal.java Source code

Java tutorial

Introduction

Here is the source code for eu.geclipse.terminal.internal.Terminal.java

Source

/*****************************************************************************
 * 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);
        }
    }
}