blob: 94d59648de61187199039bc68821ba2b2c769b64 [file] [log] [blame]
/*
* Copyright 2000-2014 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.lang.ASTNode;
import com.intellij.lang.FileASTNode;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actions.EditorActionUtil;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.StringEscapesTokenTypes;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @author Mike
*/
public class SelectWordUtil {
private static ExtendWordSelectionHandler[] SELECTIONERS = new ExtendWordSelectionHandler[]{
};
private static boolean ourExtensionsLoaded = false;
private SelectWordUtil() {
}
/**
* @see ExtendWordSelectionHandler#EP_NAME
*/
@Deprecated
public static void registerSelectioner(ExtendWordSelectionHandler selectioner) {
SELECTIONERS = ArrayUtil.append(SELECTIONERS, selectioner);
}
static ExtendWordSelectionHandler[] getExtendWordSelectionHandlers() {
if (!ourExtensionsLoaded) {
ourExtensionsLoaded = true;
for (ExtendWordSelectionHandler handler : Extensions.getExtensions(ExtendWordSelectionHandler.EP_NAME)) {
registerSelectioner(handler);
}
}
return SELECTIONERS;
}
public static final CharCondition JAVA_IDENTIFIER_PART_CONDITION = new CharCondition() {
@Override
public boolean value(char ch) {
return Character.isJavaIdentifierPart(ch);
}
};
public static void addWordSelection(boolean camel, CharSequence editorText, int cursorOffset, @NotNull List<TextRange> ranges) {
addWordSelection(camel, editorText, cursorOffset, ranges, JAVA_IDENTIFIER_PART_CONDITION);
}
public static void addWordSelection(boolean camel,
CharSequence editorText,
int cursorOffset,
@NotNull List<TextRange> ranges,
CharCondition isWordPartCondition) {
TextRange camelRange = camel ? getCamelSelectionRange(editorText, cursorOffset, isWordPartCondition) : null;
if (camelRange != null) {
ranges.add(camelRange);
}
TextRange range = getWordSelectionRange(editorText, cursorOffset, isWordPartCondition);
if (range != null && !range.equals(camelRange)) {
ranges.add(range);
}
}
@Nullable
private static TextRange getCamelSelectionRange(CharSequence editorText, int cursorOffset, CharCondition isWordPartCondition) {
if (cursorOffset < 0 || cursorOffset >= editorText.length()) {
return null;
}
if (cursorOffset > 0 && !isWordPartCondition.value(editorText.charAt(cursorOffset)) &&
isWordPartCondition.value(editorText.charAt(cursorOffset - 1))) {
cursorOffset--;
}
if (isWordPartCondition.value(editorText.charAt(cursorOffset))) {
int start = cursorOffset;
int end = cursorOffset + 1;
final int textLen = editorText.length();
while (start > 0 && isWordPartCondition.value(editorText.charAt(start - 1)) && !EditorActionUtil.isHumpBound(editorText, start, true)) {
start--;
}
while (end < textLen && isWordPartCondition.value(editorText.charAt(end)) && !EditorActionUtil.isHumpBound(editorText, end, false)) {
end++;
}
if (start + 1 < end) {
return new TextRange(start, end);
}
}
return null;
}
@Nullable
public static TextRange getWordSelectionRange(@NotNull CharSequence editorText, int cursorOffset, @NotNull CharCondition isWordPartCondition) {
int length = editorText.length();
if (length == 0) return null;
if (cursorOffset == length ||
cursorOffset > 0 && !isWordPartCondition.value(editorText.charAt(cursorOffset)) &&
isWordPartCondition.value(editorText.charAt(cursorOffset - 1))) {
cursorOffset--;
}
if (isWordPartCondition.value(editorText.charAt(cursorOffset))) {
int start = cursorOffset;
int end = cursorOffset;
while (start > 0 && isWordPartCondition.value(editorText.charAt(start - 1))) {
start--;
}
while (end < length && isWordPartCondition.value(editorText.charAt(end))) {
end++;
}
return new TextRange(start, end);
}
return null;
}
public static void processRanges(@Nullable PsiElement element,
CharSequence text,
int cursorOffset,
Editor editor,
Processor<TextRange> consumer) {
if (element == null) return;
PsiFile file = element.getContainingFile();
FileViewProvider viewProvider = file.getViewProvider();
processInFile(element, consumer, text, cursorOffset, editor);
for (PsiFile psiFile : viewProvider.getAllFiles()) {
if (psiFile == file) continue;
FileASTNode fileNode = psiFile.getNode();
if (fileNode == null) continue;
ASTNode nodeAt = fileNode.findLeafElementAt(element.getTextOffset());
if (nodeAt == null) continue;
PsiElement elementAt = nodeAt.getPsi();
while (!(elementAt instanceof PsiFile) && elementAt != null) {
if (elementAt.getTextRange().contains(element.getTextRange())) break;
elementAt = elementAt.getParent();
}
if (elementAt == null) continue;
processInFile(elementAt, consumer, text, cursorOffset, editor);
}
}
private static void processInFile(@NotNull PsiElement element,
Processor<TextRange> consumer,
CharSequence text,
int cursorOffset,
Editor editor) {
PsiElement e = element;
while (e != null && !(e instanceof PsiFile)) {
if (processElement(e, consumer, text, cursorOffset, editor)) return;
e = e.getParent();
}
}
private static boolean processElement(@NotNull PsiElement element,
Processor<TextRange> processor,
CharSequence text,
int cursorOffset,
Editor editor) {
boolean stop = false;
ExtendWordSelectionHandler[] extendWordSelectionHandlers = getExtendWordSelectionHandlers();
int minimalTextRangeLength = 0;
List<ExtendWordSelectionHandler> availableSelectioners = ContainerUtil.newLinkedList();
for (ExtendWordSelectionHandler selectioner : extendWordSelectionHandlers) {
if (selectioner.canSelect(element)) {
int selectionerMinimalTextRange = selectioner instanceof ExtendWordSelectionHandlerBase
? ((ExtendWordSelectionHandlerBase)selectioner).getMinimalTextRangeLength(element, text, cursorOffset)
: 0;
minimalTextRangeLength = Math.max(minimalTextRangeLength, selectionerMinimalTextRange);
availableSelectioners.add(selectioner);
}
}
long stamp = editor.getDocument().getModificationStamp();
for (ExtendWordSelectionHandler selectioner : availableSelectioners) {
List<TextRange> ranges = selectioner.select(element, text, cursorOffset, editor);
if (stamp != editor.getDocument().getModificationStamp()) {
throw new AssertionError("Selectioner " + selectioner + " has changed the document");
}
if (ranges == null) continue;
for (TextRange range : ranges) {
if (range == null || range.getLength() < minimalTextRangeLength) continue;
stop |= processor.process(range);
}
}
return stop;
}
public static void addWordHonoringEscapeSequences(CharSequence editorText,
TextRange literalTextRange,
int cursorOffset,
Lexer lexer,
List<TextRange> result) {
lexer.start(editorText, literalTextRange.getStartOffset(), literalTextRange.getEndOffset());
while (lexer.getTokenType() != null) {
if (lexer.getTokenStart() <= cursorOffset && cursorOffset < lexer.getTokenEnd()) {
if (StringEscapesTokenTypes.STRING_LITERAL_ESCAPES.contains(lexer.getTokenType())) {
result.add(new TextRange(lexer.getTokenStart(), lexer.getTokenEnd()));
}
else {
TextRange word = getWordSelectionRange(editorText, cursorOffset, JAVA_IDENTIFIER_PART_CONDITION);
if (word != null) {
result.add(new TextRange(Math.max(word.getStartOffset(), lexer.getTokenStart()),
Math.min(word.getEndOffset(), lexer.getTokenEnd())));
}
}
break;
}
lexer.advance();
}
}
public interface CharCondition { boolean value(char ch); }
}