blob: 399eb2156ba2d224aad232cd1ef1b940cefbfd0a [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* "Line.java"
* Line.java 1.12 01/07/24
*/
package org.netbeans.lib.terminalemulator;
final class Line {
public int glyph_glyph;
public int glyph_rendition; // Background color for the whole line
// This is independent of per-character
// rendition.
private char buf[]; // actual characters
private int attr[]; // attributes (allocated on demand)
// SHOULD use shorts?
private int capacity; // == buf.length == attr.length
private int length; // how much of buf and attr is filled
public Line() {
reset();
}
public void reset() {
length = 0;
capacity = 32;
buf = new char[capacity];
attr = null;
glyphId = 0;
backgroundColor = 0;
wrapped = false;
about_to_wrap = false;
}
public void reset(Term term, int end, int eraseAttr) {
reset();
clearToEndFrom(term, 0, end, eraseAttr);
}
public int capacity() {
return capacity;
}
/**
* Number of characters in the line.
* charArray()[length()] is where the cursor or newline would be.
*
*/
public int length() {
return length;
}
public boolean hasAttributes() {
return attr != null;
}
private int glyphId;
public void setGlyphId(int glyphId) {
this.glyphId = glyphId;
}
public int getGlyphId() {
return glyphId;
}
private int backgroundColor;// Background color for the whole line
// This is independent of per-character
// rendition.
public void setBackgroundColor(int bgColor) {
backgroundColor = bgColor;
}
public int getBackgroundColor() {
return backgroundColor;
}
public void setWrapped(boolean wrapped) {
this.wrapped = wrapped;
}
public boolean isWrapped() {
return wrapped;
}
// SHOULD collapse wrapped with about_to_wrap into a bitfield
private boolean wrapped;
public boolean setAboutToWrap(boolean about_to_wrap) {
boolean old_about_to_wrap = about_to_wrap;
this.about_to_wrap = about_to_wrap;
return old_about_to_wrap;
}
public boolean isAboutToWrap() {
return about_to_wrap;
}
// Perhaps SHOULD have a state: normal, about-to-wrap, wrapped.
private boolean about_to_wrap;
/**
* Return true if we've already seen attributes for this line
* or 'a' is the first one, in which case we allocate the 'attr' array.
*/
private boolean haveAttributes(int a) {
if (attr == null && a != 0) {
attr = new int[capacity];
}
return attr != null;
}
public void accumulateInto(int bcol, int ecol, StringBuffer buf) {
buf.append(this.buf, bcol, ecol-bcol+1);
}
public char charAt(int col) {
return buf[col];
}
private void charAtPut(int col, char c) {
buf[col] = c;
}
@Override
public String toString() {
assert false;
return new String(buf);
}
public char[] getChars(char[] array) {
System.arraycopy(buf, 0, array, 0, length);
return array;
}
public int [] attrArray() {
return attr;
}
public byte width(MyFontMetrics metrics, int at) {
if (at >= capacity)
return 1;
return (byte) metrics.wcwidth(charAt(at));
}
/*
* Convert a cell column to a buffer column.
*/
public int cellToBuf(MyFontMetrics metrics, int target_col) {
if (metrics.isMultiCell()) {
int bc = 0;
int vc = 0;
for(;;) {
int w = width(metrics, bc);
if (vc + w - 1 >= target_col)
break;
vc += w;
bc++;
}
return bc;
} else {
return target_col;
}
}
/*
* Convert a buffer column to a cell column.
*/
public int bufToCell(MyFontMetrics metrics, int target_col) {
if (metrics.isMultiCell()) {
int vc = 0;
for (int bc = 0; bc < target_col; bc++) {
vc += width(metrics, bc);
}
return vc;
} else {
return target_col;
}
}
public StringBuffer stringBuffer() {
// only used for word finding
// Grrr, why don't we have: new StringBuffer(buf, 0, length);
// This gets passed on to WordDelineator which is part of the public API
@SuppressWarnings("StringBufferMayBeStringBuilder")
StringBuffer sb = new StringBuffer(length);
return sb.append(buf, 0, length);
}
/*
* Ensure that we have at least 'min_capacity'.
*/
private void ensureCapacity(Term term, int min_capacity) {
term.noteColumn(this, min_capacity);
if (min_capacity <= capacity)
return; // nothing to do
// doubling
int new_capacity = (length+1) * 2;
if (new_capacity < 0)
new_capacity = Integer.MAX_VALUE;
else if (min_capacity > new_capacity)
new_capacity = min_capacity;
char new_buf[] = new char[new_capacity];
System.arraycopy(buf, 0, new_buf, 0, length);
buf = new_buf;
if (attr != null) {
int new_attr[] = new int[new_capacity];
System.arraycopy(attr, 0, new_attr, 0, length);
attr = new_attr;
}
capacity = new_capacity;
}
private void fillGap(int col, int eraseAttr) {
// fill any newly opened gap (between length and col) with SP
for (int cx = length; cx < col; cx++)
charAtPut(cx, ' ');
if (haveAttributes(eraseAttr)) {
for (int cx = length; cx < col; cx++)
attr[col] = eraseAttr;
}
}
/**
* Insert character and attribute at 'column' and shift everything
* past 'column' right.
*/
public void insertCharAt(Term term, char c, int column, int a) {
int new_length = length + 1;
if (column >= length) {
new_length = column+1;
ensureCapacity(term, new_length);
fillGap(column, 0); // SHOULD be eraseAttr?
} else {
ensureCapacity(term, new_length);
System.arraycopy(buf, column, buf, column + 1, length - column);
if (haveAttributes(a))
System.arraycopy(attr, column, attr, column + 1, length - column);
}
term.checkForMultiCell(c);
charAtPut(column, c);
if (haveAttributes(a))
attr[column] = a;
length = new_length;
}
/*
* Generic addition and modification.
* Line will grow to accomodate column.
*/
public void setCharAt(Term term, char c, int column, int a, int eraseAttr) {
if (column >= length) {
ensureCapacity(term, column+1);
fillGap(column, eraseAttr);
length = column+1;
}
term.checkForMultiCell(c);
if (column < 0) {
throw new IllegalArgumentException(String.format("column=%d; buf_length=%d; buf_capacity=%d", column, length, capacity)); //NOI18N
}
charAtPut(column, c);
if (haveAttributes(a)) {
attr[column] = a;
}
}
public void deleteCharAt(int column) {
if (column < 0 || column >= length)
return;
System.arraycopy(buf, column+1, buf, column, length-column-1);
charAtPut(length-1, (char) 0);
if (attr != null) {
System.arraycopy(attr, column+1, attr, column, length-column-1);
attr[length-1] = 0;
}
// SHOULD move this up
length--;
}
public void clearToEndFrom(Term term, int from, int end, int eraseAttr) {
ensureCapacity(term, end+1);
fillGap(end, eraseAttr);
// Grrr, why is there a System.arrayCopy() but no System.arrayClear()?
for (int cx = from; cx <= end; cx++)
charAtPut(cx, ' ');
if (haveAttributes(eraseAttr)) {
for (int cx = from; cx <= end; cx++)
attr[cx] = eraseAttr;
}
length = end+1;
}
public void clearTo(Term term, int col, int eraseAttr) {
ensureCapacity(term, col+1);
fillGap(col, eraseAttr);
for (int cx = 0; cx <= col; cx++)
charAtPut(cx, ' ');
if (haveAttributes(eraseAttr)) {
for (int cx = 0; cx <= col; cx++)
attr[cx] = eraseAttr;
}
if (length < col+1)
length = col+1;
}
public void clearFromTo(Term term, int from, int to, int eraseAttr) {
if (from > to)
return;
ensureCapacity(term, to+1);
fillGap(to, eraseAttr);
for (int cx = from; cx <= to; cx++)
charAtPut(cx, ' ');
if (haveAttributes(eraseAttr)) {
for (int cx = from; cx <= to; cx++)
attr[cx] = eraseAttr;
}
if (length < to+1)
length = to+1;
}
/*
* Used for selections
* If the ecol is past the actual line length a "\n" is appended.
*/
@SuppressWarnings("AssignmentToMethodParameter")
public String text(int bcol, int ecol) {
/* DEBUG
System.out.println("Line.text(bcol " + bcol + " ecol " + ecol + ")"); // NOI18N
System.out.println("\tlength " + length); // NOI18N
*/
String newline = ""; // NOI18N
// This only happens for 'empty' lines below the cursor.
// Actually it also happens for 'empty' lines in the middle.
// See issue 31951 for example.
// So in order to get newlines in selected text we will also get
// newlines from the 'empty' lines below the cursor.
// This is in fact what xterm does.
// DtTerm doesn't allow selection of the 'empty' lines below the
// cursor, but that is issue 21577 and is not easy to solve.
if (length == 0)
return "\n"; // NOI18N
if (ecol >= length) {
// The -1 snuffs out the newline.
ecol = length-1;
newline = "\n"; // NOI18N
if (bcol >= length)
bcol = length-1;
}
int count = ecol - bcol + 1;
if (bcol < 0 || count < 0) {
throw new IllegalArgumentException(String.format("offset=%d; count=%d; buf_length=%d; buf_capacity=%d", bcol, count, length, capacity)); //NOI18N
}
return new String(buf, bcol, count) + newline;
}
public void setCharacterAttribute(int bcol, int ecol,
int value, boolean on) {
// HACK: value is the ANSI code, haveAttributes takes out own
// compact encoding, but it only checks for 0 so it's OK.
if (!haveAttributes(value))
return;
try {
if (on) {
for (int c = bcol; c <= ecol; c++)
attr[c] = Attr.setAttribute(attr[c], value);
} else {
for (int c = bcol; c <= ecol; c++)
attr[c] = Attr.unsetAttribute(attr[c], value);
}
} catch (ArrayIndexOutOfBoundsException x) {
System.out.printf("bcol %d ecol %d capacity %d length %d buf %d attr %d", // NOI18N
bcol, ecol, capacity, length, buf.length, attr.length);
throw x;
}
}
}