blob: d6f17ad5e02dc773f664473704d2fae3d9ece4d6 [file] [log] [blame]
/*
* Copyright 2000-2012 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.wordSelection;
import com.intellij.codeInsight.editorActions.ExtendWordSelectionHandlerBase;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.impl.CustomSyntaxTableFileType;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiPlainText;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class NaturalLanguageTextSelectioner extends ExtendWordSelectionHandlerBase {
private static final Set<Character> NATURAL = ContainerUtil.newTroveSet(
'(', ')', '.', ',', ':', ';', '!', '?', '$', '@', '%', '\"', '\'', '<', '>', '[', ']', '_'
);
private static final Set<Character> SENTENCE_END = ContainerUtil.newTroveSet('.', '!', '?');
@Override
public boolean canSelect(PsiElement e) {
return (e instanceof PsiPlainText || e instanceof PsiComment) &&
!(e.getContainingFile().getFileType() instanceof CustomSyntaxTableFileType);
}
private static TextRange findParagraphRange(String text, int start, int end) {
int paragraphStart = text.lastIndexOf("\n\n", start);
int paragraphEnd = text.indexOf("\n\n", end);
if (paragraphStart >= paragraphEnd) {
return new TextRange(0, text.length());
}
return new TextRange(paragraphStart >= 0 ? paragraphStart + 2 : 0, paragraphEnd < 0 ? text.length() : paragraphEnd);
}
@Nullable
private static TextRange findCustomRange(String text, int start, int end, char startChar, char endChar) {
int prev = text.lastIndexOf(startChar, start);
int next = text.indexOf(endChar, end);
if (prev < 0 || next < 0) {
return null;
}
if (prev + 1 < start || next > end) {
return new TextRange(prev + 1, next);
}
return new TextRange(prev, next + 1);
}
private static TextRange findSentenceRange(String editorText, int start, int end) {
int sentenceStart = start;
while (sentenceStart > 0) {
if (isSentenceEnd(editorText, sentenceStart - 1) || !isNatural(editorText.charAt(sentenceStart - 1))) {
break;
}
sentenceStart--;
}
while (sentenceStart < end && Character.isWhitespace(editorText.charAt(sentenceStart))) {
sentenceStart++;
}
int sentenceEnd = Math.max(0, end - 1);
while (sentenceEnd < editorText.length()) {
sentenceEnd++;
if (isSentenceEnd(editorText, sentenceEnd - 1)) {
break;
}
}
return new TextRange(sentenceStart, sentenceEnd);
}
private static boolean isSentenceEnd(String text, final int i) {
return SENTENCE_END.contains(text.charAt(i)) && (i + 1 == text.length() || Character.isWhitespace(text.charAt(i + 1)));
}
private static TextRange findNaturalRange(String editorText, int start, int end) {
while (start > 0) {
if (!isNatural(editorText.charAt(start - 1))) {
break;
}
start--;
}
while (end < editorText.length()) {
final char c = editorText.charAt(end);
if (!isNatural(c)) {
break;
}
end++;
}
return new TextRange(start, end);
}
@Override
public List<TextRange> select(PsiElement e, CharSequence editorText, int cursorOffset, Editor editor) {
TextRange range = expandSelection(e, editorText, cursorOffset, cursorOffset);
if (range == null) {
return Collections.emptyList();
}
ArrayList<TextRange> result = new ArrayList<TextRange>();
result.add(range);
while (true) {
TextRange next = expandSelection(e, editorText, range.getStartOffset(), range.getEndOffset());
if (next == null || range.contains(next)) {
break;
}
result.add(next);
range = next;
}
return result;
}
@Nullable
private static TextRange expandSelection(PsiElement e, CharSequence editorText, int selStart, int selEnd) {
TextRange range = e.getTextRange();
int shift = range.getStartOffset();
if (selStart <= shift || selEnd >= range.getEndOffset()) {
return null;
}
String elementText = editorText.subSequence(shift, range.getEndOffset()).toString();
int start = selStart - shift;
int end = selEnd - shift;
TextRange best = findSentenceRange(elementText, start, end);
best = narrowRange(best, findCustomRange(elementText, start, end, '\"', '\"'));
best = narrowRange(best, findCustomRange(elementText, start, end, '(', ')'));
best = narrowRange(best, findCustomRange(elementText, start, end, '<', '>'));
best = narrowRange(best, findCustomRange(elementText, start, end, '[', ']'));
TextRange natural = findNaturalRange(elementText, start, end);
if (!natural.contains(best)) {
return null;
}
TextRange paragraph = findParagraphRange(elementText, start, end);
if (best.getStartOffset() == start && best.getEndOffset() == end || !paragraph.contains(best)) {
return paragraph.shiftRight(shift);
}
return best.shiftRight(shift);
}
private static TextRange narrowRange(TextRange best, TextRange candidate) {
return candidate != null && best.contains(candidate) ? candidate : best;
}
private static boolean isNatural(char c) {
return Character.isWhitespace(c) || Character.isLetterOrDigit(c) || NATURAL.contains(c);
}
}