com.redhat.ceylon.common.tool.WordWrap Maven / Gradle / Ivy
package com.redhat.ceylon.common.tool;
import java.io.Flushable;
import java.io.IOException;
import java.text.BreakIterator;
import java.util.TreeSet;
/**
* Facility for producing nicely word-wrapped output.
* @author tom
*/
public class WordWrap {
private class LineOutput {
private final Appendable out;
/** The current column */
private int pos = 0;
/** true if we're at the beginning of a line*/
private boolean bol = true;
/** true if this is a first line */
private boolean fl = true;
private String prefix = null;
public LineOutput(Appendable out) {
this.out = out;
}
public void append(String word) {
try {
if (bol) {
spaces(fl ? indentFirstLine : indentRestLines);
prefix();
}
out.append(word);
} catch (IOException e) {
// ignore it
}
pos+=word.length();
}
private void prefix() throws IOException {
if (prefix != null) {
out.append(prefix);
pos += prefix.length();
bol = pos == 0;
}
}
private void newline(boolean fl) {
try {
out.append(System.getProperty("line.separator"));
pos = 0;
bol = true;
this.fl = fl;
} catch (IOException e) {
// ignore it
}
}
private void newlineHard() {
newline(true);
}
private void newlineSoft() {
newline(false);
}
private void spaces(int num) {
for (int ii = 0; ii < num; ii++) {
bol = false;
try {
out.append(' ');
} catch (IOException e) {
// ignore it
}
}
pos += num;
}
public void spacesToColumn(int column) {
spaces(Math.max(column - pos, 0));
}
public int column() {
return pos;
}
}
private final int width;
private int rightIndent = 0;
private TreeSet tabstops = new TreeSet();
private int indentFirstLine;
private int indentRestLines;
private final LineOutput out;
/**
* Creates a wordwrap instance wrapping
* {@linkplain #System.out standard output}.
*/
public WordWrap() {
this(System.out);
}
public WordWrap(Appendable out) {
this(out, Integer.getInteger("com.redhat.ceylon.common.tool.terminal.width", 80));
}
public WordWrap(Appendable out, int width) {
if (width <= 0) {
throw new IllegalArgumentException();
}
this.width = width;
this.out = new LineOutput(out);
}
private void boundsCheck(int column) {
if (column < 0 || column >= width) {
throw new IllegalArgumentException();
}
}
/**
* Sets the indentation level. Equivalent to calling
* {@link #setIndentFirstLine(int) setIndentFirstLine(indent)} followed by
* {@link #setIndentRestLines(int) setIndentRestLines(indent)}.
*/
public void setIndent(int indent) {
setIndentFirstLine(indent);
setIndentRestLines(indent);
}
/**
* Sets the indentation level of first lines
* @see #setIndentRestLines(int)
*/
public void setIndentFirstLine(int indent) {
boundsCheck(indent);
this.indentFirstLine = indent;
}
public int getIndentFirstLine() {
return this.indentFirstLine;
}
/**
* Sets the indentation level of non-first lines
* @see #setIndentFirstLines(int)
*/
public void setIndentRestLines(int indent) {
boundsCheck(indent);
this.indentRestLines = indent;
}
public int getIndentRestLines() {
return this.indentRestLines;
}
/**
* Sets the right margin
*/
public void setRightIndent(int indent) {
boundsCheck(indent);
this.rightIndent = indent;
}
public int getRightIndent() {
return this.rightIndent;
}
public void setPrefix(String prefix) {
out.prefix = prefix;
}
public String getPrefix() {
return out.prefix;
}
public int getWidth() {
return width;
}
/**
* Adds a tab stop.
* @param stop
* @see #tab()
* @see #removeTabStop(int)
*/
public void addTabStop(int stop) {
boundsCheck(stop);
tabstops.add(stop);
}
/**
* Removes a tab stop.
* @param stop
*/
public void removeTabStop(int stop) {
tabstops.remove(stop);
}
/**
* Clears all tab stops.
* @param stop
*/
public void clearTabStops() {
tabstops.clear();
}
private String collapseWs(String s) {
return s.replaceAll("\\s+", " ");
}
private String trimRight(String s) {
return s.replaceAll("\\s+$", "");
}
private String prepend = "";
/**
* Appends the given string to the output.
* @param s The string.
*/
public WordWrap append(String s) {
s = collapseWs(s);
String prependNext = s.endsWith(" ") ? " " : "";
s = prepend + trimRight(s);
prepend = prependNext;
// TODO Some control over soft and hard hyphens
// spaces and newlines
BreakIterator bi = BreakIterator.getLineInstance();
bi.setText(s);
int endOfLast = 0;
int ii;
// iterate forwards through characters...
for (ii = 0; ii < s.length(); ii++) {
// ...until the line is too long...
if (exceedsWidth(endOfLast, ii)) {
endOfLast = addLineBreak(s, bi, endOfLast, ii);
}
}
if (exceedsWidth(endOfLast, ii)) {
ii--;
endOfLast = addLineBreak(s, bi, endOfLast, ii);
}
s = s.substring(endOfLast);
out.append(s);
return this;
}
private int addLineBreak(String s, BreakIterator bi, int endOfLast, int ii) {
int safe = ii;
while (!bi.isBoundary(ii)) {
ii--;
if (ii < endOfLast) {
ii = safe;
break;
}
}
out.append(trimRight(s.substring(endOfLast, ii)));
out.newlineSoft();
endOfLast = ii;
return endOfLast;
}
private boolean exceedsWidth(int endOfLast, int ii) {
return out.column() + (ii-endOfLast) >= width-rightIndent;
}
/**
* Prints a hard new line (in other words, starts a new paragraph).
*/
public WordWrap newline() {
this.prepend = "";
out.newlineHard();
return this;
}
/**
* Moves to the next tab stop, that is the one to the right of the
* {@linkplain #getColumn() current column}.
* @see #addTabStop(int)
*/
public WordWrap tab() {
out.spacesToColumn(tabstops.higher(out.column()));
return this;
}
/**
* If {@link #getColumn()}{@code < column} then writes sufficient spaces
* so that the next chararacter will appear in that column.
* Otherwise does nothing.
* @param column
* @return
*/
public WordWrap column(int column) {
out.spacesToColumn(column);
return this;
}
/**
* Gets the column position where the next character will be written
* @return
*/
public int getColumn() {
return out.column();
}
/**
* Flushes the output, if it is {@link Flushable}.
*/
public void flush() {
try {
if (out instanceof Flushable) {
((Flushable)out).flush();
}
} catch (IOException e) {
// ignore
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy