| /* |
| * 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.highlighting; |
| |
| import com.intellij.codeInsight.CodeInsightBundle; |
| import com.intellij.codeInsight.TargetElementUtilBase; |
| import com.intellij.codeInsight.daemon.impl.IdentifierUtil; |
| import com.intellij.find.EditorSearchComponent; |
| import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter; |
| import com.intellij.injected.editor.EditorWindow; |
| import com.intellij.lang.injection.InjectedLanguageManager; |
| import com.intellij.navigation.NavigationItem; |
| import com.intellij.openapi.actionSystem.ActionManager; |
| import com.intellij.openapi.actionSystem.IdeActions; |
| import com.intellij.openapi.actionSystem.Shortcut; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.SelectionModel; |
| import com.intellij.openapi.editor.colors.EditorColors; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.markup.HighlighterLayer; |
| import com.intellij.openapi.editor.markup.RangeHighlighter; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.keymap.KeymapUtil; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.wm.WindowManager; |
| import com.intellij.pom.PomTarget; |
| import com.intellij.pom.PomTargetPsiElement; |
| import com.intellij.pom.PsiDeclaredTarget; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.PsiUtilBase; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.usages.UsageTarget; |
| import com.intellij.usages.UsageTargetUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.util.*; |
| |
| public class HighlightUsagesHandler extends HighlightHandlerBase { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.highlighting.HighlightUsagesHandler"); |
| |
| public static void invoke(@NotNull Project project, @NotNull Editor editor, PsiFile file) { |
| PsiDocumentManager.getInstance(project).commitAllDocuments(); |
| |
| SelectionModel selectionModel = editor.getSelectionModel(); |
| if (file == null && !selectionModel.hasSelection()) { |
| selectionModel.selectWordAtCaret(false); |
| } |
| if (file == null || selectionModel.hasSelection()) { |
| doRangeHighlighting(editor, project); |
| return; |
| } |
| |
| final HighlightUsagesHandlerBase handler = createCustomHandler(editor, file); |
| if (handler != null) { |
| handler.highlightUsages(); |
| return; |
| } |
| |
| UsageTarget[] usageTargets = UsageTargetUtil.findUsageTargets(editor, file); |
| |
| if (usageTargets == null) { |
| PsiElement targetElement = getTargetElement(editor, file); |
| if (targetElement != null && targetElement != file) { |
| if (!(targetElement instanceof NavigationItem)) { |
| targetElement = targetElement.getNavigationElement(); |
| } |
| if (targetElement instanceof NavigationItem) { |
| usageTargets = new UsageTarget[]{new PsiElement2UsageTargetAdapter(targetElement)}; |
| } |
| } |
| } |
| |
| if (usageTargets == null) { |
| PsiReference ref = TargetElementUtilBase.findReference(editor); |
| |
| if (ref instanceof PsiPolyVariantReference) { |
| ResolveResult[] results = ((PsiPolyVariantReference)ref).multiResolve(false); |
| |
| if (results.length > 0) { |
| usageTargets = ContainerUtil.mapNotNull(results, new Function<ResolveResult, UsageTarget>() { |
| @Override |
| public UsageTarget fun(ResolveResult result) { |
| PsiElement element = result.getElement(); |
| return element == null ? null : new PsiElement2UsageTargetAdapter(element); |
| } |
| }, UsageTarget.EMPTY_ARRAY); |
| } |
| } |
| } |
| |
| if (usageTargets == null) { |
| if (file.findElementAt(editor.getCaretModel().getOffset()) instanceof PsiWhiteSpace) return; |
| selectionModel.selectWordAtCaret(false); |
| String selection = selectionModel.getSelectedText(); |
| LOG.assertTrue(selection != null); |
| for (int i = 0; i < selection.length(); i++) { |
| if (!Character.isJavaIdentifierPart(selection.charAt(i))) { |
| selectionModel.removeSelection(); |
| return; |
| } |
| } |
| |
| doRangeHighlighting(editor, project); |
| selectionModel.removeSelection(); |
| return; |
| } |
| |
| boolean clearHighlights = isClearHighlights(editor); |
| for (UsageTarget target : usageTargets) { |
| target.highlightUsages(file, editor, clearHighlights); |
| } |
| } |
| |
| @Nullable |
| public static HighlightUsagesHandlerBase createCustomHandler(final Editor editor, final PsiFile file) { |
| for (HighlightUsagesHandlerFactory factory : Extensions.getExtensions(HighlightUsagesHandlerFactory.EP_NAME)) { |
| final HighlightUsagesHandlerBase handler = factory.createHighlightUsagesHandler(editor, file); |
| if (handler != null) { |
| return handler; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static PsiElement getTargetElement(Editor editor, PsiFile file) { |
| PsiElement target = TargetElementUtilBase.findTargetElement(editor, TargetElementUtilBase.getInstance().getReferenceSearchFlags()); |
| |
| if (target == null) { |
| int offset = TargetElementUtilBase.adjustOffset(file, editor.getDocument(), editor.getCaretModel().getOffset()); |
| PsiElement element = file.findElementAt(offset); |
| if (element == null) return null; |
| } |
| |
| return target; |
| } |
| |
| private static void doRangeHighlighting(Editor editor, Project project) { |
| if (!editor.getSelectionModel().hasSelection()) return; |
| |
| final String text = editor.getSelectionModel().getSelectedText(); |
| if (text == null) return; |
| |
| if (editor instanceof EditorWindow) { |
| // highlight selection in the whole editor, not injected fragment only |
| editor = ((EditorWindow)editor).getDelegate(); |
| } |
| |
| final JComponent oldHeader = editor.getHeaderComponent(); |
| if (oldHeader instanceof EditorSearchComponent) { |
| final EditorSearchComponent oldSearch = (EditorSearchComponent)oldHeader; |
| if (oldSearch.hasMatches()) { |
| String oldText = oldSearch.getTextInField(); |
| if (!oldSearch.isRegexp()) { |
| oldText = StringUtil.escapeToRegexp(oldText); |
| oldSearch.setRegexp(true); |
| } |
| |
| String newText = oldText + '|' + StringUtil.escapeToRegexp(text); |
| oldSearch.setTextInField(newText); |
| return; |
| } |
| } |
| |
| final EditorSearchComponent header = new EditorSearchComponent(editor, project); |
| header.setRegexp(false); |
| editor.setHeaderComponent(header); |
| } |
| |
| public static class DoHighlightRunnable implements Runnable { |
| private final List<PsiReference> myRefs; |
| private final Project myProject; |
| private final PsiElement myTarget; |
| private final Editor myEditor; |
| private final PsiFile myFile; |
| private final boolean myClearHighlights; |
| |
| public DoHighlightRunnable(@NotNull List<PsiReference> refs, @NotNull Project project, @NotNull PsiElement target, Editor editor, |
| PsiFile file, boolean clearHighlights) { |
| myRefs = refs; |
| myProject = project; |
| myTarget = target; |
| myEditor = editor instanceof EditorWindow ? ((EditorWindow)editor).getDelegate() : editor; |
| myFile = file; |
| myClearHighlights = clearHighlights; |
| } |
| |
| @Override |
| public void run() { |
| highlightReferences(myProject, myTarget, myRefs, myEditor, myFile, myClearHighlights); |
| setStatusText(myProject, getElementName(myTarget), myRefs.size(), myClearHighlights); |
| } |
| } |
| |
| public static void highlightOtherOccurrences(final List<PsiElement> otherOccurrences, Editor editor, boolean clearHighlights) { |
| EditorColorsManager manager = EditorColorsManager.getInstance(); |
| TextAttributes attributes = manager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); |
| |
| PsiElement[] elements = PsiUtilCore.toPsiElementArray(otherOccurrences); |
| doHighlightElements(editor, elements, attributes, clearHighlights); |
| } |
| |
| public static void highlightReferences(@NotNull Project project, |
| @NotNull PsiElement element, |
| @NotNull List<PsiReference> refs, |
| Editor editor, |
| PsiFile file, |
| boolean clearHighlights) { |
| |
| HighlightManager highlightManager = HighlightManager.getInstance(project); |
| EditorColorsManager manager = EditorColorsManager.getInstance(); |
| TextAttributes attributes = manager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); |
| TextAttributes writeAttributes = manager.getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES); |
| |
| setupFindModel(project); |
| |
| ReadWriteAccessDetector detector = ReadWriteAccessDetector.findDetector(element); |
| |
| if (detector != null) { |
| List<PsiReference> readRefs = new ArrayList<PsiReference>(); |
| List<PsiReference> writeRefs = new ArrayList<PsiReference>(); |
| |
| for (PsiReference ref : refs) { |
| if (detector.getReferenceAccess(element, ref) == ReadWriteAccessDetector.Access.Read) { |
| readRefs.add(ref); |
| } |
| else { |
| writeRefs.add(ref); |
| } |
| } |
| doHighlightRefs(highlightManager, editor, readRefs, attributes, clearHighlights); |
| doHighlightRefs(highlightManager, editor, writeRefs, writeAttributes, clearHighlights); |
| } |
| else { |
| doHighlightRefs(highlightManager, editor, refs, attributes, clearHighlights); |
| } |
| |
| TextRange range = getNameIdentifierRange(file, element); |
| if (range != null) { |
| TextAttributes nameAttributes = attributes; |
| if (detector != null && detector.isDeclarationWriteAccess(element)) { |
| nameAttributes = writeAttributes; |
| } |
| highlightRanges(highlightManager, editor, nameAttributes, clearHighlights, Arrays.asList(range)); |
| } |
| } |
| |
| @Nullable |
| public static TextRange getNameIdentifierRange(PsiFile file, PsiElement element) { |
| final InjectedLanguageManager injectedManager = InjectedLanguageManager.getInstance(element.getProject()); |
| if (element instanceof PomTargetPsiElement) { |
| final PomTarget target = ((PomTargetPsiElement)element).getTarget(); |
| if (target instanceof PsiDeclaredTarget) { |
| final PsiDeclaredTarget declaredTarget = (PsiDeclaredTarget)target; |
| final TextRange range = declaredTarget.getNameIdentifierRange(); |
| if (range != null) { |
| if (range.getStartOffset() < 0 || range.getLength() <= 0) { |
| return null; |
| } |
| final PsiElement navElement = declaredTarget.getNavigationElement(); |
| if (PsiUtilBase.isUnderPsiRoot(file, navElement)) { |
| return injectedManager.injectedToHost(navElement, range.shiftRight(navElement.getTextRange().getStartOffset())); |
| } |
| } |
| } |
| } |
| |
| if (!PsiUtilBase.isUnderPsiRoot(file, element)) { |
| return null; |
| } |
| |
| PsiElement identifier = IdentifierUtil.getNameIdentifier(element); |
| if (identifier != null && PsiUtilBase.isUnderPsiRoot(file, identifier)) { |
| return injectedManager.injectedToHost(identifier, identifier.getTextRange()); |
| } |
| return null; |
| } |
| |
| public static void doHighlightElements(Editor editor, PsiElement[] elements, TextAttributes attributes, boolean clearHighlights) { |
| HighlightManager highlightManager = HighlightManager.getInstance(editor.getProject()); |
| List<TextRange> textRanges = new ArrayList<TextRange>(elements.length); |
| for (PsiElement element : elements) { |
| TextRange range = element.getTextRange(); |
| // injection occurs |
| range = InjectedLanguageManager.getInstance(element.getProject()).injectedToHost(element, range); |
| textRanges.add(range); |
| } |
| highlightRanges(highlightManager, editor, attributes, clearHighlights, textRanges); |
| } |
| |
| public static void highlightRanges(HighlightManager highlightManager, Editor editor, TextAttributes attributes, |
| boolean clearHighlights, |
| List<TextRange> textRanges) { |
| if (clearHighlights) { |
| clearHighlights(editor, highlightManager, textRanges, attributes); |
| return; |
| } |
| ArrayList<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>(); |
| for (TextRange range : textRanges) { |
| highlightManager.addRangeHighlight(editor, range.getStartOffset(), range.getEndOffset(), attributes, false, highlighters); |
| } |
| for (RangeHighlighter highlighter : highlighters) { |
| String tooltip = getLineTextErrorStripeTooltip(editor.getDocument(), highlighter.getStartOffset(), true); |
| highlighter.setErrorStripeTooltip(tooltip); |
| } |
| } |
| |
| public static boolean isClearHighlights(Editor editor) { |
| if (editor instanceof EditorWindow) editor = ((EditorWindow)editor).getDelegate(); |
| |
| RangeHighlighter[] highlighters = ((HighlightManagerImpl)HighlightManager.getInstance(editor.getProject())).getHighlighters(editor); |
| int caretOffset = editor.getCaretModel().getOffset(); |
| for (RangeHighlighter highlighter : highlighters) { |
| if (TextRange.create(highlighter).grown(1).contains(caretOffset)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static void clearHighlights(Editor editor, |
| HighlightManager highlightManager, |
| List<TextRange> rangesToHighlight, |
| TextAttributes attributes) { |
| if (editor instanceof EditorWindow) editor = ((EditorWindow)editor).getDelegate(); |
| RangeHighlighter[] highlighters = ((HighlightManagerImpl)highlightManager).getHighlighters(editor); |
| Arrays.sort(highlighters, new Comparator<RangeHighlighter>() { |
| @Override |
| public int compare(RangeHighlighter o1, RangeHighlighter o2) { |
| return o1.getStartOffset() - o2.getStartOffset(); |
| } |
| }); |
| Collections.sort(rangesToHighlight, new Comparator<TextRange>() { |
| @Override |
| public int compare(TextRange o1, TextRange o2) { |
| return o1.getStartOffset() - o2.getStartOffset(); |
| } |
| }); |
| int i = 0; |
| int j = 0; |
| while (i < highlighters.length && j < rangesToHighlight.size()) { |
| RangeHighlighter highlighter = highlighters[i]; |
| TextRange highlighterRange = TextRange.create(highlighter); |
| TextRange refRange = rangesToHighlight.get(j); |
| if (refRange.equals(highlighterRange) && attributes.equals(highlighter.getTextAttributes()) && |
| highlighter.getLayer() == HighlighterLayer.SELECTION - 1) { |
| highlightManager.removeSegmentHighlighter(editor, highlighter); |
| i++; |
| } |
| else if (refRange.getStartOffset() > highlighterRange.getEndOffset()) { |
| i++; |
| } |
| else if (refRange.getEndOffset() < highlighterRange.getStartOffset()) { |
| j++; |
| } |
| else { |
| i++; |
| j++; |
| } |
| } |
| } |
| |
| private static void doHighlightRefs(HighlightManager highlightManager, @NotNull Editor editor, @NotNull List<PsiReference> refs, |
| TextAttributes attributes, boolean clearHighlights) { |
| List<TextRange> textRanges = new ArrayList<TextRange>(refs.size()); |
| for (PsiReference ref : refs) { |
| textRanges.addAll(getRangesToHighlight(ref)); |
| } |
| highlightRanges(highlightManager, editor, attributes, clearHighlights, textRanges); |
| } |
| |
| public static List<TextRange> getRangesToHighlight(final PsiReference ref) { |
| final List<TextRange> relativeRanges = ReferenceRange.getRanges(ref); |
| List<TextRange> answer = new ArrayList<TextRange>(relativeRanges.size()); |
| for (TextRange relativeRange : relativeRanges) { |
| PsiElement element = ref.getElement(); |
| TextRange range = safeCut(element.getTextRange(), relativeRange); |
| // injection occurs |
| answer.add(InjectedLanguageManager.getInstance(element.getProject()).injectedToHost(element, range)); |
| } |
| return answer; |
| } |
| |
| private static TextRange safeCut(TextRange range, TextRange relative) { |
| int start = Math.min(range.getEndOffset(), range.getStartOffset() + relative.getStartOffset()); |
| int end = Math.min(range.getEndOffset(), range.getStartOffset() + relative.getEndOffset()); |
| return new TextRange(start, end); |
| } |
| |
| public static void setStatusText(Project project, final String elementName, int refCount, boolean clearHighlights) { |
| String message; |
| if (clearHighlights) { |
| message = ""; |
| } |
| else if (refCount > 0) { |
| message = CodeInsightBundle.message(elementName != null ? |
| "status.bar.highlighted.usages.message" : |
| "status.bar.highlighted.usages.no.target.message", refCount, elementName, getShortcutText()); |
| } |
| else { |
| message = CodeInsightBundle.message(elementName != null ? |
| "status.bar.highlighted.usages.not.found.message" : |
| "status.bar.highlighted.usages.not.found.no.target.message", elementName); |
| } |
| WindowManager.getInstance().getStatusBar(project).setInfo(message); |
| } |
| |
| private static String getElementName(final PsiElement element) { |
| return ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE); |
| } |
| |
| public static String getShortcutText() { |
| final Shortcut[] shortcuts = ActionManager.getInstance() |
| .getAction(IdeActions.ACTION_HIGHLIGHT_USAGES_IN_FILE) |
| .getShortcutSet() |
| .getShortcuts(); |
| if (shortcuts.length == 0) { |
| return "<no key assigned>"; |
| } |
| return KeymapUtil.getShortcutText(shortcuts[0]); |
| } |
| } |