| /******************************************************************************* |
| * Copyright (c) 2007, 2011 Wind River Systems, Inc. and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Michael Scharf (Wind River) - initial API and implementation |
| * Anton Leherbauer (Wind River) - [206329] Changing terminal size right after connect does not scroll properly |
| *******************************************************************************/ |
| package org.eclipse.tm.internal.terminal.emulator; |
| |
| import static java.util.Collections.emptyList; |
| |
| import java.util.*; |
| |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.hyperlink.IHyperlink; |
| import org.eclipse.tm.terminal.model.*; |
| |
| import com.google.eclipse.terminal.ui.hyperlink.*; |
| |
| /** |
| * |
| */ |
| public class VT100EmulatorBackend implements IVT100EmulatorBackend { |
| |
| /** |
| * This field holds the number of the column in which the cursor is |
| * logically positioned. The leftmost column on the screen is column 0, and |
| * column numbers increase to the right. The maximum value of this field is |
| * {@link #widthInColumns} - 1. We track the cursor column using this field |
| * to avoid having to recompute it repeatly using StyledText method calls. |
| * <p> |
| * |
| * The StyledText widget that displays text has a vertical bar (called the |
| * "caret") that appears _between_ character cells, but ANSI terminals have |
| * the concept of a cursor that appears _in_ a character cell, so we need a |
| * convention for which character cell the cursor logically occupies when |
| * the caret is physically between two cells. The convention used in this |
| * class is that the cursor is logically in column N when the caret is |
| * physically positioned immediately to the _left_ of column N. |
| * <p> |
| * |
| * When fCursorColumn is N, the next character output to the terminal appears |
| * in column N. When a character is output to the rightmost column on a |
| * given line (column widthInColumns - 1), the cursor moves to column 0 on |
| * the next line after the character is drawn (this is how line wrapping is |
| * implemented). If the cursor is in the bottommost line when line wrapping |
| * occurs, the topmost visible line is scrolled off the top edge of the |
| * screen. |
| * <p> |
| */ |
| private int fCursorColumn; |
| private int fCursorLine; |
| private Style fDefaultStyle; |
| private Style fStyle; |
| int fLines; |
| int fColumns; |
| final private ITerminalTextData fTerminal; |
| |
| private final IHyperlinkFactory urlHyperlinkFactory = new HttpHyperlinkFactory(); |
| private final Map<Integer, List<IHyperlink>> hyperlinks = new HashMap<Integer, List<IHyperlink>>(); |
| |
| public VT100EmulatorBackend(ITerminalTextData terminal) { |
| fTerminal=terminal; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#clearAll() |
| */ |
| public void clearAll() { |
| synchronized (fTerminal) { |
| // clear the history |
| int n=fTerminal.getHeight(); |
| for (int line = 0; line < n; line++) { |
| fTerminal.cleanLine(line); |
| } |
| fTerminal.setDimensions(fLines, fTerminal.getWidth()); |
| setStyle(getDefaultStyle()); |
| setCursor(0, 0); |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setDimensions(int, int) |
| */ |
| public void setDimensions(int lines, int cols) { |
| synchronized (fTerminal) { |
| if(lines==fLines && cols==fColumns) |
| { |
| return; // nothing to do |
| } |
| // relative cursor line |
| int cl=getCursorLine(); |
| int cc=getCursorColumn(); |
| int height=fTerminal.getHeight(); |
| // absolute cursor line |
| int acl=cl+height-fLines; |
| int newLines=Math.max(lines,height); |
| if(lines<fLines) { |
| if(height==fLines) { |
| // if the terminal has no history, then resize by |
| // setting the size to the new size |
| // TODO We are assuming that cursor line points at end of text |
| newLines=Math.max(lines, cl+1); |
| } |
| } |
| fLines=lines; |
| fColumns=cols; |
| // make the terminal at least as high as we need lines |
| fTerminal.setDimensions(newLines, fColumns); |
| // compute relative cursor line |
| cl=acl-(newLines-fLines); |
| setCursor(cl, cc); |
| } |
| } |
| |
| int toAbsoluteLine(int line) { |
| synchronized (fTerminal) { |
| return fTerminal.getHeight()-fLines+line; |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#insertCharacters(int) |
| */ |
| public void insertCharacters(int charactersToInsert) { |
| synchronized (fTerminal) { |
| int line=toAbsoluteLine(fCursorLine); |
| int n=charactersToInsert; |
| for (int col = fColumns-1; col >=fCursorColumn+n; col--) { |
| char c=fTerminal.getChar(line, col-n); |
| Style style=fTerminal.getStyle(line, col-n); |
| fTerminal.setChar(line, col,c, style); |
| } |
| int last=Math.min(fCursorColumn+n, fColumns); |
| for (int col = fCursorColumn; col <last; col++) { |
| fTerminal.setChar(line, col,'\000', null); |
| } |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#eraseToEndOfScreen() |
| */ |
| public void eraseToEndOfScreen() { |
| synchronized (fTerminal) { |
| eraseLineToEnd(); |
| for (int line = toAbsoluteLine(fCursorLine+1); line < toAbsoluteLine(fLines); line++) { |
| fTerminal.cleanLine(line); |
| } |
| } |
| |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#eraseToCursor() |
| */ |
| public void eraseToCursor() { |
| synchronized (fTerminal) { |
| for (int line = toAbsoluteLine(0); line < toAbsoluteLine(fCursorLine); line++) { |
| fTerminal.cleanLine(line); |
| } |
| eraseLineToCursor(); |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#eraseAll() |
| */ |
| public void eraseAll() { |
| synchronized (fTerminal) { |
| for (int line = toAbsoluteLine(0); line < toAbsoluteLine(fLines); line++) { |
| fTerminal.cleanLine(line); |
| } |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#eraseLine() |
| */ |
| public void eraseLine() { |
| synchronized (fTerminal) { |
| fTerminal.cleanLine(toAbsoluteLine(fCursorLine)); |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#eraseLineToEnd() |
| */ |
| public void eraseLineToEnd() { |
| synchronized (fTerminal) { |
| int line=toAbsoluteLine(fCursorLine); |
| for (int col = fCursorColumn; col < fColumns; col++) { |
| fTerminal.setChar(line, col, '\000', null); |
| } |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#eraseLineToCursor() |
| */ |
| @Override |
| public void eraseLineToCursor() { |
| synchronized (fTerminal) { |
| int line=toAbsoluteLine(fCursorLine); |
| for (int col = 0; col <= fCursorColumn; col++) { |
| fTerminal.setChar(line, col, '\000', null); |
| } |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#insertLines(int) |
| */ |
| @Override |
| public void insertLines(int n) { |
| synchronized (fTerminal) { |
| if(!isCusorInScrollingRegion()) { |
| return; |
| } |
| assert n>0; |
| int line=toAbsoluteLine(fCursorLine); |
| int nLines=fTerminal.getHeight()-line; |
| fTerminal.scroll(line, nLines, n); |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#deleteCharacters(int) |
| */ |
| public void deleteCharacters(int n) { |
| synchronized (fTerminal) { |
| int line=toAbsoluteLine(fCursorLine); |
| for (int col = fCursorColumn+n; col < fColumns; col++) { |
| char c=fTerminal.getChar(line, col); |
| Style style=fTerminal.getStyle(line, col); |
| fTerminal.setChar(line, col-n,c, style); |
| } |
| int first=Math.max(fCursorColumn, fColumns-n); |
| for (int col = first; col <fColumns; col++) { |
| fTerminal.setChar(line, col,'\000', null); |
| } |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#deleteLines(int) |
| */ |
| @Override |
| public void deleteLines(int n) { |
| synchronized (fTerminal) { |
| if(!isCusorInScrollingRegion()) { |
| return; |
| } |
| assert n>0; |
| int line=toAbsoluteLine(fCursorLine); |
| int nLines=fTerminal.getHeight()-line; |
| fTerminal.scroll(line, nLines, -n); |
| } |
| } |
| private boolean isCusorInScrollingRegion() { |
| // TODO Auto-generated method stub |
| return true; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getDefaultStyle() |
| */ |
| public Style getDefaultStyle() { |
| synchronized (fTerminal) { |
| return fDefaultStyle; |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setDefaultStyle(org.eclipse.tm.terminal.model.Style) |
| */ |
| public void setDefaultStyle(Style defaultStyle) { |
| synchronized (fTerminal) { |
| fDefaultStyle = defaultStyle; |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getStyle() |
| */ |
| public Style getStyle() { |
| synchronized (fTerminal) { |
| if(fStyle==null) { |
| return fDefaultStyle; |
| } |
| return fStyle; |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setStyle(org.eclipse.tm.terminal.model.Style) |
| */ |
| public void setStyle(Style style) { |
| synchronized (fTerminal) { |
| fStyle=style; |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#appendString(java.lang.String) |
| */ |
| public void appendString(String buffer) { |
| synchronized (fTerminal) { |
| char[] chars=buffer.toCharArray(); |
| int line=toAbsoluteLine(fCursorLine); |
| int originalLine = line; |
| List<IHyperlink> found = emptyList(); |
| if (buffer != null) { |
| found = urlHyperlinkFactory.hyperlinksIn(fCursorColumn, buffer); |
| hyperlinks.put(new Integer(line), found); |
| } |
| int i=0; |
| while (i < chars.length) { |
| int n=Math.min(fColumns-fCursorColumn,chars.length-i); |
| fTerminal.setChars(line, fCursorColumn, chars, i, n, fStyle); |
| int col=fCursorColumn+n; |
| i+=n; |
| // wrap needed? |
| if(col>=fColumns) { |
| doNewline(); |
| line=toAbsoluteLine(fCursorLine); |
| setCursorColumn(0); |
| } else { |
| setCursorColumn(col); |
| } |
| } |
| drawHyperlinks(found, originalLine); |
| } |
| } |
| |
| private void drawHyperlinks(List<IHyperlink> hyperlinks, int line) { |
| for (IHyperlink hyperlink : hyperlinks) { |
| IRegion region = hyperlink.getHyperlinkRegion(); |
| int start = region.getOffset(); |
| int end = start + region.getLength(); |
| for (int column = start; column < end; column++) { |
| Style style = fTerminal.getStyle(line, column); |
| if (style != null) { |
| style = style.setUnderline(true); |
| fTerminal.setChar(line, column, fTerminal.getChar(line, column), style); |
| } |
| } |
| } |
| } |
| |
| /** |
| * MUST be called from a synchronized block! |
| */ |
| private void doNewline() { |
| if(fCursorLine+1>=fLines) { |
| int h=fTerminal.getHeight(); |
| fTerminal.addLine(); |
| if(h!=fTerminal.getHeight()) { |
| setCursorLine(fCursorLine+1); |
| } |
| } else { |
| setCursorLine(fCursorLine+1); |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#processNewline() |
| */ |
| public void processNewline() { |
| synchronized (fTerminal) { |
| doNewline(); |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getCursorLine() |
| */ |
| public int getCursorLine() { |
| synchronized (fTerminal) { |
| return fCursorLine; |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getCursorColumn() |
| */ |
| public int getCursorColumn() { |
| synchronized (fTerminal) { |
| return fCursorColumn; |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setCursor(int, int) |
| */ |
| public void setCursor(int targetLine, int targetColumn) { |
| synchronized (fTerminal) { |
| setCursorLine(targetLine); |
| setCursorColumn(targetColumn); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setCursorColumn(int) |
| */ |
| public void setCursorColumn(int targetColumn) { |
| synchronized (fTerminal) { |
| if(targetColumn<0) { |
| targetColumn=0; |
| } else if(targetColumn>=fColumns) { |
| targetColumn=fColumns-1; |
| } |
| fCursorColumn=targetColumn; |
| // We make the assumption that nobody is changing the |
| // terminal cursor except this class! |
| // This assumption gives a huge performance improvement |
| fTerminal.setCursorColumn(targetColumn); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setCursorLine(int) |
| */ |
| public void setCursorLine(int targetLine) { |
| synchronized (fTerminal) { |
| if(targetLine<0) { |
| targetLine=0; |
| } else if(targetLine>=fLines) { |
| targetLine=fLines-1; |
| } |
| fCursorLine=targetLine; |
| // We make the assumption that nobody is changing the |
| // terminal cursor except this class! |
| // This assumption gives a huge performance improvement |
| fTerminal.setCursorLine(toAbsoluteLine(targetLine)); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getLines() |
| */ |
| public int getLines() { |
| synchronized (fTerminal) { |
| return fLines; |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getColumns() |
| */ |
| public int getColumns() { |
| synchronized (fTerminal) { |
| return fColumns; |
| } |
| } |
| |
| public List<IHyperlink> hyperlinksAt(int line) { |
| List<IHyperlink> found = hyperlinks.get(new Integer(line)); |
| if (found == null) { |
| return emptyList(); |
| } |
| return found; |
| } |
| } |