| /*=============================================================================# |
| # Copyright (c) 2007, 2020 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; |
| } |
| |
| } |