blob: 80b544a79ed2ff01ca586a85b390196cad1da067 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2019 Stephan Wahlbrink 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, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.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 org.eclipse.statet.ecommons.text.core.PartitionConstraint;
/**
* 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;
}
}
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 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 PartitionConstraint 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;
}
}