package com.jediterm.terminal.display;
import com.jediterm.terminal.*;
import com.jediterm.terminal.emulator.charset.CharacterSet;
import com.jediterm.terminal.emulator.charset.GraphicSet;
import com.jediterm.terminal.emulator.charset.GraphicSetState;
import com.jediterm.terminal.emulator.mouse.*;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.*;
/**
* Terminal that reflects obtained commands and text at {@link TerminalDisplay}(handles change of cursor position, screen size etc)
* and {@link BackBuffer}(stores printed text)
*
* @author traff
*/
public class JediTerminal implements Terminal, TerminalMouseListener {
private static final Logger LOG = Logger.getLogger(JediTerminal.class.getName());
private static final int MIN_WIDTH = 5;
private int myScrollRegionTop;
private int myScrollRegionBottom;
volatile private int myCursorX = 0;
volatile private int myCursorY = 1;
private int myTerminalWidth = 80;
private int myTerminalHeight = 24;
private final TerminalDisplay myDisplay;
private final BackBuffer myBackBuffer;
private final StyleState myStyleState;
private StoredCursor myStoredCursor = null;
private final EnumSet<TerminalMode> myModes = EnumSet.noneOf(TerminalMode.class);
private final TerminalKeyEncoder myTerminalKeyEncoder = new TerminalKeyEncoder();
private final Tabulator myTabulator;
private final GraphicSetState myGraphicSetState;
private MouseFormat myMouseFormat = MouseFormat.MOUSE_FORMAT_XTERM;
private TerminalOutputStream myTerminalOutput = null;
private MouseMode myMouseMode = MouseMode.MOUSE_REPORTING_NONE;
public JediTerminal(final TerminalDisplay display, final BackBuffer buf, final StyleState initialStyleState) {
myDisplay = display;
myBackBuffer = buf;
myStyleState = initialStyleState;
myTerminalWidth = display.getColumnCount();
myTerminalHeight = display.getRowCount();
myScrollRegionTop = 1;
myScrollRegionBottom = myTerminalHeight;
myTabulator = new DefaultTabulator(myTerminalWidth);
myGraphicSetState = new GraphicSetState();
reset();
}
@Override
public void setModeEnabled(TerminalMode mode, boolean enabled) {
if (enabled) {
myModes.add(mode);
}
else {
myModes.remove(mode);
}
mode.setEnabled(this, enabled);
}
@Override
public void disconnected() {
myDisplay.setCursorVisible(false);
}
private void wrapLines() {
if (myCursorX >= myTerminalWidth) {
myCursorX = 0;
// clear the end of the line in the text buffer
myBackBuffer.getLine(myCursorY - 1).deleteCharacters(myTerminalWidth);
if (isAutoWrap()) {
myBackBuffer.getLine(myCursorY - 1).setWrapped(true);
myCursorY += 1;
}
}
}
private void finishText() {
myDisplay.setCursor(myCursorX, myCursorY);
scrollY();
}
@Override
public void writeCharacters(String string) {
writeCharacters(decode(string).toCharArray(), 0, string.length());
}
private void writeCharacters(final char[] chosenBuffer, final int start,
final int length) {
myBackBuffer.lock();
try {
wrapLines();
scrollY();
if (length != 0) {
myBackBuffer.writeBytes(myCursorX, myCursorY, chosenBuffer, start, length);
}
myCursorX += CharacterUtils.getTextLength(chosenBuffer, start, length);
finishText();
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void writeDoubleByte(final char[] bytesOfChar) throws UnsupportedEncodingException {
writeString(new String(bytesOfChar, 0, 2));
}
public void writeString(String string) {
doWriteString(decode(string));
}
private String decode(String string) {
StringBuilder result = new StringBuilder();
for (char c : string.toCharArray()) {
result.append(myGraphicSetState.map(c));
}
return result.toString();
}
private void doWriteString(String string) {
myBackBuffer.lock();
try {
wrapLines();
scrollY();
myBackBuffer.writeString(myCursorX, myCursorY, string);
myCursorX += string.length();
finishText();
}
finally {
myBackBuffer.unlock();
}
}
public void writeUnwrappedString(String string) {
int length = string.length();
int off = 0;
while (off < length) {
int amountInLine = Math.min(distanceToLineEnd(), length - off);
writeString(string.substring(off, off + amountInLine));
wrapLines();
scrollY();
off += amountInLine;
}
}
public void scrollY() {
myBackBuffer.lock();
try {
if (myCursorY > myScrollRegionBottom) {
final int dy = myScrollRegionBottom - myCursorY;
myCursorY = myScrollRegionBottom;
scrollArea(myScrollRegionTop, scrollingRegionSize(), dy);
myDisplay.setCursor(myCursorX, myCursorY);
}
if (myCursorY < myScrollRegionTop) {
myCursorY = myScrollRegionTop;
}
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void newLine() {
myCursorY += 1;
scrollY();
if (isAutoNewLine()) {
carriageReturn();
}
myDisplay.setCursor(myCursorX, myCursorY);
}
@Override
public void mapCharsetToGL(int num) {
myGraphicSetState.setGL(num);
}
@Override
public void mapCharsetToGR(int num) {
myGraphicSetState.setGR(num);
}
@Override
public void designateCharacterSet(int tableNumber, char charset) {
GraphicSet gs = myGraphicSetState.getGraphicSet(tableNumber);
myGraphicSetState.designateGraphicSet(gs, charset);
}
@Override
public void singleShiftSelect(int num) {
myGraphicSetState.overrideGL(num);
}
@Override
public void setAnsiConformanceLevel(int level) {
if (level == 1 || level == 2) {
myGraphicSetState.designateGraphicSet(0, CharacterSet.ASCII); //ASCII designated as G0
myGraphicSetState
.designateGraphicSet(1, CharacterSet.DEC_SUPPLEMENTAL); //TODO: not DEC supplemental, but ISO Latin-1 supplemental designated as G1
mapCharsetToGL(0);
mapCharsetToGR(1);
}
else if (level == 3) {
designateCharacterSet(0, 'B'); //ASCII designated as G0
mapCharsetToGL(0);
}
else {
throw new IllegalArgumentException();
}
}
@Override
public void setWindowTitle(String name) {
myDisplay.setWindowTitle(name);
}
@Override
public void backspace() {
myCursorX -= 1;
if (myCursorX < 0) {
myCursorY -= 1;
myCursorX = myTerminalWidth - 1;
}
myDisplay.setCursor(myCursorX, myCursorY);
}
@Override
public void carriageReturn() {
myCursorX = 0;
myDisplay.setCursor(myCursorX, myCursorY);
}
@Override
public void horizontalTab() {
myCursorX = myTabulator.nextTab(myCursorX);
if (myCursorX >= myTerminalWidth) {
myCursorX = 0;
myCursorY += 1;
}
myDisplay.setCursor(myCursorX, myCursorY);
}
@Override
public void eraseInDisplay(final int arg) {
myBackBuffer.lock();
try {
int beginY;
int endY;
switch (arg) {
case 0:
// Initial line
if (myCursorX < myTerminalWidth) {
myBackBuffer.eraseCharacters(myCursorX, myTerminalWidth, myCursorY - 1);
}
// Rest
beginY = myCursorY;
endY = myTerminalHeight;
break;
case 1:
// initial line
myBackBuffer.eraseCharacters(0, myCursorX + 1, myCursorY - 1);
beginY = 0;
endY = myCursorY - 1;
break;
case 2:
beginY = 0;
endY = myTerminalHeight;
break;
default:
LOG.error("Unsupported erase in display mode:" + arg);
beginY = 1;
endY = 1;
break;
}
// Rest of lines
if (beginY != endY) {
clearLines(beginY, endY);
}
}
finally {
myBackBuffer.unlock();
}
}
public void clearLines(final int beginY, final int endY) {
myBackBuffer.lock();
try {
myBackBuffer.clearLines(beginY, endY);
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void clearScreen() {
clearLines(0, myTerminalHeight);
}
@Override
public void setCursorVisible(boolean visible) {
myDisplay.setCursorVisible(visible);
}
@Override
public void useAlternateBuffer(boolean enabled) {
myBackBuffer.useAlternateBuffer(enabled);
myDisplay.setScrollingEnabled(!enabled);
}
@Override
public byte[] getCodeForKey(int key) {
return myTerminalKeyEncoder.getCode(key);
}
@Override
public void setApplicationArrowKeys(boolean enabled) {
if (enabled) {
myTerminalKeyEncoder.arrowKeysApplicationSequences();
}
else {
myTerminalKeyEncoder.arrowKeysAnsiCursorSequences();
}
}
@Override
public void setApplicationKeypad(boolean enabled) {
if (enabled) {
myTerminalKeyEncoder.keypadApplicationSequences();
}
else {
myTerminalKeyEncoder.normalKeypad();
}
}
@Override
public void setAutoNewLine(boolean enabled) {
myTerminalKeyEncoder.setAutoNewLine(enabled);
}
public void eraseInLine(int arg) {
myBackBuffer.lock();
try {
switch (arg) {
case 0:
if (myCursorX < myTerminalWidth) {
myBackBuffer.eraseCharacters(myCursorX, myTerminalWidth, myCursorY - 1);
}
// delete to the end of line : line is no more wrapped
myBackBuffer.getLine(myCursorY - 1).setWrapped(false);
break;
case 1:
final int extent = Math.min(myCursorX + 1, myTerminalWidth);
myBackBuffer.eraseCharacters(0, extent, myCursorY - 1);
break;
case 2:
myBackBuffer.eraseCharacters(0, myTerminalWidth, myCursorY - 1);
break;
default:
LOG.error("Unsupported erase in line mode:" + arg);
break;
}
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void deleteCharacters(int count) {
myBackBuffer.lock();
try {
final int extent = Math.min(count, myTerminalWidth - myCursorX);
myBackBuffer.deleteCharacters(myCursorX, myCursorY - 1, extent);
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void insertBlankCharacters(int count) {
myBackBuffer.lock();
try {
final int extent = Math.min(count, myTerminalWidth - myCursorX);
myBackBuffer.insertBlankCharacters(myCursorX, myCursorY - 1, extent);
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void eraseCharacters(int count) {
//Clear the next n characters on the cursor's line, including the cursor's
//position.
myBackBuffer.lock();
try {
final int extent = Math.min(count, myTerminalWidth - myCursorX);
myBackBuffer.eraseCharacters(myCursorX, myCursorX + extent, myCursorY - 1);
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void clearTabStopAtCursor() {
myTabulator.clearTabStop(myCursorX);
}
@Override
public void clearAllTabStops() {
myTabulator.clearAllTabStops();
}
@Override
public void setTabStopAtCursor() {
myTabulator.setTabStop(myCursorX);
}
@Override
public void insertLines(int count) {
myBackBuffer.lock();
try {
myBackBuffer.insertLines(myCursorY - 1, count, myScrollRegionBottom);
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void deleteLines(int count) {
myBackBuffer.lock();
try {
myBackBuffer.deleteLines(myCursorY - 1, count, myScrollRegionBottom);
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void setBlinkingCursor(boolean enabled) {
myDisplay.setBlinkingCursor(enabled);
}
@Override
public void cursorUp(final int countY) {
myBackBuffer.lock();
try {
myCursorY -= countY;
myCursorY = Math.max(myCursorY, scrollingRegionTop());
myDisplay.setCursor(myCursorX, myCursorY);
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void cursorDown(final int dY) {
myBackBuffer.lock();
try {
myCursorY += dY;
myCursorY = Math.min(myCursorY, scrollingRegionBottom());
myDisplay.setCursor(myCursorX, myCursorY);
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void index() {
//Moves the cursor down one line in the
//same column. If the cursor is at the
//bottom margin, the page scrolls up
myBackBuffer.lock();
try {
if (myCursorY == myScrollRegionBottom) {
scrollArea(myScrollRegionTop, scrollingRegionSize(), -1);
}
else {
myCursorY += 1;
myDisplay.setCursor(myCursorX, myCursorY);
}
}
finally {
myBackBuffer.unlock();
}
}
private void scrollArea(int scrollRegionTop, int scrollRegionSize, int dy) {
myDisplay.scrollArea(scrollRegionTop, scrollRegionSize, dy);
myBackBuffer.scrollArea(scrollRegionTop, dy, scrollRegionTop + scrollRegionSize - 1);
}
@Override
public void nextLine() {
myBackBuffer.lock();
try {
myCursorX = 0;
if (myCursorY == myScrollRegionBottom) {
scrollArea(myScrollRegionTop, scrollingRegionSize(), -1);
}
else {
myCursorY += 1;
}
myDisplay.setCursor(myCursorX, myCursorY);
}
finally {
myBackBuffer.unlock();
}
}
private int scrollingRegionSize() {
return myScrollRegionBottom - myScrollRegionTop + 1;
}
@Override
public void reverseIndex() {
//Moves the cursor up one line in the same
//column. If the cursor is at the top margin,
//the page scrolls down.
myBackBuffer.lock();
try {
if (myCursorY == myScrollRegionTop) {
scrollArea(myScrollRegionTop, scrollingRegionSize(), 1);
}
else {
myCursorY -= 1;
myDisplay.setCursor(myCursorX, myCursorY);
}
}
finally {
myBackBuffer.unlock();
}
}
private int scrollingRegionTop() {
return isOriginMode() ? myScrollRegionTop : 1;
}
private int scrollingRegionBottom() {
return isOriginMode() ? myScrollRegionBottom : myTerminalHeight;
}
@Override
public void cursorForward(final int dX) {
myCursorX += dX;
myCursorX = Math.min(myCursorX, myTerminalWidth - 1);
myDisplay.setCursor(myCursorX, myCursorY);
}
@Override
public void cursorBackward(final int dX) {
myCursorX -= dX;
myCursorX = Math.max(myCursorX, 0);
myDisplay.setCursor(myCursorX, myCursorY);
}
@Override
public void cursorHorizontalAbsolute(int x) {
cursorPosition(x, myCursorY);
}
@Override
public void linePositionAbsolute(int y) {
myCursorY = y;
myDisplay.setCursor(myCursorX, myCursorY);
}
@Override
public void cursorPosition(int x, int y) {
if (isOriginMode()) {
myCursorY = y + scrollingRegionTop() - 1;
}
else {
myCursorY = y;
}
if (myCursorY > scrollingRegionBottom()) {
myCursorY = scrollingRegionBottom();
}
// avoid issue due to malformed sequence
myCursorX = Math.max(0, x - 1);
myCursorX = Math.min(myCursorX, myTerminalWidth - 1);
myDisplay.setCursor(myCursorX, myCursorY);
}
@Override
public void setScrollingRegion(int top, int bottom) {
if (top > bottom) {
LOG.error("Top margin of scroll region can't be greater then bottom: " + top + ">" + bottom);
}
myScrollRegionTop = Math.max(1, top);
myScrollRegionBottom = Math.min(myTerminalHeight, bottom);
//DECSTBM moves the cursor to column 1, line 1 of the page
cursorPosition(1, 1);
}
@Override
public void scrollUp(int count) {
myBackBuffer.lock();
try {
scrollArea(myScrollRegionTop, scrollingRegionSize(), -count);
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void scrollDown(int count) {
myBackBuffer.lock();
try {
scrollArea(myScrollRegionTop, scrollingRegionSize(), count);
}
finally {
myBackBuffer.unlock();
}
}
@Override
public void resetScrollRegions() {
setScrollingRegion(1, myTerminalHeight);
}
@Override
public void characterAttributes(final TextStyle textStyle) {
myStyleState.setCurrent(textStyle);
}
@Override
public void beep() {
myDisplay.beep();
}
@Override
public int distanceToLineEnd() {
return myTerminalWidth - myCursorX;
}
@Override
public void saveCursor() {
myStoredCursor = createCursorState();
}
private StoredCursor createCursorState() {
return new StoredCursor(myCursorX, myCursorY, myStyleState.getCurrent().clone(),
isAutoWrap(), isOriginMode(), myGraphicSetState);
}
@Override
public void restoreCursor() {
if (myStoredCursor != null) {
restoreCursor(myStoredCursor);
}
else { //If nothing was saved by DECSC
setModeEnabled(TerminalMode.OriginMode, false); //Resets origin mode (DECOM)
cursorPosition(1, 1); //Moves the cursor to the home position (upper left of screen).
myStyleState.reset(); //Turns all character attributes off (normal setting).
myGraphicSetState.resetState();
//myGraphicSetState.designateGraphicSet(0, CharacterSet.ASCII);//Maps the ASCII character set into GL
//mapCharsetToGL(0);
//myGraphicSetState.designateGraphicSet(1, CharacterSet.DEC_SUPPLEMENTAL);
//mapCharsetToGR(1); //and the DEC Supplemental Graphic set into GR
}
myDisplay.setCursor(myCursorX, myCursorY);
}
public void restoreCursor(@NotNull StoredCursor storedCursor) {
myCursorX = storedCursor.getCursorX();
myCursorY = storedCursor.getCursorY();
myStyleState.setCurrent(storedCursor.getTextStyle().clone());
setModeEnabled(TerminalMode.AutoWrap, storedCursor.isAutoWrap());
setModeEnabled(TerminalMode.OriginMode, storedCursor.isOriginMode());
CharacterSet[] designations = storedCursor.getDesignations();
for (int i = 0; i < designations.length; i++) {
myGraphicSetState.designateGraphicSet(i, designations[i]);
}
myGraphicSetState.setGL(storedCursor.getGLMapping());
myGraphicSetState.setGR(storedCursor.getGRMapping());
if (storedCursor.getGLOverride() >= 0) {
myGraphicSetState.overrideGL(storedCursor.getGLOverride());
}
}
@Override
public void reset() {
myGraphicSetState.resetState();
myStyleState.reset();
myBackBuffer.clearAll();
myDisplay.setScrollingEnabled(true);
initModes();
initMouseModes();
cursorPosition(1, 1);
}
private void initMouseModes() {
setMouseMode(MouseMode.MOUSE_REPORTING_NONE);
setMouseFormat(MouseFormat.MOUSE_FORMAT_XTERM);
}
private void initModes() {
myModes.clear();
setModeEnabled(TerminalMode.AutoWrap, true);
setModeEnabled(TerminalMode.AutoNewLine, false);
setModeEnabled(TerminalMode.CursorVisible, true);
setModeEnabled(TerminalMode.CursorBlinking, true);
}
public boolean isAutoNewLine() {
return myModes.contains(TerminalMode.AutoNewLine);
}
public boolean isOriginMode() {
return myModes.contains(TerminalMode.OriginMode);
}
public boolean isAutoWrap() {
return myModes.contains(TerminalMode.AutoWrap);
}
private static int createButtonCode(MouseEvent event) {
if ((event.getButton()) == MouseEvent.BUTTON1) {
return MouseButtonCodes.LEFT;
}
else if (event.getButton() == MouseEvent.BUTTON3) {
return MouseButtonCodes.NONE; //we dont handle right mouse button as it used for the context menu invocation
}
else {
return MouseButtonCodes.RIGHT;
}
}
private byte[] mouseReport(int button, int x, int y) {
StringBuilder sb = new StringBuilder();
switch (myMouseFormat) {
case MOUSE_FORMAT_XTERM_EXT:
sb.append(String.format("\033[M%c%c%c",
(char)(32 + button),
(char)(32 + x),
(char)(32 + y)));
break;
case MOUSE_FORMAT_URXVT:
sb.append(String.format("\033[%d;%d;%dM", 32 + button, x, y));
break;
case MOUSE_FORMAT_SGR:
if ((button & MouseButtonModifierFlags.MOUSE_BUTTON_SGR_RELEASE_FLAG) != 0) {
// for mouse release event
sb.append(String.format("\033[<%d;%d;%dm",
button ^ MouseButtonModifierFlags.MOUSE_BUTTON_SGR_RELEASE_FLAG,
x,
y));
}
else {
// for mouse press/motion event
sb.append(String.format("\033[<%d;%d;%dM", button, x, y));
}
break;
case MOUSE_FORMAT_XTERM:
default:
sb.append(String.format("\033[M%c%c%c", (char)(32 + button), (char)(32 + x), (char)(32 + y)));
break;
}
return sb.toString().getBytes(Charset.forName("UTF-8"));
}
private boolean shouldSendMouseData() {
return myTerminalOutput != null &&
(myMouseMode == MouseMode.MOUSE_REPORTING_NORMAL || myMouseMode == MouseMode.MOUSE_REPORTING_ALL_MOTION ||
myMouseMode == MouseMode.MOUSE_REPORTING_BUTTON_MOTION);
}
@Override
public void mousePressed(int x, int y, MouseEvent event) {
if (shouldSendMouseData()) {
int cb = createButtonCode(event);
if (cb != MouseButtonCodes.NONE) {
if (createButtonCode(event) == MouseButtonCodes.SCROLLDOWN || createButtonCode(event) == MouseButtonCodes.SCROLLUP) {
// convert x11 scroll button number to terminal button code
int offset = MouseButtonCodes.SCROLLDOWN;
cb -= offset;
cb |= MouseButtonModifierFlags.MOUSE_BUTTON_SCROLL_FLAG;
}
cb = applyModifierKeys(event, cb);
myTerminalOutput.sendBytes(mouseReport(cb, x + 1, y + 1));
}
}
}
@Override
public void mouseReleased(int x, int y, MouseEvent event) {
if (shouldSendMouseData()) {
int cb = createButtonCode(event);
if (cb != MouseButtonCodes.NONE) {
if (myMouseFormat == MouseFormat.MOUSE_FORMAT_SGR) {
// for SGR 1006 mode
cb |= MouseButtonModifierFlags.MOUSE_BUTTON_SGR_RELEASE_FLAG;
}
else {
// for 1000/1005/1015 mode
cb = MouseButtonCodes.RELEASE;
}
cb = applyModifierKeys(event, cb);
myTerminalOutput.sendBytes(mouseReport(cb, x + 1, y + 1));
}
}
}
private static int applyModifierKeys(MouseEvent event, int cb) {
if (event.isControlDown()) {
cb |= MouseButtonModifierFlags.MOUSE_BUTTON_CTRL_FLAG;
}
if (event.isShiftDown()) {
cb |= MouseButtonModifierFlags.MOUSE_BUTTON_SHIFT_FLAG;
}
if ((event.getModifiersEx() & InputEvent.META_MASK) != 0) {
cb |= MouseButtonModifierFlags.MOUSE_BUTTON_META_FLAG;
}
return cb;
}
public void setTerminalOutput(TerminalOutputStream terminalOutput) {
myTerminalOutput = terminalOutput;
}
@Override
public void setMouseMode(@NotNull MouseMode mode) {
myMouseMode = mode;
myDisplay.terminalMouseModeSet(mode);
}
@Override
public void setMouseFormat(MouseFormat mouseFormat) {
myMouseFormat = mouseFormat;
}
public interface ResizeHandler {
void sizeUpdated(int termWidth, int termHeight, int cursorY);
}
public Dimension resize(final Dimension pendingResize, final RequestOrigin origin) {
final int oldHeight = myTerminalHeight;
if (pendingResize.width <= MIN_WIDTH) {
pendingResize.setSize(MIN_WIDTH, pendingResize.height);
}
final Dimension pixelSize = myDisplay.requestResize(pendingResize, origin, myCursorY, new ResizeHandler() {
@Override
public void sizeUpdated(int termWidth, int termHeight, int cursorY) {
myTerminalWidth = termWidth;
myTerminalHeight = termHeight;
myCursorY = cursorY;
myCursorX = Math.min(myCursorX, myTerminalWidth - 1);
myTabulator.resize(myTerminalWidth);
}
});
myScrollRegionBottom += myTerminalHeight - oldHeight;
return pixelSize;
}
@Override
public void fillScreen(final char c) {
myBackBuffer.lock();
try {
final char[] chars = new char[myTerminalWidth];
Arrays.fill(chars, c);
final String str = new String(chars);
for (int row = 1; row <= myTerminalHeight; row++) {
myBackBuffer.writeString(0, row, str);
}
}
finally {
myBackBuffer.unlock();
}
}
@Override
public int getTerminalHeight() {
return myTerminalHeight;
}
@Override
public int getCursorX() {
return myCursorX + 1;
}
@Override
public int getCursorY() {
return myCursorY;
}
@Override
public StyleState getStyleState() {
return myStyleState;
}
private static class DefaultTabulator implements Tabulator {
private static final int TAB_LENGTH = 8;
private final SortedSet<Integer> myTabStops;
private int myWidth;
private int myTabLength;
public DefaultTabulator(int width) {
this(width, TAB_LENGTH);
}
public DefaultTabulator(int width, int tabLength) {
myTabStops = new TreeSet<Integer>();
myWidth = width;
myTabLength = tabLength;
initTabStops(width, tabLength);
}
private void initTabStops(int columns, int tabLength) {
for (int i = tabLength; i < columns; i += tabLength) {
myTabStops.add(i);
}
}
public void resize(int columns) {
if (columns > myWidth) {
for (int i = myTabLength * (myWidth / myTabLength); i < columns; i += myTabLength) {
if (i >= myWidth) {
myTabStops.add(i);
}
}
}
else {
Iterator<Integer> it = myTabStops.iterator();
while (it.hasNext()) {
int i = it.next();
if (i > columns) {
it.remove();
}
}
}
myWidth = columns;
}
@Override
public void clearTabStop(int position) {
myTabStops.remove(Integer.valueOf(position));
}
@Override
public void clearAllTabStops() {
myTabStops.clear();
}
@Override
public int getNextTabWidth(int position) {
return nextTab(position) - position;
}
@Override
public int getPreviousTabWidth(int position) {
return position - previousTab(position);
}
@Override
public int nextTab(int position) {
int tabStop = Integer.MAX_VALUE;
// Search for the first tab stop after the given position...
SortedSet<Integer> tailSet = myTabStops.tailSet(position + 1);
if (!tailSet.isEmpty()) {
tabStop = tailSet.first();
}
// Don't go beyond the end of the line...
return Math.min(tabStop, (myWidth - 1));
}
@Override
public int previousTab(int position) {
int tabStop = 0;
// Search for the first tab stop before the given position...
SortedSet<Integer> headSet = myTabStops.headSet(Integer.valueOf(position));
if (!headSet.isEmpty()) {
tabStop = headSet.last();
}
// Don't go beyond the start of the line...
return Math.max(0, tabStop);
}
@Override
public void setTabStop(int position) {
myTabStops.add(Integer.valueOf(position));
}
}
}