| /* |
| * Copyright 2000-2011 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.execution.impl; |
| |
| import com.intellij.execution.filters.Filter; |
| import com.intellij.execution.filters.HyperlinkInfo; |
| import com.intellij.execution.filters.HyperlinkInfoBase; |
| import com.intellij.ide.OccurenceNavigator; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.LogicalPosition; |
| import com.intellij.openapi.editor.colors.CodeInsightColors; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.event.EditorMouseAdapter; |
| import com.intellij.openapi.editor.event.EditorMouseEvent; |
| import com.intellij.openapi.editor.ex.EditorEx; |
| import com.intellij.openapi.editor.ex.MarkupModelEx; |
| import com.intellij.openapi.editor.ex.RangeHighlighterEx; |
| import com.intellij.openapi.editor.ex.util.EditorUtil; |
| import com.intellij.openapi.editor.markup.HighlighterLayer; |
| import com.intellij.openapi.editor.markup.HighlighterTargetArea; |
| import com.intellij.openapi.editor.markup.RangeHighlighter; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.pom.NavigatableAdapter; |
| import com.intellij.ui.awt.RelativePoint; |
| import com.intellij.util.CommonProcessors; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.FilteringProcessor; |
| import com.intellij.util.containers.hash.LinkedHashMap; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.awt.*; |
| import java.awt.event.MouseEvent; |
| import java.awt.event.MouseMotionAdapter; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * @author peter |
| */ |
| public class EditorHyperlinkSupport { |
| public static final Key<TextAttributes> OLD_HYPERLINK_TEXT_ATTRIBUTES = Key.create("OLD_HYPERLINK_TEXT_ATTRIBUTES"); |
| private static final Key<HyperlinkInfoTextAttributes> HYPERLINK = Key.create("HYPERLINK"); |
| private static final int HYPERLINK_LAYER = HighlighterLayer.SELECTION - 123; |
| private static final int HIGHLIGHT_LAYER = HighlighterLayer.SELECTION - 111; |
| |
| private final Editor myEditor; |
| @NotNull private final Project myProject; |
| |
| public EditorHyperlinkSupport(@NotNull final Editor editor, @NotNull final Project project) { |
| myEditor = editor; |
| myProject = project; |
| |
| editor.addEditorMouseListener(new EditorMouseAdapter() { |
| @Override |
| public void mouseClicked(EditorMouseEvent e) { |
| final MouseEvent mouseEvent = e.getMouseEvent(); |
| if (mouseEvent.getButton() == MouseEvent.BUTTON1 && !mouseEvent.isPopupTrigger()) { |
| Runnable runnable = getLinkNavigationRunnable(myEditor.xyToLogicalPosition(e.getMouseEvent().getPoint())); |
| if (runnable != null) { |
| runnable.run(); |
| } |
| } |
| } |
| }); |
| |
| editor.getContentComponent().addMouseMotionListener(new MouseMotionAdapter() { |
| public void mouseMoved(final MouseEvent e) { |
| final HyperlinkInfo info = getHyperlinkInfoByPoint(e.getPoint()); |
| if (info != null) { |
| myEditor.getContentComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| } |
| else { |
| final Cursor cursor = editor instanceof EditorEx ? |
| UIUtil.getTextCursor(((EditorEx)editor).getBackgroundColor()) : |
| Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR); |
| myEditor.getContentComponent().setCursor(cursor); |
| } |
| } |
| } |
| ); |
| } |
| |
| public void clearHyperlinks() { |
| for (RangeHighlighter highlighter : getHyperlinks(0, myEditor.getDocument().getTextLength(), myEditor)) { |
| removeHyperlink(highlighter); |
| } |
| } |
| |
| @Deprecated |
| public Map<RangeHighlighter, HyperlinkInfo> getHyperlinks() { |
| LinkedHashMap<RangeHighlighter, HyperlinkInfo> result = new LinkedHashMap<RangeHighlighter, HyperlinkInfo>(); |
| for (RangeHighlighter highlighter : getHyperlinks(0, myEditor.getDocument().getTextLength(), myEditor)) { |
| HyperlinkInfo info = getHyperlinkInfo(highlighter); |
| if (info != null) { |
| result.put(highlighter, info); |
| } |
| } |
| return result; |
| } |
| |
| @Nullable |
| public Runnable getLinkNavigationRunnable(final LogicalPosition logical) { |
| if (EditorUtil.inVirtualSpace(myEditor, logical)) { |
| return null; |
| } |
| |
| final RangeHighlighter range = findLinkRangeAt(this.myEditor.logicalPositionToOffset(logical)); |
| if (range != null) { |
| final HyperlinkInfo hyperlinkInfo = getHyperlinkInfo(range); |
| if (hyperlinkInfo != null) { |
| return new Runnable() { |
| @Override |
| public void run() { |
| if (hyperlinkInfo instanceof HyperlinkInfoBase) { |
| RelativePoint point = new RelativePoint(myEditor.getContentComponent(), myEditor.logicalPositionToXY(logical)); |
| ((HyperlinkInfoBase)hyperlinkInfo).navigate(myProject, point); |
| } |
| else { |
| hyperlinkInfo.navigate(myProject); |
| } |
| linkFollowed(myEditor, getHyperlinks(0, myEditor.getDocument().getTextLength(),myEditor), range); |
| } |
| }; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HyperlinkInfo getHyperlinkInfo(@NotNull RangeHighlighter range) { |
| final HyperlinkInfoTextAttributes attributes = range.getUserData(HYPERLINK); |
| return attributes != null ? attributes.getHyperlinkInfo() : null; |
| } |
| |
| @Nullable |
| private RangeHighlighter findLinkRangeAt(final int offset) { |
| //noinspection LoopStatementThatDoesntLoop |
| for (final RangeHighlighter highlighter : getHyperlinks(offset, offset, myEditor)) { |
| return highlighter; |
| } |
| return null; |
| } |
| |
| @Nullable |
| private HyperlinkInfo getHyperlinkAt(final int offset) { |
| RangeHighlighter range = findLinkRangeAt(offset); |
| return range == null ? null : getHyperlinkInfo(range); |
| } |
| |
| public List<RangeHighlighter> findAllHyperlinksOnLine(int line) { |
| final int lineStart = myEditor.getDocument().getLineStartOffset(line); |
| final int lineEnd = myEditor.getDocument().getLineEndOffset(line); |
| return getHyperlinks(lineStart, lineEnd, myEditor); |
| } |
| |
| public static List<RangeHighlighter> getHyperlinks(int startOffset, int endOffset, final Editor editor) { |
| final MarkupModelEx markupModel = (MarkupModelEx)editor.getMarkupModel(); |
| final CommonProcessors.CollectProcessor<RangeHighlighterEx> processor = new CommonProcessors.CollectProcessor<RangeHighlighterEx>(); |
| markupModel.processRangeHighlightersOverlappingWith(startOffset, endOffset, |
| new FilteringProcessor<RangeHighlighterEx>(new Condition<RangeHighlighterEx>() { |
| @Override |
| public boolean value(RangeHighlighterEx rangeHighlighterEx) { |
| return rangeHighlighterEx.getEditorFilter().avaliableIn(editor) && |
| HYPERLINK_LAYER == rangeHighlighterEx.getLayer() && |
| rangeHighlighterEx.isValid() && |
| getHyperlinkInfo(rangeHighlighterEx) != null; |
| } |
| }, processor) |
| ); |
| return new ArrayList<RangeHighlighter>(processor.getResults()); |
| } |
| |
| public void removeHyperlink(@NotNull RangeHighlighter hyperlink) { |
| myEditor.getMarkupModel().removeHighlighter(hyperlink); |
| } |
| |
| @Nullable |
| public HyperlinkInfo getHyperlinkInfoByLineAndCol(final int line, final int col) { |
| return getHyperlinkAt(myEditor.logicalPositionToOffset(new LogicalPosition(line, col))); |
| } |
| |
| /** |
| * @deprecated for binary compatibility with older plugins |
| * @see #createHyperlink(int, int, com.intellij.openapi.editor.markup.TextAttributes, com.intellij.execution.filters.HyperlinkInfo) |
| */ |
| public void addHyperlink(final int highlightStartOffset, |
| final int highlightEndOffset, |
| @Nullable final TextAttributes highlightAttributes, |
| @NotNull final HyperlinkInfo hyperlinkInfo) { |
| createHyperlink(highlightStartOffset, highlightEndOffset, highlightAttributes, hyperlinkInfo); |
| } |
| |
| @NotNull |
| public RangeHighlighter createHyperlink(final int highlightStartOffset, |
| final int highlightEndOffset, |
| @Nullable final TextAttributes highlightAttributes, |
| @NotNull final HyperlinkInfo hyperlinkInfo) { |
| TextAttributes textAttributes = highlightAttributes != null ? highlightAttributes : getHyperlinkAttributes(); |
| final RangeHighlighter highlighter = myEditor.getMarkupModel().addRangeHighlighter(highlightStartOffset, |
| highlightEndOffset, |
| HYPERLINK_LAYER, |
| textAttributes, |
| HighlighterTargetArea.EXACT_RANGE); |
| associateHyperlink(highlighter, hyperlinkInfo); |
| return highlighter; |
| } |
| |
| public static void associateHyperlink(@NotNull RangeHighlighter highlighter, @NotNull HyperlinkInfo hyperlinkInfo) { |
| highlighter.putUserData(HYPERLINK, new HyperlinkInfoTextAttributes(hyperlinkInfo)); |
| } |
| |
| @Nullable |
| public HyperlinkInfo getHyperlinkInfoByPoint(final Point p) { |
| final LogicalPosition pos = myEditor.xyToLogicalPosition(new Point(p.x, p.y)); |
| if (EditorUtil.inVirtualSpace(myEditor, pos)) { |
| return null; |
| } |
| |
| return getHyperlinkInfoByLineAndCol(pos.line, pos.column); |
| } |
| |
| @Deprecated |
| public void highlightHyperlinks(final Filter customFilter, final Filter predefinedMessageFilter, final int line1, final int endLine) { |
| highlightHyperlinks(new Filter() { |
| @Nullable |
| @Override |
| public Result applyFilter(String line, int entireLength) { |
| Result result = customFilter.applyFilter(line, entireLength); |
| return result != null ? result : predefinedMessageFilter.applyFilter(line, entireLength); |
| } |
| }, line1, endLine); |
| } |
| |
| public void highlightHyperlinks(final Filter customFilter, final int line1, final int endLine) { |
| final Document document = myEditor.getDocument(); |
| |
| final int startLine = Math.max(0, line1); |
| |
| for (int line = startLine; line <= endLine; line++) { |
| int endOffset = document.getLineEndOffset(line); |
| if (endOffset < document.getTextLength()) { |
| endOffset++; // add '\n' |
| } |
| final String text = getLineText(document, line, true); |
| Filter.Result result = customFilter.applyFilter(text, endOffset); |
| if (result != null) { |
| for (Filter.ResultItem resultItem : result.getResultItems()) { |
| if (resultItem.getHyperlinkInfo() != null) { |
| createHyperlink(resultItem.getHighlightStartOffset(), resultItem.getHighlightEndOffset(), resultItem.getHighlightAttributes(), resultItem.getHyperlinkInfo()); |
| } |
| else if (resultItem.getHighlightAttributes() != null) { |
| addHighlighter(resultItem.getHighlightStartOffset(), resultItem.getHighlightEndOffset(), resultItem.getHighlightAttributes()); |
| } |
| } |
| } |
| } |
| } |
| |
| public void addHighlighter(int highlightStartOffset, int highlightEndOffset, TextAttributes highlightAttributes) { |
| myEditor.getMarkupModel().addRangeHighlighter(highlightStartOffset, highlightEndOffset, HIGHLIGHT_LAYER, highlightAttributes, |
| HighlighterTargetArea.EXACT_RANGE); |
| } |
| |
| private static TextAttributes getHyperlinkAttributes() { |
| return EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES); |
| } |
| |
| private static TextAttributes getFollowedHyperlinkAttributes() { |
| return EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.FOLLOWED_HYPERLINK_ATTRIBUTES); |
| } |
| |
| @Nullable |
| public static OccurenceNavigator.OccurenceInfo getNextOccurrence(final Editor editor, |
| final int delta, |
| final Consumer<RangeHighlighter> action) { |
| final List<RangeHighlighter> ranges = getHyperlinks(0, editor.getDocument().getTextLength(),editor); |
| if (ranges.isEmpty()) { |
| return null; |
| } |
| int i; |
| for (i = 0; i < ranges.size(); i++) { |
| RangeHighlighter range = ranges.get(i); |
| if (range.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES) != null) { |
| break; |
| } |
| } |
| i = i % ranges.size(); |
| int newIndex = i; |
| while (newIndex < ranges.size() && newIndex >= 0) { |
| newIndex = (newIndex + delta + ranges.size()) % ranges.size(); |
| final RangeHighlighter next = ranges.get(newIndex); |
| if (editor.getFoldingModel().getCollapsedRegionAtOffset(next.getStartOffset()) == null) { |
| return new OccurenceNavigator.OccurenceInfo(new NavigatableAdapter() { |
| public void navigate(final boolean requestFocus) { |
| action.consume(next); |
| linkFollowed(editor, ranges, next); |
| } |
| }, newIndex == -1 ? -1 : newIndex + 1, ranges.size()); |
| } |
| if (newIndex == i) { |
| break; // cycled through everything, found no next/prev hyperlink |
| } |
| } |
| return null; |
| } |
| |
| // todo fix link followed here! |
| private static void linkFollowed(Editor editor, Collection<RangeHighlighter> ranges, final RangeHighlighter link) { |
| MarkupModelEx markupModel = (MarkupModelEx)editor.getMarkupModel(); |
| for (RangeHighlighter range : ranges) { |
| TextAttributes oldAttr = range.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES); |
| if (oldAttr != null) { |
| markupModel.setRangeHighlighterAttributes(range, oldAttr); |
| range.putUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES, null); |
| } |
| if (range == link) { |
| range.putUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES, range.getTextAttributes()); |
| markupModel.setRangeHighlighterAttributes(range, getFollowedHyperlinkAttributes()); |
| } |
| } |
| //refresh highlighter text attributes |
| markupModel.addRangeHighlighter(0, 0, HYPERLINK_LAYER, getHyperlinkAttributes(), HighlighterTargetArea.EXACT_RANGE).dispose(); |
| } |
| |
| |
| public static String getLineText(Document document, int lineNumber, boolean includeEol) { |
| int endOffset = document.getLineEndOffset(lineNumber); |
| if (includeEol && endOffset < document.getTextLength()) { |
| endOffset++; |
| } |
| return document.getCharsSequence().subSequence(document.getLineStartOffset(lineNumber), endOffset).toString(); |
| } |
| |
| private static class HyperlinkInfoTextAttributes extends TextAttributes { |
| private HyperlinkInfo myHyperlinkInfo; |
| |
| public HyperlinkInfoTextAttributes(@NotNull HyperlinkInfo hyperlinkInfo) { |
| myHyperlinkInfo = hyperlinkInfo; |
| } |
| |
| @NotNull |
| public HyperlinkInfo getHyperlinkInfo() { |
| return myHyperlinkInfo; |
| } |
| } |
| } |