blob: f3364b0507d20777f22b4b930d6972aa30ea5dc9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2007 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.ui.text.util;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
/**
* An utility class that provides helper methods for language-dependant
* implementations of AutoEditStrategies.
*
* @author Andrey Tarantsov
*/
public class AutoEditUtils {
private AutoEditUtils() {
throw new AssertionError("Cannot instantiate utility class"); //$NON-NLS-1$
}
/**
* Return a pair for the given brace. Ex. '(' for ')', e.t.c.
*
* @param b
* input brace
* @return peer brace
* @author Kalugin Mikhail
*/
public static char getBracePair(char b) {
switch (b) {
case '(':
return ')';
case ')':
return '(';
case '[':
return ']';
case ']':
return '[';
case '{':
return '}';
case '}':
return '{';
case '\"':
return '\"';
case '\'':
return '\'';
}
return b;
}
/**
* Checks if the given command is an insertion command that inserts a
* snippet of text ending with one of the valid end-of-line sequences.
*
* @param d
* @param c
* @return
*/
public static boolean isNewLineInsertionCommand(IDocument d, DocumentCommand c) {
if (c.length > 0 || c.text == null)
return false;
String[] legalLineDelimiters = d.getLegalLineDelimiters();
for (int i = 0; i < legalLineDelimiters.length; i++) {
if (legalLineDelimiters[i].equals(c.text))
return true;
}
return false;
}
/**
* Checks if the given command inserts or replaces a single character.
*
* @param c
* @return
*/
public static boolean isSingleCharactedInsertionOrReplaceCommand(DocumentCommand c) {
return c.length <= 1 && c.text.length() == 1;
}
/**
* Returns the given number of spaces.
*
* @param spaces
* @return
*/
public static String getNSpaces(int spaces) {
return getNChars(spaces, ' ');
}
/**
* Returns <code>count</code> copies of the given character.
*
* @param count
* @param ch
* @return
*/
public static String getNChars(int count, char ch) {
StringBuffer buf = new StringBuffer(count);
for (int i = 0; i < count; i++)
buf.append(ch);
return buf.toString();
}
/**
* Determines if the given document range contains at least one character
* that belongs to one of the given partitions.
*
* @param document
* @param startOffset
* @param length
* @param partitions
* @param partitioning
* @return
* @throws BadLocationException
*/
public static boolean rangeContainsPartitions(IDocument document, int startOffset, int length,
String[] partitions, String partitioning) throws BadLocationException {
for (int offset = startOffset; offset < startOffset + length; offset++) {
ITypedRegion region = TextUtilities.getPartition(document, partitioning, offset, true);
String rt = region.getType();
if (AutoEditUtils.isPartitionOneOf(rt, partitions))
return true;
offset = region.getOffset() + region.getLength(); // will be
// incremented
// by loop
}
return false;
}
/**
* Determines whether all characters of the given range belong to the
* default partition.
*
* @param document
* @param startOffset
* @param length
* @param partitions
* @param partitioning
* @return
* @throws BadLocationException
*/
public static boolean rangeIsInsideDefaultPartition(IDocument document, int startOffset,
int length, String partitioning) throws BadLocationException {
for (int offset = startOffset; offset < startOffset + length; offset++) {
ITypedRegion region = TextUtilities.getPartition(document, partitioning, offset, true);
String rt = region.getType();
if (rt != IDocument.DEFAULT_CONTENT_TYPE)
return false;
offset = region.getOffset() + region.getLength(); // will be
// incremented
// by loop
}
return true;
}
/**
* Determines if the given offset is at the very start of line (that is, at
* the beginning of the document or immediately after a valid end-of-line
* sequence).
*
* Assumes that no end-of-line sequence is longer than 2 characters.
*
* @param document
* @param offset
* @return
* @throws BadLocationException
*/
public static boolean isStartOfLine(IDocument document, int offset) throws BadLocationException {
String string;
if (offset >= 2)
string = document.get(offset - 2, 2);
else if (offset >= 1)
string = document.get(offset - 1, 1);
else
return true;
return TextUtilities.endsWith(document.getLegalLineDelimiters(), string) != -1;
}
/**
* Returns the leading whitespaces and tabs.
*
* @param document -
* the document being parsed
* @param line -
* the line being searched
* @return the leading whitespace
* @throws BadLocationException
* in case <code>line</code> is invalid in the document
*/
public static String getLineIndent(IDocument document, int line) throws BadLocationException {
if (line > -1) {
int start = document.getLineOffset(line);
int end = start + document.getLineLength(line); // was - 1
int whiteend = AutoEditUtils.findEndOfWhiteSpace(document, start, end);
return document.get(start, whiteend - start);
}
return ""; //$NON-NLS-1$
}
/**
* Returns the first offset greater than or equal to <code>offset</code>
* and less than <code>end</code> whose character is not a space or tab
* character. If no such offset is found, <code>end</code> is returned.
*
* @param document
* the document to search in
* @param offset
* the offset at which searching start
* @param end
* the offset at which searching stops
* @return the offset in the specified range whose character is not a space
* or tab
* @exception BadLocationException
* if position is an invalid range in the given document
*/
public static int findEndOfWhiteSpace(IDocument document, int offset, int end)
throws BadLocationException {
// int docLength = document.getLength();
while (offset < end) {
// if (offset >= docLength) {
// return docLength;
// }
char c = document.getChar(offset);
if (c != ' ' && c != '\t') {
return offset;
}
offset++;
}
return end;
}
/**
* Returns the leading whitespaces and tabs.
*
* @param line -
* the line being searched
* @return the leading whitespace
*/
public static String getLineIndent(String line) {
int end = line.length();
int whiteend = end;
int offset = 0;
while (offset < end) {
char c = line.charAt(offset);
if (c != ' ' && c != '\t') {
whiteend = offset;
break;
}
offset++;
}
return line.substring(0, whiteend);
}
/**
* Returns the document's text for the given line.
*
* @param document
* the document
* @param line
* the index of the line to get
* @return The line with the specified index from the document.
* @throws BadLocationException
* if <b>line</b> is not correct line number
*/
public static String getDocumentLine(IDocument document, int line) throws BadLocationException {
int lineStart = document.getLineOffset(line);
int lineLength = document.getLineLength(line);
return document.get(lineStart, lineLength);
}
/**
* Find line with number less or equal to <code>line</code>, that is not
* empty and is not a comment line starting with <code>commentString</code>.
*
* @param d
* the document to search in
* @param line
* the index of the line to start searching on (must be less than
* the number of lines in the document)
* @return The index of the line, or -1 if no such line is found.
* @throws BadLocationException
*/
public static int getLastNonEmptyLine(IDocument d, int line, String commentString)
throws BadLocationException {
if (commentString == null)
for (int res = line; res >= 0; res--) {
String str = getDocumentLine(d, res).trim();
if (str.trim().length() > 0)
return res;
}
else
for (int res = line; res >= 0; res--) {
String str = getDocumentLine(d, res).trim();
if (!str.startsWith(commentString) && str.trim().length() > 0)
return res;
}
return -1;
}
/**
* Returns the partition type covering the given offset.
*
* @param d
* @param partitioning
* the partitioning to be used
* @param offset
* @return
* @throws BadLocationException
* if the offset is invalid in the given document
*/
public static String getRegionType(IDocument d, String partitioning, int offset)
throws BadLocationException {
int p = ((offset == d.getLength()) ? offset - 1 : offset);
ITypedRegion region = TextUtilities.getPartition(d, partitioning, p, true);
return region.getType();
}
/**
* Searchs a pair from the given offset, forward of backwards. Doesn't go
* more than maxCharsAway chars away from the given offset.
*
* @param document
* @param startingOffset
* @param forward
* @param opening
* @param closing
* @param skipCommentLines
* @param skipStrings
* @return offset of the matched pair character, or <code>-1</code> if
* none is found
* @throws BadLocationException
*/
public static int findMatchingCharacter(IDocument document, int startingOffset,
boolean forward, String partitioning, char opening, char closing,
IPartitionFilter partitionFilter, int maxCharsAway) throws BadLocationException {
int deep = 0;
int offset = startingOffset;
if (forward) {
while (offset < document.getLength()) {
if (partitionFilter != null) {
ITypedRegion region = TextUtilities.getPartition(document, partitioning,
offset, true);
// TODO: don't refetch the partition while where're inside
// it
if (!partitionFilter.allowPartition(region.getType())) {
offset = region.getOffset() + region.getLength();
continue;
}
}
char c = document.getChar(offset);
if (c == opening)
deep++;
if (c == closing) {
if (deep == 0)
return offset;
deep--;
}
offset++;
if (offset - startingOffset > maxCharsAway)
return -1;
}
} else {
while (offset >= 0) {
if (partitionFilter != null) {
ITypedRegion region = TextUtilities.getPartition(document, partitioning,
offset, true);
// TODO: don't refetch the partition while where're inside
// it
if (!partitionFilter.allowPartition(region.getType())) {
offset = region.getOffset() - 1;
continue;
}
}
char c = document.getChar(offset);
if (c == closing)
deep++;
if (c == opening) {
if (deep == 0)
return offset;
deep--;
}
offset--;
if (startingOffset - offset > maxCharsAway)
return -1;
}
}
return -1;
}
public static boolean isPartitionOneOf(String partition, String[] list) {
for (int i = 0; i < list.length; i++) {
String item = list[i];
if (partition == item)
return true;
}
return false;
}
/**
* Calculates the number of columns the given part of the document would
* occupy in the text editor. This takes into account the size of the tab
* character. Note that the given range must fit entirely into a single
* line.
*
* @param prefs
* the preferences provider to get the tab size from.
* @param document
* the document to calculate the length in
* @param lineStart
* the start of the line where the given part of the document
* resides
* @param lineLength
* the length of the line where the given part of the document
* resides
* @param start
* the starting offset of the given part of the document
* @param end
* the ending offset of the given part of the document
* @return the number of columns the given part of the document would occupy
* in the text viewer
* @throws BadLocationException
*/
public static int calculateVisualLength(ITabPreferencesProvider prefs, IDocument document,
int lineStart, int lineLength, int start, int end) throws BadLocationException {
if (end == document.getLength())
end -= 1;
int res = 0;
int tabSize = prefs.getTabSize();
Assert.isLegal(end <= (lineStart + lineLength),
Messages.AutoEditUtils_cannotCalculateVisualLengthForSeveralLines);
int col = 0;
for (int offset = lineStart; offset < start; offset++) {
if (document.getChar(offset) != '\t')
col++;
else {
col += (tabSize - col % tabSize);
}
}
for (int offset = start; offset < end; offset++) {
if (document.getChar(offset) != '\t') {
res++;
col++;
} else {
res += (tabSize - col % tabSize);
col += (tabSize - col % tabSize);
}
}
return res;
}
/**
* Calculates the number of columns the given part of the document would
* occupy in the text editor. This takes into account the size of the tab
* character. Note that the given range must fit entirely into a single
* line.
*
* This function calculates the starting and ending offsets of the line and
* calls another overload.
*
* @param prefs
* the preferences provider to get the tab size from.
* @param document
* the document to calculate the length in
* @param lineIndex
* the index of the line where the given part of the document
* resides
* @param start
* the starting offset of the given part of the document
* @param end
* the ending offset of the given part of the document
* @return the number of columns the given part of the document would occupy
* in the text viewer
* @throws BadLocationException
*/
public static int calculateVisualLength(ITabPreferencesProvider prefs, IDocument document,
int lineIndex, int start, int end) throws BadLocationException {
return calculateVisualLength(prefs, document, document.getLineOffset(lineIndex), document
.getLineLength(lineIndex), start, end);
}
/**
* Calculates the number of columns the given part of the document would
* occupy in the text editor. This takes into account the size of the tab
* character. Note that the given range must fit entirely into a single
* line.
*
* This function calculates the starting and ending offsets of the line and
* calls another overload.
*
* @param prefs
* the preferences provider to get the tab size from.
* @param document
* the document to calculate the length in
* @param start
* the starting offset of the given part of the document
* @param end
* the ending offset of the given part of the document
* @return the number of columns the given part of the document would occupy
* in the text editor
* @throws BadLocationException
*/
public static int calculateVisualLength(ITabPreferencesProvider prefs, IDocument document,
int start, int end) throws BadLocationException {
return calculateVisualLength(prefs, document, document.getLineOfOffset(start), start, end);
}
/**
* Calculates the number of columns the given indentation string would
* occupy in the text editor. This takes into account the size of the tab
* character. Assumes that the given string starts in the first column, so
* can only be used for indentation strings.
*
* @param prefs
* the preferences provider to get the tab size from.
* @param str
* the indentation strings (probably containing whitespace
* characters only) to calculate the length of
* @return
*/
public static int getIndentVisualLength(ITabPreferencesProvider prefs, String str) {
int res = 0;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == '\t')
res += prefs.getTabSize();
else
res++;
}
return res;
}
/**
* Calculates the number of columns the indentation of the given line would
* occupy in the text editor. This takes into account the size of the tab
* character.
*
* @param prefs
* the preferences provider to get the tab size from.
* @param document
* the document to calculate the length in
* @param lineIndex
* the line whose indent length needs to be calculated
* @return
* @throws BadLocationException
*/
public static int getIndentVisualLength(ITabPreferencesProvider prefs, IDocument document,
int lineIndex) throws BadLocationException {
int lineOffset = document.getLineOffset(lineIndex);
int lineLength = document.getLineLength(lineIndex);
int lineHome = findEndOfWhiteSpace(document, lineOffset, lineOffset + lineLength);
return calculateVisualLength(prefs, document, lineOffset, lineLength, lineOffset, lineHome);
}
public static int getMaximumLineDelimiterLength(IDocument document) {
String[] lineDelimiters = document.getLegalLineDelimiters();
int length = 0;
for (int i = 0; i < lineDelimiters.length; i++)
length = Math.max(length, lineDelimiters[i].length());
return length;
}
/**
* Determine if the given offset is right before the end-of-line
* character(s) on the given line. Also returns true if the given offset
* is equal to the <code>endOffset</code>.
*
* @param document
* the document that determines the set of legal line delimiter
* strings
* @param offset
* the offset to check
* @param endOffset
* the offset right after the end of the line containing
* <code>offset</code>, should equal the value that would be
* returned from
* <code>document.getLineOffset(document.getLineOfOffset(offset)) +
* document.getLineLength(document.getLineOfOffset(offset))</code>.
* @return
* @throws BadLocationException
*/
public static boolean atEndOfLine(IDocument document, int offset, int endOffset) throws BadLocationException {
if (offset == endOffset)
return true;
int maxDelta = getMaximumLineDelimiterLength(document);
if (offset + maxDelta < endOffset)
return false;
String s = document.get(offset, Math.min(maxDelta, document.getLength() - offset));
String[] searchStrings = document.getLegalLineDelimiters();
for (int i= 0; i < searchStrings.length; i++)
if (s.startsWith(searchStrings[i]))
return true;
return false;
}
}