blob: 7ce4e8f7548d1f0c26abcf952b38380571a924f8 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2015, 2018 David Green and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# David Green - org.eclipse.mylyn.docs: initial API and implementation
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.docmlet.wikitext.commonmark.core;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.regex.Matcher;
import org.eclipse.statet.jcommons.text.core.TextRegion;
public class Line implements TextRegion {
private static class LazyLine extends Line {
public LazyLine(final int lineNumber, final int offset, final int column,
final String text, final String lineDelimiter,
final int indentColumns, final int indentLength) {
super(lineNumber, offset, column, text, lineDelimiter, indentColumns, indentLength);
}
@Override
public boolean isLazy() {
return true;
}
@Override
public LazyLine lazy() {
return this;
}
}
private static final String[] SPACES= new String[] {
"", " ", " ", " ", " "
};
private final int startOffset;
private final int column;
private final String text;
private int indentColumns= -1;
private int indentLength= -1;
private final int lineNumber;
private final String lineDelimiter;
public Line(final int lineNumber, final int offset, final int column,
final String text, final String lineDelimiter) {
if (lineNumber < 0) {
throw new IllegalArgumentException("lineNumber= " + lineNumber);
}
if (offset < 0) {
throw new IllegalArgumentException("startOffset= " + offset);
}
if (column < 0) {
throw new IllegalArgumentException("column= " + column);
}
this.lineNumber= lineNumber;
this.startOffset= offset;
this.column= column;
this.text= checkNotNull(text);
this.lineDelimiter= checkNotNull(lineDelimiter);
computeIndent();
}
private Line(final int lineNumber, final int offset, final int column,
final String text, final String lineDelimiter,
final int indentColumns, final int indentLength) {
this.lineNumber= lineNumber;
this.startOffset= offset;
this.column= column;
this.text= text;
this.lineDelimiter= lineDelimiter;
this.indentColumns= indentColumns;
this.indentLength= indentLength;
}
public boolean isBlank() {
return (this.text.length() == this.indentLength);
}
public String getText() {
return this.text;
}
/**
* Provides the 0-based startOffset of the first character of the line.
*
* @return the line startOffset
*/
@Override
public int getStartOffset() {
return this.startOffset;
}
@Override
public int getEndOffset() {
return this.startOffset + getLength();
}
/**
* Returns the length of the line including the line delimiter.
*
* @return the length
*/
@Override
public int getLength() {
return this.text.length() + this.lineDelimiter.length();
}
/**
* Provides the 0-based column of the first character of the line.
*
* @return the line column
*/
public int getColumn() {
return this.column;
}
/**
* Returns the 0-based column of the character at the specified offset.
*
* @return the column
*/
public int getColumn(final int offset) {
return (offset == this.indentLength) ?
(this.column + this.indentColumns) : computeColumn(offset);
}
/**
* Returns the indent by space and tab chars at the start of this line.
*
* @return the indent in columns
*/
public int getIndent() {
return this.indentColumns;
}
public int getIndentLength() {
return this.indentLength;
}
public String getTextContent(final boolean trimEnd) {
return (trimEnd) ?
this.text.substring(this.indentLength, computeTrimEnd()) :
this.text.substring(this.indentLength);
}
public int getTextContentOffset() {
return this.startOffset + this.indentLength;
}
public int getTextContentLength() {
return this.text.length() - this.indentLength;
}
public String getCodeContent() {
if (this.text.isEmpty()
|| this.indentLength == 0
|| this.column % 4 == 0
|| this.text.charAt(0) != '\t') {
return this.text;
}
return SPACES[this.column % 4] + this.text.substring(1);
}
public String getLineDelimiter() {
return this.lineDelimiter;
}
/**
* Provides the 0-based line number.
*
* @return the line number
*/
public int getLineNumber() {
return this.lineNumber;
}
/**
* Provides a segment of this line, with {@link #getText() text}.
*
* @param offset
* the 0-based offset of the {@link #getText() text} of this line
* @param length
* the length of the {@link #getText() text} from the given {@code offset}
* @return the segment
*/
public Line segment(final int offset, final int length) {
if (offset < 0 || offset > this.text.length()) {
throw new IllegalArgumentException("offset= " + offset);
}
if (length < 0 || length > this.text.length() - offset) {
throw new IllegalArgumentException("length= " + length);
}
final int offsetColumn= getColumn(offset);
return new Line(this.lineNumber, this.startOffset + offset, offsetColumn,
this.text.substring(offset, offset + length),
(offset + length == this.text.length()) ? this.lineDelimiter : "" );
}
public Line segmentByIndent(final int indent) {
final int indentLength= (indent == this.indentColumns) ?
this.indentLength : computeLength(indent);
if (indentLength == -1) {
return segment(this.text.length(), 0);
}
if (indent <= this.indentColumns) {
return new Line(this.lineNumber, this.startOffset + indentLength, this.column + indent,
this.text.substring(indentLength), this.lineDelimiter,
this.indentColumns - indent,
this.indentLength - indentLength );
}
else {
return new Line(this.lineNumber, this.startOffset + indentLength, this.column + indent,
this.text.substring(indentLength), this.lineDelimiter );
}
}
public boolean isLazy() {
return false;
}
public Line lazy() {
return new LazyLine(this.lineNumber, this.startOffset, this.column,
this.text, this.lineDelimiter, this.indentColumns, this.indentLength );
}
public Matcher setup(final Matcher matcher) {
return matcher.reset(this.text);
}
public Matcher setupIndent(final Matcher matcher) {
return setup(matcher, true, false);
}
public Matcher setup(final Matcher matcher, final boolean trimIndent, final boolean trimEnd) {
matcher.reset(this.text);
if (trimIndent || trimEnd) {
final int start= (trimIndent) ? getIndentLength() : 0;
final int end= (trimEnd && start < this.text.length()) ? computeTrimEnd() : this.text.length();
if (start != 0 || end != this.text.length()) {
matcher.region(start, end);
}
}
return matcher;
}
private void computeIndent() {
final String text= this.text;
int column= this.column;
int idx= 0;
ITER_CHAR: while (idx < text.length()) {
final char c= text.charAt(idx);
switch (c) {
case ' ':
column++;
idx++;
continue;
case '\t':
column+= 4 - (column % 4);
idx++;
continue;
default:
break ITER_CHAR;
}
}
this.indentColumns= column - this.column;
this.indentLength= idx;
}
private int computeLength(final int indent) {
if (indent == 0) {
return 0;
}
final String text= this.text;
int column= this.column;
int idx= 0;
while (idx < text.length()) {
final char c= text.charAt(idx);
switch (c) {
case '\t':
column+= 4 - (column % 4);
if (column - this.column > indent) {
return idx;
}
if (column - this.column >= indent) {
return idx + 1;
}
idx++;
continue;
default:
column++;
if (column - this.column >= indent) {
return idx + 1;
}
idx++;
continue;
}
}
return -1;
}
private int computeColumn(final int offset) {
assert (offset <= this.text.length());
final String text= this.text;
int column= this.column;
int idx= 0;
while (idx < offset) {
final char c= text.charAt(idx);
switch (c) {
case '\t':
column+= 4 - (column % 4);
idx++;
continue;
default:
column++;
idx++;
continue;
}
}
return column;
}
private int computeTrimEnd() {
final String text= this.text;
for (int idx= text.length() - 1; idx >= 0; idx--) {
switch (text.charAt(idx)) {
case ' ':
case '\t':
continue;
default:
return idx + 1;
}
}
return 0;
}
@Override
public String toString() {
final StringBuilder sb= new StringBuilder("Line");
sb.append(" (" + "lineNumber= ").append(this.lineNumber);
sb.append(", " + "startOffset= ").append(this.startOffset);
sb.append(", " + "column= ").append(this.column);
sb.append(")");
sb.append("\n\t" + "text= ").append(ToStringHelper.toStringValue(this.text));
return sb.toString();
}
}