/*=============================================================================#
# Copyright (c) 2007-2016 Stephan Wahlbrink (WalWare.de) and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.ecommons.text;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.text.AbstractDocument;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPartitioningException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import de.walware.ecommons.text.core.IPartitionConstraint;
/**
* Text utilities, in addition to {@link TextUtilities} of JFace.
*/
public class TextUtil {
public static final Pattern LINE_DELIMITER_PATTERN = Pattern.compile("\\r[\\n]?|\\n"); //$NON-NLS-1$
private static final IScopeContext PLATFORM_SCOPE = InstanceScope.INSTANCE;
private static class PositionComparator implements Comparator<Position> {
@Override
public int compare(final Position o1, final Position o2) {
final int diff = o1.offset - o2.offset;
if (diff != 0) {
return diff;
}
return o1.length - o2.length;
}
}
public static final Comparator<Position> POSITION_COMPARATOR = new PositionComparator();
/**
* Returns the default line delimiter of the Eclipse platform (workbench)
*
* @return the line delimiter string
*/
public static final String getPlatformLineDelimiter() {
final String lineDelimiter = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, null,
new IScopeContext[] { PLATFORM_SCOPE });
if (lineDelimiter != null) {
return lineDelimiter;
}
return System.getProperty("line.separator"); //$NON-NLS-1$
}
/**
* Returns the default line delimiter for the specified project
*
* If it cannot find a project specific setting, it returns the
* {@link #getPlatformLineDelimiter()}
*
* @param project the project handle, may be <code>null</code>
* @return the line delimiter string
*/
public static String getLineDelimiter(final IProject project) {
if (project != null) {
final String lineSeparator = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, null,
new IScopeContext[] { new ProjectScope(project.getProject()), PLATFORM_SCOPE });
if (lineSeparator != null) {
return lineSeparator;
}
}
return getPlatformLineDelimiter();
}
/**
* Return the length of the overlapping length of two regions.
* If they don't overlap, it return the negative distance of the regions.
*/
public static final int overlaps(final int reg1Start, final int reg1End, final int reg2Start, final int reg2End) {
if (reg1Start <= reg2Start) {
if (reg2End < reg1End) {
return reg2End-reg2Start;
}
return reg1End-reg2Start;
}
else {
if (reg1End < reg2End) {
return reg1End-reg1Start;
}
return reg2End-reg1Start;
}
}
/**
* Return the distance of two regions
*/
public final int distance(final int reg1Start, final int reg1End, final int reg2Start, final int reg2End) {
if (reg2Start > reg1End) {
return reg2Start-reg1End;
}
if (reg1Start > reg2End) {
return reg1Start-reg2End;
}
return 0;
}
/**
* Return the distance of a point to the region.
*/
public static final int distance(final IRegion region, final int pointOffset) {
int regPointOffset = region.getOffset();
if (pointOffset < regPointOffset) {
return regPointOffset-pointOffset;
}
regPointOffset += region.getLength();
if (pointOffset > regPointOffset) {
return pointOffset-regPointOffset;
}
return 0;
}
public static ArrayList<String> toLines(final String text) {
final ArrayList<String> lines = new ArrayList<>(2 + text.length() / 30);
TextUtil.addLines(text, lines);
return lines;
}
/**
* Adds text of lines of a string without its line delimiters to the list.
*
* @param text the text
* @param lines list the lines are added to
*/
public static void addLines(final String text, final List<String> lines) {
final int n = text.length();
int i = 0;
int lineStart = 0;
while (i < n) {
switch (text.charAt(i)) {
case '\r':
lines.add(text.substring(lineStart, i));
i++;
if (i < n && text.charAt(i) == '\n') {
i++;
}
lineStart = i;
continue;
case '\n':
lines.add(text.substring(lineStart, i));
i++;
if (i < n && text.charAt(i) == '\r') {
i++;
}
lineStart = i;
continue;
default:
i++;
continue;
}
}
if (lineStart < n) {
lines.add(text.substring(lineStart, n));
}
}
/**
* Adds text of lines of a document without its line delimiters to the list.
*
* The first line begins at <code>offset</code>, the last line ends at <code>offset+length</code>.
* The positions must not be inside a line delimiter (if it consists of multiple chars).
*
* @param document the document
* @param offset the offset of region to include
* @param length the length of region to include
* @param lines list the lines are added to
* @throws BadLocationException
*/
public static final void addLines(final IDocument document, final int offset, final int length,
final ArrayList<String> lines) throws BadLocationException {
final int startLine = document.getLineOfOffset(offset);
final int endLine = document.getLineOfOffset(offset+length);
lines.ensureCapacity(lines.size() + endLine-startLine+1);
IRegion lineInfo;
if (startLine > endLine) {
throw new IllegalArgumentException();
}
if (startLine == endLine) {
lineInfo = document.getLineInformation(endLine);
lines.add(document.get(offset, length));
return;
}
else {
lineInfo = document.getLineInformation(startLine);
lines.add(document.get(offset, Math.max(0, lineInfo.getOffset()+lineInfo.getLength()-offset)));
for (int line = startLine+1; line < endLine; line++) {
lineInfo = document.getLineInformation(line);
lines.add(document.get(lineInfo.getOffset(), lineInfo.getLength()));
}
lineInfo = document.getLineInformation(endLine);
if (offset+length > lineInfo.getOffset()) {
lines.add(document.get(lineInfo.getOffset(), offset+length-lineInfo.getOffset()));
}
}
}
/**
* Computes the region of full lines containing the two specified positions
* (e.g. begin and end offset of the editor selection).
*
* If the second position is in column 0 and in another line than the first position,
* the line of second position is not included in the region. The last line contains
* the line delimiter, if exists (not if EOF).
*
* @param document the document
* @param position1 first position
* @param position2 second position >= position1
* @return a region for the block
* @throws BadLocationException
*/
public static final IRegion getBlock(final IDocument document, final int position1, final int position2) throws BadLocationException {
final int line1 = document.getLineOfOffset(position1);
int line2 = document.getLineOfOffset(position2);
if (line1 < line2 && document.getLineOffset(line2) == position2) {
line2--;
}
final int start = document.getLineOffset(line1);
final int length = document.getLineOffset(line2)+document.getLineLength(line2)-start;
return new Region(start, length);
}
public static final IRegion expand(final IRegion region1, final IRegion region2) {
if (region2 == null) {
return region1;
}
final int offset = Math.min(region1.getOffset(), region2.getOffset());
return new Region(offset, Math.max(
region1.getOffset()+region1.getLength(), region2.getOffset()+region2.getLength())
- offset);
}
public static final int getColumn(final IDocument document, final int offset, int line, int tabWidth)
throws BadLocationException {
if (offset > document.getLength()) {
return -1;
}
if (line < 0) {
line = document.getLineOfOffset(offset);
}
if (tabWidth <= 0) {
tabWidth = 8;
}
int currentColumn = 0;
int currentOffset = document.getLineOffset(line);
while (currentOffset < offset) {
final char c = document.getChar(currentOffset++);
switch (c) {
case '\n':
case '\r':
return -1;
case '\t':
currentColumn += tabWidth - (currentColumn % tabWidth);
continue;
default:
currentColumn++;
continue;
}
}
return currentColumn;
}
public static final int getColumn(final String text, final int offset, int tabWidth) {
if (offset > text.length()) {
return -1;
}
if (tabWidth <= 0) {
tabWidth = 8;
}
int currentColumn = 0;
int currentOffset = 0;
while (currentOffset < offset) {
final char c = text.charAt(currentOffset++);
switch (c) {
case '\n':
case '\r':
return -1;
case '\t':
currentColumn += tabWidth - (currentColumn % tabWidth);
continue;
default:
currentColumn++;
continue;
}
}
return currentColumn;
}
public static final int countBackward(final IDocument document, int offset, final char c)
throws BadLocationException {
int count= 0;
while (offset > 0 && document.getChar(--offset) == c) {
count++;
}
return count;
}
public static final int countForward(final IDocument document, int offset, final char c)
throws BadLocationException {
int count= 0;
final int length= document.getLength();
while (offset < length && document.getChar(offset++) == c) {
count++;
}
return count;
}
public static List<IRegion> getMatchingRegions(final AbstractDocument document,
final String partitioning, final IPartitionConstraint contraint,
final IRegion region, final boolean extend) throws BadLocationException, BadPartitioningException {
final List<IRegion> regions = new ArrayList<>();
final int regionEnd = region.getOffset() + region.getLength();
int validBegin = -1;
int offset = region.getOffset();
if (extend && offset > 0) {
final ITypedRegion partition = document.getPartition(partitioning, offset - 1, false);
if (contraint.matches(partition.getType())) {
offset = partition.getOffset();
do {
final ITypedRegion prevPartition = document.getPartition(partitioning, offset - 1, false);
if (!contraint.matches(prevPartition.getType())) {
break;
}
offset = prevPartition.getOffset();
} while (offset > 0);
validBegin = offset;
}
offset = partition.getOffset() + partition.getLength();
}
do {
final ITypedRegion partition = document.getPartition(partitioning, offset, false);
if (validBegin < 0) {
if (contraint.matches(partition.getType())) {
validBegin = partition.getOffset();
}
}
else { // (validBegin >= 0)
if (!contraint.matches(partition.getType())) {
regions.add(new Region(validBegin, offset - validBegin));
validBegin = -1;
}
}
offset = partition.getOffset() + partition.getLength();
} while (offset < regionEnd);
if (validBegin >= 0) {
if (extend) {
do {
final ITypedRegion partition = document.getPartition(partitioning, offset, false);
if (!contraint.matches(partition.getType())) {
break;
}
offset = partition.getOffset() + partition.getLength();
} while (offset < document.getLength());
}
regions.add(new Region(validBegin, offset - validBegin));
}
return regions;
}
}