blob: 913552134ae5559b9460e16af9455666c565e672 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.editorActions;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author yole
*/
public abstract class ExtendWordSelectionHandlerBase implements ExtendWordSelectionHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.ExtendWordSelectionHandlerBase");
@Override
public abstract boolean canSelect(PsiElement e);
@Override
public List<TextRange> select(PsiElement e, CharSequence editorText, int cursorOffset, Editor editor) {
final TextRange originalRange = e.getTextRange();
if (originalRange.getEndOffset() > editorText.length()) {
throw new AssertionError("Invalid element range in " + getClass() +
"; element=" + e +
"; range=" + originalRange +
"; length=" + editorText +
"; editor=" + editor +
"; committed=" + PsiDocumentManager.getInstance(e.getProject()).isCommitted(editor.getDocument()));
}
List<TextRange> ranges = expandToWholeLine(editorText, originalRange, true);
if (ranges.size() == 1 && ranges.contains(originalRange)) {
return expandToWholeLine(editorText, originalRange, false);
}
return ranges;
}
/**
* Returns minimal selection length for given element.
*
* Sometimes the length of word selection should be bounded below.
* E.g. it is useful in languages that requires prefixes for variable (php, less, etc.).
* By default this kind of variables will be selected without prefix: @<selection>variable</selection>,
* but it make sense to exclude this range from selection list.
* So if this method returns 9 as a minimal length of selection
* then first selection range for @variable will be: <selection>@variable</selection>.
*
* @param element element at caret
* @param text text in editor
* @param cursorOffset current caret offset in editor
* @return minimal selection length for given element
*/
public int getMinimalTextRangeLength(@NotNull PsiElement element, @NotNull CharSequence text, int cursorOffset) {
return 0;
}
public static List<TextRange> expandToWholeLine(CharSequence text, @Nullable TextRange range, boolean isSymmetric) {
List<TextRange> result = new ArrayList<TextRange>();
if (range == null) {
return result;
}
LOG.assertTrue(range.getEndOffset() <= text.length());
if (!StringUtil.contains(text, range.getStartOffset(), range.getEndOffset(), '\n')) {
result.add(range);
}
TextRange expanded = getExpandedRange(text, range, isSymmetric);
if (expanded != null) {
result.add(expanded);
} else {
result.add(range);
}
return result;
}
@Nullable
private static TextRange getExpandedRange(CharSequence text, TextRange range, boolean isSymmetric) {
int startOffset = range.getStartOffset();
int endOffset = range.getEndOffset();
int index1 = CharArrayUtil.shiftBackward(text, startOffset - 1, " \t");
if (endOffset > startOffset && text.charAt(endOffset - 1) == '\n') {
endOffset--;
}
int textLength = text.length();
int index2 = Math.min(textLength, CharArrayUtil.shiftForward(text, endOffset, " \t"));
if (index1 < 0 || text.charAt(index1) == '\n' || index2 == textLength || text.charAt(index2) == '\n') {
if (!isSymmetric) {
if (index1 < 0 || text.charAt(index1) == '\n') {
startOffset = index1 + 1;
}
if (index2 == textLength || text.charAt(index2) == '\n') {
endOffset = index2;
if (endOffset < textLength) {
endOffset++;
}
}
return new TextRange(startOffset, endOffset);
}
if ((index1 < 0 || text.charAt(index1) == '\n') &&
(index2 == textLength || text.charAt(index2) == '\n')) {
startOffset = index1 + 1;
endOffset = index2;
if (endOffset < textLength) {
endOffset++;
}
return new TextRange(startOffset, endOffset);
}
}
return null;
}
public static List<TextRange> expandToWholeLinesWithBlanks(CharSequence text, TextRange range) {
List<TextRange> result = ContainerUtil.newArrayList();
result.addAll(expandToWholeLine(text, range, true));
TextRange last = result.isEmpty() ? range : result.get(result.size() - 1);
int start = last.getStartOffset();
int end = last.getEndOffset();
while (true) {
int blankLineEnd = CharArrayUtil.shiftForward(text, end, " \t");
if (blankLineEnd >= text.length() || text.charAt(blankLineEnd) != '\n') {
break;
}
end = blankLineEnd + 1;
}
if (end == last.getEndOffset()) {
while (start > 0 && text.charAt(start - 1) == '\n') {
int blankLineStart = CharArrayUtil.shiftBackward(text, start - 2, " \t");
if (blankLineStart <= 0 || text.charAt(blankLineStart) != '\n') {
break;
}
start = blankLineStart + 1;
}
}
if (start != last.getStartOffset() || end != last.getEndOffset()) {
result.add(new TextRange(start, end));
}
return result;
}
public static List<TextRange> expandToWholeLine(CharSequence text, TextRange range) {
return expandToWholeLine(text, range, true);
}
}