2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.codeInsight.editorActions;
19 import com.intellij.lang.ASTNode;
20 import com.intellij.lang.FileASTNode;
21 import com.intellij.lexer.Lexer;
22 import com.intellij.openapi.editor.Editor;
23 import com.intellij.openapi.editor.actions.EditorActionUtil;
24 import com.intellij.openapi.extensions.Extensions;
25 import com.intellij.openapi.project.DumbService;
26 import com.intellij.openapi.project.IndexNotReadyException;
27 import com.intellij.openapi.util.TextRange;
28 import com.intellij.psi.FileViewProvider;
29 import com.intellij.psi.PsiElement;
30 import com.intellij.psi.PsiFile;
31 import com.intellij.psi.StringEscapesTokenTypes;
32 import com.intellij.util.ArrayUtil;
33 import com.intellij.util.Processor;
34 import com.intellij.util.containers.ContainerUtil;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
38 import java.util.List;
43 public class SelectWordUtil {
44 private static ExtendWordSelectionHandler[] SELECTIONERS = new ExtendWordSelectionHandler[]{
47 private static boolean ourExtensionsLoaded = false;
49 private SelectWordUtil() {
53 * @see ExtendWordSelectionHandler#EP_NAME
56 public static void registerSelectioner(ExtendWordSelectionHandler selectioner) {
57 SELECTIONERS = ArrayUtil.append(SELECTIONERS, selectioner);
60 static ExtendWordSelectionHandler[] getExtendWordSelectionHandlers() {
61 if (!ourExtensionsLoaded) {
62 ourExtensionsLoaded = true;
63 for (ExtendWordSelectionHandler handler : Extensions.getExtensions(ExtendWordSelectionHandler.EP_NAME)) {
64 registerSelectioner(handler);
70 public static final CharCondition JAVA_IDENTIFIER_PART_CONDITION = new CharCondition() {
72 public boolean value(char ch) {
73 return Character.isJavaIdentifierPart(ch);
77 public static void addWordSelection(boolean camel, CharSequence editorText, int cursorOffset, @NotNull List<TextRange> ranges) {
78 addWordSelection(camel, editorText, cursorOffset, ranges, JAVA_IDENTIFIER_PART_CONDITION);
81 public static void addWordOrLexemeSelection(boolean camel, @NotNull Editor editor, int cursorOffset, @NotNull List<TextRange> ranges) {
82 addWordOrLexemeSelection(camel, editor, cursorOffset, ranges, JAVA_IDENTIFIER_PART_CONDITION);
85 public static void addWordSelection(boolean camel,
86 CharSequence editorText,
88 @NotNull List<TextRange> ranges,
89 CharCondition isWordPartCondition) {
90 TextRange camelRange = camel ? getCamelSelectionRange(editorText, cursorOffset, isWordPartCondition) : null;
91 if (camelRange != null) {
92 ranges.add(camelRange);
95 TextRange range = getWordSelectionRange(editorText, cursorOffset, isWordPartCondition);
96 if (range != null && !range.equals(camelRange)) {
101 public static void addWordOrLexemeSelection(boolean camel,
102 @NotNull Editor editor,
104 @NotNull List<TextRange> ranges,
105 CharCondition isWordPartCondition) {
106 TextRange camelRange = camel ? getCamelSelectionRange(editor.getDocument().getImmutableCharSequence(),
107 cursorOffset, isWordPartCondition) : null;
108 if (camelRange != null) {
109 ranges.add(camelRange);
112 TextRange range = getWordOrLexemeSelectionRange(editor, cursorOffset, isWordPartCondition);
113 if (range != null && !range.equals(camelRange)) {
119 private static TextRange getCamelSelectionRange(CharSequence editorText, int cursorOffset, CharCondition isWordPartCondition) {
120 if (cursorOffset < 0 || cursorOffset >= editorText.length()) {
123 if (cursorOffset > 0 && !isWordPartCondition.value(editorText.charAt(cursorOffset)) &&
124 isWordPartCondition.value(editorText.charAt(cursorOffset - 1))) {
128 if (isWordPartCondition.value(editorText.charAt(cursorOffset))) {
129 int start = cursorOffset;
130 int end = cursorOffset + 1;
131 final int textLen = editorText.length();
133 while (start > 0 && isWordPartCondition.value(editorText.charAt(start - 1)) && !EditorActionUtil.isHumpBound(editorText, start, true)) {
137 while (end < textLen && isWordPartCondition.value(editorText.charAt(end)) && !EditorActionUtil.isHumpBound(editorText, end, false)) {
141 if (start + 1 < end) {
142 return new TextRange(start, end);
150 public static TextRange getWordOrLexemeSelectionRange(@NotNull Editor editor, int cursorOffset,
151 @NotNull CharCondition isWordPartCondition) {
152 return getWordOrLexemeSelectionRange(editor, editor.getDocument().getImmutableCharSequence(), cursorOffset, isWordPartCondition);
156 public static TextRange getWordSelectionRange(@NotNull CharSequence editorText, int cursorOffset,
157 @NotNull CharCondition isWordPartCondition) {
158 return getWordOrLexemeSelectionRange(null, editorText, cursorOffset, isWordPartCondition);
162 private static TextRange getWordOrLexemeSelectionRange(@Nullable Editor editor, @NotNull CharSequence editorText, int cursorOffset,
163 @NotNull CharCondition isWordPartCondition) {
164 int length = editorText.length();
165 if (length == 0) return null;
166 if (cursorOffset == length ||
167 cursorOffset > 0 && !isWordPartCondition.value(editorText.charAt(cursorOffset)) &&
168 isWordPartCondition.value(editorText.charAt(cursorOffset - 1))) {
172 if (isWordPartCondition.value(editorText.charAt(cursorOffset))) {
173 int start = cursorOffset;
174 int end = cursorOffset;
176 while (start > 0 && isWordPartCondition.value(editorText.charAt(start - 1)) &&
177 (editor == null || !EditorActionUtil.isLexemeBoundary(editor, start))) {
181 while (end < length && isWordPartCondition.value(editorText.charAt(end)) &&
182 (end == start || editor == null || !EditorActionUtil.isLexemeBoundary(editor, end))) {
186 return new TextRange(start, end);
192 public static void processRanges(@Nullable PsiElement element,
196 Processor<TextRange> consumer) {
197 if (element == null) return;
199 PsiFile file = element.getContainingFile();
201 FileViewProvider viewProvider = file.getViewProvider();
203 processInFile(element, consumer, text, cursorOffset, editor);
205 for (PsiFile psiFile : viewProvider.getAllFiles()) {
206 if (psiFile == file) continue;
208 FileASTNode fileNode = psiFile.getNode();
209 if (fileNode == null) continue;
211 ASTNode nodeAt = fileNode.findLeafElementAt(element.getTextOffset());
212 if (nodeAt == null) continue;
214 PsiElement elementAt = nodeAt.getPsi();
216 while (!(elementAt instanceof PsiFile) && elementAt != null) {
217 if (elementAt.getTextRange().contains(element.getTextRange())) break;
219 elementAt = elementAt.getParent();
222 if (elementAt == null) continue;
224 processInFile(elementAt, consumer, text, cursorOffset, editor);
228 private static void processInFile(@NotNull final PsiElement element,
229 final Processor<TextRange> consumer,
230 final CharSequence text,
231 final int cursorOffset,
232 final Editor editor) {
233 DumbService.getInstance(element.getProject()).withAlternativeResolveEnabled(() -> {
234 PsiElement e = element;
235 while (e != null && !(e instanceof PsiFile)) {
236 if (processElement(e, consumer, text, cursorOffset, editor)) return;
242 private static boolean processElement(@NotNull PsiElement element,
243 Processor<TextRange> processor,
247 boolean stop = false;
249 ExtendWordSelectionHandler[] extendWordSelectionHandlers = getExtendWordSelectionHandlers();
250 int minimalTextRangeLength = 0;
251 List<ExtendWordSelectionHandler> availableSelectioners = ContainerUtil.newLinkedList();
252 for (ExtendWordSelectionHandler selectioner : extendWordSelectionHandlers) {
253 if (selectioner.canSelect(element)) {
254 int selectionerMinimalTextRange = selectioner instanceof ExtendWordSelectionHandlerBase
255 ? ((ExtendWordSelectionHandlerBase)selectioner).getMinimalTextRangeLength(element, text, cursorOffset)
257 minimalTextRangeLength = Math.max(minimalTextRangeLength, selectionerMinimalTextRange);
258 availableSelectioners.add(selectioner);
261 for (ExtendWordSelectionHandler selectioner : availableSelectioners) {
262 List<TextRange> ranges = askSelectioner(element, text, cursorOffset, editor, selectioner);
263 if (ranges == null) continue;
265 for (TextRange range : ranges) {
266 if (range == null || range.getLength() < minimalTextRangeLength) continue;
268 stop |= processor.process(range);
276 private static List<TextRange> askSelectioner(@NotNull PsiElement element,
280 ExtendWordSelectionHandler selectioner) {
282 long stamp = editor.getDocument().getModificationStamp();
283 List<TextRange> ranges = selectioner.select(element, text, cursorOffset, editor);
284 if (stamp != editor.getDocument().getModificationStamp()) {
285 throw new AssertionError("Selectioner " + selectioner + " has changed the document");
289 catch (IndexNotReadyException e) {
294 public static void addWordHonoringEscapeSequences(CharSequence editorText,
295 TextRange literalTextRange,
298 List<TextRange> result) {
299 lexer.start(editorText, literalTextRange.getStartOffset(), literalTextRange.getEndOffset());
301 while (lexer.getTokenType() != null) {
302 if (lexer.getTokenStart() <= cursorOffset && cursorOffset < lexer.getTokenEnd()) {
303 if (StringEscapesTokenTypes.STRING_LITERAL_ESCAPES.contains(lexer.getTokenType())) {
304 result.add(new TextRange(lexer.getTokenStart(), lexer.getTokenEnd()));
307 TextRange word = getWordSelectionRange(editorText, cursorOffset, JAVA_IDENTIFIER_PART_CONDITION);
309 result.add(new TextRange(Math.max(word.getStartOffset(), lexer.getTokenStart()),
310 Math.min(word.getEndOffset(), lexer.getTokenEnd())));
319 public interface CharCondition { boolean value(char ch); }