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.ide.bookmarks;
19 import com.intellij.codeInsight.daemon.GutterMark;
20 import com.intellij.icons.AllIcons;
21 import com.intellij.ide.IdeBundle;
22 import com.intellij.ide.structureView.StructureViewBuilder;
23 import com.intellij.ide.structureView.StructureViewModel;
24 import com.intellij.ide.structureView.TreeBasedStructureViewBuilder;
25 import com.intellij.lang.LanguageStructureViewBuilder;
26 import com.intellij.navigation.ItemPresentation;
27 import com.intellij.navigation.NavigationItem;
28 import com.intellij.openapi.editor.Document;
29 import com.intellij.openapi.editor.RangeMarker;
30 import com.intellij.openapi.editor.colors.CodeInsightColors;
31 import com.intellij.openapi.editor.colors.EditorColors;
32 import com.intellij.openapi.editor.colors.EditorColorsManager;
33 import com.intellij.openapi.editor.ex.MarkupModelEx;
34 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
35 import com.intellij.openapi.editor.impl.DocumentMarkupModel;
36 import com.intellij.openapi.editor.markup.GutterIconRenderer;
37 import com.intellij.openapi.editor.markup.HighlighterLayer;
38 import com.intellij.openapi.editor.markup.RangeHighlighter;
39 import com.intellij.openapi.editor.markup.TextAttributes;
40 import com.intellij.openapi.fileEditor.FileDocumentManager;
41 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
42 import com.intellij.openapi.project.Project;
43 import com.intellij.openapi.util.Comparing;
44 import com.intellij.openapi.util.Ref;
45 import com.intellij.openapi.util.text.StringUtil;
46 import com.intellij.openapi.vfs.VirtualFile;
47 import com.intellij.pom.Navigatable;
48 import com.intellij.psi.PsiDocumentManager;
49 import com.intellij.psi.PsiFile;
50 import com.intellij.psi.PsiManager;
51 import com.intellij.ui.ColorUtil;
52 import com.intellij.ui.JBColor;
53 import com.intellij.util.NotNullProducer;
54 import com.intellij.util.PlatformIcons;
55 import com.intellij.util.Processor;
56 import org.jetbrains.annotations.NotNull;
57 import org.jetbrains.annotations.Nullable;
62 public class Bookmark implements Navigatable {
63 public static final Icon DEFAULT_ICON = new MyCheckedIcon();
65 private final VirtualFile myFile;
66 @NotNull private final OpenFileDescriptor myTarget;
67 private final Project myProject;
69 private String myDescription;
70 private char myMnemonic = 0;
71 public static final Font MNEMONIC_FONT = new Font("Monospaced", 0, 11);
73 public Bookmark(@NotNull Project project, @NotNull VirtualFile file, int line, @NotNull String description) {
76 myDescription = description;
78 myTarget = new OpenFileDescriptor(project, file, line, -1, true);
83 public void updateHighlighter() {
88 private void addHighlighter() {
89 Document document = FileDocumentManager.getInstance().getCachedDocument(getFile());
90 if (document != null) {
91 createHighlighter((MarkupModelEx)DocumentMarkupModel.forDocument(document, myProject, true));
95 public RangeHighlighter createHighlighter(@NotNull MarkupModelEx markup) {
96 final RangeHighlighterEx myHighlighter;
99 myHighlighter = markup.addPersistentLineHighlighter(line, HighlighterLayer.ERROR + 1, null);
100 if (myHighlighter != null) {
101 myHighlighter.setGutterIconRenderer(new MyGutterIconRenderer(this));
103 TextAttributes textAttributes =
104 EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.BOOKMARKS_ATTRIBUTES);
106 Color stripeColor = textAttributes.getErrorStripeColor();
107 myHighlighter.setErrorStripeMarkColor(stripeColor != null ? stripeColor : Color.black);
108 myHighlighter.setErrorStripeTooltip(getBookmarkTooltip());
110 TextAttributes attributes = myHighlighter.getTextAttributes();
111 if (attributes == null) {
112 attributes = new TextAttributes();
114 attributes.setBackgroundColor(textAttributes.getBackgroundColor());
115 attributes.setForegroundColor(textAttributes.getForegroundColor());
116 myHighlighter.setTextAttributes(attributes);
120 myHighlighter = null;
122 return myHighlighter;
126 public Document getDocument() {
127 return FileDocumentManager.getInstance().getCachedDocument(getFile());
130 public void release() {
131 int line = getLine();
135 final Document document = getDocument();
136 if (document == null) return;
137 MarkupModelEx markup = (MarkupModelEx)DocumentMarkupModel.forDocument(document, myProject, true);
138 final Document markupDocument = markup.getDocument();
139 if (markupDocument.getLineCount() <= line) return;
140 final int startOffset = markupDocument.getLineStartOffset(line);
141 final int endOffset = markupDocument.getLineEndOffset(line);
143 final Ref<RangeHighlighterEx> found = new Ref<RangeHighlighterEx>();
144 markup.processRangeHighlightersOverlappingWith(startOffset, endOffset, new Processor<RangeHighlighterEx>() {
146 public boolean process(RangeHighlighterEx highlighter) {
147 GutterMark renderer = highlighter.getGutterIconRenderer();
148 if (renderer instanceof MyGutterIconRenderer && ((MyGutterIconRenderer)renderer).myBookmark == Bookmark.this) {
149 found.set(highlighter);
155 if (!found.isNull()) found.get().dispose();
158 public Icon getIcon() {
159 return myMnemonic == 0 ? DEFAULT_ICON : MnemonicIcon.getIcon(myMnemonic);
162 public String getDescription() {
163 return myDescription;
166 public void setDescription(String description) {
167 myDescription = description;
170 public char getMnemonic() {
174 public void setMnemonic(char mnemonic) {
175 myMnemonic = Character.toUpperCase(mnemonic);
179 public VirtualFile getFile() {
184 public String getNotEmptyDescription() {
185 return StringUtil.isEmpty(myDescription) ? null : myDescription;
188 public boolean isValid() {
189 if (!getFile().isValid()) {
193 // There is a possible case that target document line that is referenced by the current bookmark is removed. We assume
194 // that corresponding range marker becomes invalid then.
195 RangeMarker rangeMarker = myTarget.getRangeMarker();
196 return rangeMarker == null || rangeMarker.isValid();
200 public boolean canNavigate() {
201 return myTarget.canNavigate();
205 public boolean canNavigateToSource() {
206 return myTarget.canNavigateToSource();
210 public void navigate(boolean requestFocus) {
211 myTarget.navigate(requestFocus);
214 public int getLine() {
215 RangeMarker marker = myTarget.getRangeMarker();
216 if (marker != null && marker.isValid()) {
217 Document document = marker.getDocument();
218 return document.getLineNumber(marker.getStartOffset());
220 return myTarget.getLine();
224 public String toString() {
225 StringBuilder result = new StringBuilder(getQualifiedName());
226 String description = StringUtil.escapeXml(getNotEmptyDescription());
227 if (description != null) {
228 result.append(": ").append(description);
230 return result.toString();
233 public String getQualifiedName() {
234 String presentableUrl = myFile.getPresentableUrl();
235 if (myFile.isDirectory()) return presentableUrl;
237 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
238 final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(myFile);
240 if (psiFile == null) return presentableUrl;
242 StructureViewBuilder builder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(psiFile);
243 if (builder instanceof TreeBasedStructureViewBuilder) {
244 StructureViewModel model = ((TreeBasedStructureViewBuilder)builder).createStructureViewModel(null);
247 element = model.getCurrentEditorElement();
252 if (element instanceof NavigationItem) {
253 ItemPresentation presentation = ((NavigationItem)element).getPresentation();
254 if (presentation != null) {
255 presentableUrl = ((NavigationItem)element).getName() + " " + presentation.getLocationString();
260 return IdeBundle.message("bookmark.file.X.line.Y", presentableUrl, getLine() + 1);
263 private String getBookmarkTooltip() {
264 StringBuilder result = new StringBuilder("Bookmark");
265 if (myMnemonic != 0) {
266 result.append(" ").append(myMnemonic);
268 String description = StringUtil.escapeXml(getNotEmptyDescription());
269 if (description != null) {
270 result.append(": ").append(description);
272 return result.toString();
275 static class MnemonicIcon implements Icon {
276 private static final MnemonicIcon[] cache = new MnemonicIcon[36];//0..9 + A..Z
277 private final char myMnemonic;
280 static MnemonicIcon getIcon(char mnemonic) {
281 int index = mnemonic - 48;
284 if (index < 0 || index > cache.length-1)
285 return new MnemonicIcon(mnemonic);
286 if (cache[index] == null)
287 cache[index] = new MnemonicIcon(mnemonic);
291 private MnemonicIcon(char mnemonic) {
292 myMnemonic = mnemonic;
296 public void paintIcon(Component c, Graphics g, int x, int y) {
297 g.setColor(new JBColor(new NotNullProducer<Color>() {
300 public Color produce() {
301 //noinspection UseJBColor
302 return !darkBackground() ? new Color(0xffffcc) : new Color(0x675133);
305 g.fillRect(x, y, getIconWidth(), getIconHeight());
307 g.setColor(JBColor.GRAY);
308 g.drawRect(x, y, getIconWidth(), getIconHeight());
310 g.setColor(EditorColorsManager.getInstance().getGlobalScheme().getDefaultForeground());
311 final Font oldFont = g.getFont();
312 g.setFont(MNEMONIC_FONT);
314 ((Graphics2D)g).drawString(Character.toString(myMnemonic), x + 3, y + getIconHeight() - 1.5F);
319 public int getIconWidth() {
320 return DEFAULT_ICON.getIconWidth();
324 public int getIconHeight() {
325 return DEFAULT_ICON.getIconHeight();
329 public boolean equals(Object o) {
330 if (this == o) return true;
331 if (o == null || getClass() != o.getClass()) return false;
333 MnemonicIcon that = (MnemonicIcon)o;
335 return myMnemonic == that.myMnemonic;
339 public int hashCode() {
340 return (int)myMnemonic;
344 private static class MyCheckedIcon implements Icon {
346 public void paintIcon(Component c, Graphics g, int x, int y) {
347 (darkBackground() ? AllIcons.Actions.CheckedGrey : AllIcons.Actions.CheckedBlack).paintIcon(c, g, x, y);
351 public int getIconWidth() {
352 return PlatformIcons.CHECK_ICON.getIconWidth();
356 public int getIconHeight() {
357 return PlatformIcons.CHECK_ICON.getIconHeight();
361 private static boolean darkBackground() {
362 return ColorUtil.isDark(EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.GUTTER_BACKGROUND));
365 private static class MyGutterIconRenderer extends GutterIconRenderer {
366 private final Bookmark myBookmark;
368 public MyGutterIconRenderer(@NotNull Bookmark bookmark) {
369 myBookmark = bookmark;
374 public Icon getIcon() {
375 return myBookmark.getIcon();
379 public String getTooltipText() {
380 return myBookmark.getBookmarkTooltip();
384 public boolean equals(Object obj) {
385 return obj instanceof MyGutterIconRenderer &&
386 Comparing.equal(getTooltipText(), ((MyGutterIconRenderer)obj).getTooltipText()) &&
387 Comparing.equal(getIcon(), ((MyGutterIconRenderer)obj).getIcon());
391 public int hashCode() {
392 return getIcon().hashCode();