package org.xpect.text;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import org.eclipse.xtext.util.Strings;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
*
* @author Moritz Eysholdt
*/
public class Text {
private String nl;
private final CharSequence text;
public Text(CharSequence text) {
Preconditions.checkNotNull(text);
this.text = text;
}
public char charAt(int c) {
return text.charAt(c);
}
public int currentLineEnd(int offset) {
int nl = indexOf('\n', offset);
if (nl > 0)
return text.charAt(nl - 1) == '\r' ? nl - 1 : nl;
return text.length();
}
public int currentLineEndLenght(int offset) {
int nl = indexOf('\n', offset);
if (nl > 0)
return text.charAt(nl - 1) == '\r' ? 2 : 1;
return 0;
}
public int currentLineStart(int offset) {
return lastIndexOf('\n', offset) + 1;
}
protected String determineNL() {
boolean lastIsR = false;
for (int i = 0; i < text.length(); i++)
switch (text.charAt(i)) {
case '\r':
lastIsR = true;
break;
case '\n':
return lastIsR ? "\r\n" : "\n";
default:
lastIsR = false;
}
return "\n";
}
public String escapeNewLines() {
return text.toString().replace("\n", "\\n").replace("\r", "\\r");
}
public String findIndentation(int offset) {
int nl = text.toString().lastIndexOf("\n", offset);
if (nl < 0)
nl = 0;
StringBuilder result = new StringBuilder();
for (int i = nl + 1; i < text.length() && Character.isWhitespace(text.charAt(i)) && text.charAt(i) != '\n'; i++)
result.append(text.charAt(i));
return result.toString();
}
public String findIndentation(String prefix, int offset, int end) {
if (end <= offset)
return "";
prefix = trimRight(prefix);
List<String> prefixed = Lists.newArrayList(), unprefixed = Lists.newArrayList();
for (String line : new Text(text.subSequence(offset, end)).splitIntoLines())
if (line.startsWith(prefix))
prefixed.add(line.substring(prefix.length()));
else
unprefixed.add(line);
if (prefix.isEmpty() && unprefixed.isEmpty())
return "";
List<String> lines = prefixed.size() > unprefixed.size() ? prefixed : unprefixed;
StringBuilder result = new StringBuilder(prefixed.size() > unprefixed.size() ? prefix : "");
for (int i = 0; true; i++) {
String first = lines.get(0);
if (i >= first.length())
return result.toString();
char c = first.charAt(i);
if (!Character.isWhitespace(c) || c == '\n')
return result.toString();
for (int j = 1; j < lines.size(); j++) {
String l = lines.get(j);
if (i >= l.length() || c != l.charAt(i))
return result.toString();
}
result.append(c);
}
}
public String getNL() {
if (nl == null)
nl = determineNL();
return nl;
}
public CharSequence getText() {
return text;
}
public String indentWith(String indentation) {
return text.toString().replace("\n", "\n" + indentation);
}
public int indexOf(char c, int fromIndex) {
return text.toString().indexOf(c, fromIndex);
}
public int indexOf(String str, int fromIndex) {
return text.toString().indexOf(str, fromIndex);
}
public boolean isMultiline() {
return indexOf('\n', 0) >= 0;
}
public int lastIndexOf(char c, int fromIndex) {
return text.toString().lastIndexOf(c, fromIndex);
}
public int length() {
return text.length();
}
public int nextLineStart(int offset) {
return indexOf('\n', offset) + 1;
}
public int previousLineEnd(int offset) {
int nl = lastIndexOf('\n', offset);
if (nl > 0)
return text.charAt(nl - 1) == '\r' ? nl - 1 : nl;
return nl;
}
public int previousLineStart(int offset) {
int prevEnd = previousLineEnd(offset);
return currentLineStart(prevEnd - 1);
}
public IReplacement replacementTo(String other) {
if (Strings.isEmpty(other))
return null;
int prefix = 0;
while (text.charAt(prefix) == other.charAt(prefix)) {
prefix++;
if (prefix >= text.length() || prefix >= other.length())
return null;
}
int suffix = 1;
while (text.charAt(text.length() - suffix) == other.charAt(other.length() - suffix)) {
suffix++;
if (suffix > text.length() || suffix > other.length())
return null;
}
int length = text.length() - prefix - suffix + 1;
int endIndex = other.length() - suffix + 1;
String replacement = prefix < endIndex ? other.substring(prefix, endIndex) : "";
return new Replacement(text, prefix, length, replacement);
}
public List<String> splitIntoLines() {
// ... because string.split() ignores trailing whitespace (wtf!)
List<String> result = Lists.newArrayList();
String document = text.toString();
int index, lastIndex = 0;
while ((index = document.indexOf('\n', lastIndex)) >= 0) {
int end = index > 0 && document.charAt(index - 1) == '\r' ? index - 1 : index;
result.add(document.substring(lastIndex, end));
lastIndex = index + 1;
}
result.add(document.substring(lastIndex, document.length()));
return ImmutableList.copyOf(result);
}
public String substring(int beginIndex, int endIndex) {
return text.toString().substring(beginIndex, endIndex);
}
@Override
public String toString() {
return text.toString();
}
protected String trimRight(String str) {
int i = str.length() - 1;
while (i >= 0 && Character.isWhitespace(str.charAt(i)))
i--;
return str.substring(0, i + 1);
}
public String with(Collection<IReplacement> replacements) {
if (replacements.isEmpty())
return text.toString();
Set<IReplacement> sortedReplacements = Sets.newTreeSet(new RegionOffsetComparator());
for (IReplacement rep : replacements)
if (!sortedReplacements.add(rep))
throw new IllegalStateException("Multiple replacements for same offset");
int last = 0;
StringBuilder result = new StringBuilder();
for (IReplacement rep : sortedReplacements) {
if (rep.getOffset() < last)
throw new IllegalStateException("Overlapping replacements");
result.append(text.toString().substring(last, rep.getOffset()));
result.append(rep.getReplacement());
last = rep.getOffset() + rep.getLength();
}
if (last < text.length())
result.append(text.toString().substring(last));
return result.toString();
}
public String with(IChange change) {
if (change instanceof IPatch) {
List<IReplacement> replacements = Lists.newArrayList();
Stack<IPatch> patches = new Stack<IPatch>();
patches.push((IPatch) change);
while (!patches.isEmpty()) {
IPatch p = patches.pop();
for (IChange c : p.getChanges())
if (c instanceof IPatch)
patches.push((IPatch) c);
else if (c instanceof IReplacement)
replacements.add((IReplacement) c);
}
return with(replacements);
} else if (change instanceof IReplacement) {
return with(Collections.singleton((IReplacement) change));
}
throw new IllegalStateException();
}
}