com.intellij.ide.bookmarks.BookmarkManager.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.ide.bookmarks.BookmarkManager.java

Source

/*
 * 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.ide.bookmarks;

import com.intellij.ide.IdeBundle;
import com.intellij.openapi.components.*;
import com.intellij.openapi.components.StoragePathMacros;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.editor.ex.MarkupModelEx;
import com.intellij.openapi.editor.impl.DocumentMarkupModel;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.DumbAwareRunnable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.InputValidator;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.ui.UIUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.awt.event.InputEvent;
import java.util.*;
import java.util.List;

@State(name = "BookmarkManager", storages = { @Storage(file = StoragePathMacros.WORKSPACE_FILE) })
public class BookmarkManager extends AbstractProjectComponent implements PersistentStateComponent<Element> {
    private static final int MAX_AUTO_DESCRIPTION_SIZE = 50;

    private final List<Bookmark> myBookmarks = new ArrayList<Bookmark>();

    private final MessageBus myBus;

    public static BookmarkManager getInstance(Project project) {
        return project.getComponent(BookmarkManager.class);
    }

    public BookmarkManager(Project project, MessageBus bus, PsiDocumentManager documentManager) {
        super(project);
        myBus = bus;
        EditorEventMulticaster multicaster = EditorFactory.getInstance().getEventMulticaster();
        multicaster.addDocumentListener(new MyDocumentListener(), myProject);
        multicaster.addEditorMouseListener(new MyEditorMouseListener(), myProject);

        documentManager.addListener(new PsiDocumentManager.Listener() {
            @Override
            public void documentCreated(@NotNull final Document document, PsiFile psiFile) {
                final VirtualFile file = FileDocumentManager.getInstance().getFile(document);
                if (file == null)
                    return;
                for (final Bookmark bookmark : myBookmarks) {
                    if (Comparing.equal(bookmark.getFile(), file)) {
                        UIUtil.invokeLaterIfNeeded(new Runnable() {
                            @Override
                            public void run() {
                                if (myProject.isDisposed())
                                    return;
                                bookmark.createHighlighter(
                                        (MarkupModelEx) DocumentMarkupModel.forDocument(document, myProject, true));
                            }
                        });
                    }
                }
            }

            @Override
            public void fileCreated(@NotNull PsiFile file, @NotNull Document document) {
            }
        });
    }

    public void editDescription(@NotNull Bookmark bookmark) {
        String description = Messages.showInputDialog(myProject,
                IdeBundle.message("action.bookmark.edit.description.dialog.message"),
                IdeBundle.message("action.bookmark.edit.description.dialog.title"), Messages.getQuestionIcon(),
                bookmark.getDescription(), new InputValidator() {
                    @Override
                    public boolean checkInput(String inputString) {
                        return true;
                    }

                    @Override
                    public boolean canClose(String inputString) {
                        return true;
                    }
                });
        if (description != null) {
            setDescription(bookmark, description);
        }
    }

    @NotNull
    @Override
    public String getComponentName() {
        return "BookmarkManager";
    }

    public void addEditorBookmark(Editor editor, int lineIndex) {
        Document document = editor.getDocument();
        PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
        if (psiFile == null)
            return;

        final VirtualFile virtualFile = psiFile.getVirtualFile();
        if (virtualFile == null)
            return;

        addTextBookmark(virtualFile, lineIndex, getAutoDescription(editor, lineIndex));
    }

    public Bookmark addTextBookmark(VirtualFile file, int lineIndex, String description) {
        Bookmark b = new Bookmark(myProject, file, lineIndex, description);
        myBookmarks.add(0, b);
        myBus.syncPublisher(BookmarksListener.TOPIC).bookmarkAdded(b);
        return b;
    }

    public static String getAutoDescription(final Editor editor, final int lineIndex) {
        String autoDescription = editor.getSelectionModel().getSelectedText();
        if (autoDescription == null) {
            Document document = editor.getDocument();
            autoDescription = document.getCharsSequence()
                    .subSequence(document.getLineStartOffset(lineIndex), document.getLineEndOffset(lineIndex))
                    .toString().trim();
        }
        if (autoDescription.length() > MAX_AUTO_DESCRIPTION_SIZE) {
            return autoDescription.substring(0, MAX_AUTO_DESCRIPTION_SIZE) + "...";
        }
        return autoDescription;
    }

    @Nullable
    public Bookmark addFileBookmark(VirtualFile file, String description) {
        if (file == null)
            return null;
        if (findFileBookmark(file) != null)
            return null;

        Bookmark b = new Bookmark(myProject, file, -1, description);
        myBookmarks.add(0, b);
        myBus.syncPublisher(BookmarksListener.TOPIC).bookmarkAdded(b);
        return b;
    }

    @NotNull
    public List<Bookmark> getValidBookmarks() {
        List<Bookmark> answer = new ArrayList<Bookmark>();
        for (Bookmark bookmark : myBookmarks) {
            if (bookmark.isValid())
                answer.add(bookmark);
        }
        return answer;
    }

    @Nullable
    public Bookmark findEditorBookmark(@NotNull Document document, int line) {
        for (Bookmark bookmark : myBookmarks) {
            if (bookmark.getDocument() == document && bookmark.getLine() == line) {
                return bookmark;
            }
        }

        return null;
    }

    @Nullable
    public Bookmark findFileBookmark(@NotNull VirtualFile file) {
        for (Bookmark bookmark : myBookmarks) {
            if (Comparing.equal(bookmark.getFile(), file) && bookmark.getLine() == -1)
                return bookmark;
        }

        return null;
    }

    @Nullable
    public Bookmark findBookmarkForMnemonic(char m) {
        final char mm = Character.toUpperCase(m);
        for (Bookmark bookmark : myBookmarks) {
            if (mm == bookmark.getMnemonic())
                return bookmark;
        }
        return null;
    }

    public boolean hasBookmarksWithMnemonics() {
        for (Bookmark bookmark : myBookmarks) {
            if (bookmark.getMnemonic() != 0)
                return true;
        }

        return false;
    }

    public void removeBookmark(@NotNull Bookmark bookmark) {
        myBookmarks.remove(bookmark);
        bookmark.release();
        myBus.syncPublisher(BookmarksListener.TOPIC).bookmarkRemoved(bookmark);
    }

    @Override
    public Element getState() {
        Element container = new Element("BookmarkManager");
        writeExternal(container);
        return container;
    }

    @Override
    public void loadState(final Element state) {
        StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new DumbAwareRunnable() {
            @Override
            public void run() {
                BookmarksListener publisher = myBus.syncPublisher(BookmarksListener.TOPIC);
                for (Bookmark bookmark : myBookmarks) {
                    bookmark.release();
                    publisher.bookmarkRemoved(bookmark);
                }
                myBookmarks.clear();

                readExternal(state);
            }
        });
    }

    private void readExternal(Element element) {
        for (final Object o : element.getChildren()) {
            Element bookmarkElement = (Element) o;

            if ("bookmark".equals(bookmarkElement.getName())) {
                String url = bookmarkElement.getAttributeValue("url");
                String line = bookmarkElement.getAttributeValue("line");
                String description = StringUtil.notNullize(bookmarkElement.getAttributeValue("description"));
                String mnemonic = bookmarkElement.getAttributeValue("mnemonic");

                Bookmark b = null;
                VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url);
                if (file != null) {
                    if (line != null) {
                        try {
                            int lineIndex = Integer.parseInt(line);
                            b = addTextBookmark(file, lineIndex, description);
                        } catch (NumberFormatException e) {
                            // Ignore. Will miss bookmark if line number cannot be parsed
                        }
                    } else {
                        b = addFileBookmark(file, description);
                    }
                }

                if (b != null && mnemonic != null && mnemonic.length() == 1) {
                    setMnemonic(b, mnemonic.charAt(0));
                }
            }
        }
    }

    private void writeExternal(Element element) {
        List<Bookmark> reversed = new ArrayList<Bookmark>(myBookmarks);
        Collections.reverse(reversed);

        for (Bookmark bookmark : reversed) {
            if (!bookmark.isValid())
                continue;
            Element bookmarkElement = new Element("bookmark");

            bookmarkElement.setAttribute("url", bookmark.getFile().getUrl());

            String description = bookmark.getNotEmptyDescription();
            if (description != null) {
                bookmarkElement.setAttribute("description", description);
            }

            int line = bookmark.getLine();
            if (line >= 0) {
                bookmarkElement.setAttribute("line", String.valueOf(line));
            }

            char mnemonic = bookmark.getMnemonic();
            if (mnemonic != 0) {
                bookmarkElement.setAttribute("mnemonic", String.valueOf(mnemonic));
            }

            element.addContent(bookmarkElement);
        }
    }

    /**
     * Try to move bookmark one position up in the list
     *
     * @return bookmark list after moving
     */
    @NotNull
    public List<Bookmark> moveBookmarkUp(@NotNull Bookmark bookmark) {
        final int index = myBookmarks.indexOf(bookmark);
        if (index > 0) {
            Collections.swap(myBookmarks, index, index - 1);
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    myBus.syncPublisher(BookmarksListener.TOPIC).bookmarkChanged(myBookmarks.get(index));
                    myBus.syncPublisher(BookmarksListener.TOPIC).bookmarkChanged(myBookmarks.get(index - 1));
                }
            });
        }
        return myBookmarks;
    }

    /**
     * Try to move bookmark one position down in the list
     *
     * @return bookmark list after moving
     */
    @NotNull
    public List<Bookmark> moveBookmarkDown(@NotNull Bookmark bookmark) {
        final int index = myBookmarks.indexOf(bookmark);
        if (index < myBookmarks.size() - 1) {
            Collections.swap(myBookmarks, index, index + 1);
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    myBus.syncPublisher(BookmarksListener.TOPIC).bookmarkChanged(myBookmarks.get(index));
                    myBus.syncPublisher(BookmarksListener.TOPIC).bookmarkChanged(myBookmarks.get(index + 1));
                }
            });
        }

        return myBookmarks;
    }

    @Nullable
    public Bookmark getNextBookmark(@NotNull Editor editor, boolean isWrapped) {
        Bookmark[] bookmarksForDocument = getBookmarksForDocument(editor.getDocument());
        int lineNumber = editor.getCaretModel().getLogicalPosition().line;
        for (Bookmark bookmark : bookmarksForDocument) {
            if (bookmark.getLine() > lineNumber)
                return bookmark;
        }
        if (isWrapped && bookmarksForDocument.length > 0) {
            return bookmarksForDocument[0];
        }
        return null;
    }

    @Nullable
    public Bookmark getPreviousBookmark(@NotNull Editor editor, boolean isWrapped) {
        Bookmark[] bookmarksForDocument = getBookmarksForDocument(editor.getDocument());
        int lineNumber = editor.getCaretModel().getLogicalPosition().line;
        for (int i = bookmarksForDocument.length - 1; i >= 0; i--) {
            Bookmark bookmark = bookmarksForDocument[i];
            if (bookmark.getLine() < lineNumber)
                return bookmark;
        }
        if (isWrapped && bookmarksForDocument.length > 0) {
            return bookmarksForDocument[bookmarksForDocument.length - 1];
        }
        return null;
    }

    @NotNull
    private Bookmark[] getBookmarksForDocument(@NotNull Document document) {
        ArrayList<Bookmark> answer = new ArrayList<Bookmark>();
        for (Bookmark bookmark : getValidBookmarks()) {
            if (document.equals(bookmark.getDocument())) {
                answer.add(bookmark);
            }
        }

        Bookmark[] bookmarks = answer.toArray(new Bookmark[answer.size()]);
        Arrays.sort(bookmarks, new Comparator<Bookmark>() {
            @Override
            public int compare(final Bookmark o1, final Bookmark o2) {
                return o1.getLine() - o2.getLine();
            }
        });
        return bookmarks;
    }

    public void setMnemonic(@NotNull Bookmark bookmark, char c) {
        final Bookmark old = findBookmarkForMnemonic(c);
        if (old != null)
            removeBookmark(old);

        bookmark.setMnemonic(c);
        myBus.syncPublisher(BookmarksListener.TOPIC).bookmarkChanged(bookmark);
    }

    public void setDescription(@NotNull Bookmark bookmark, String description) {
        bookmark.setDescription(description);
        myBus.syncPublisher(BookmarksListener.TOPIC).bookmarkChanged(bookmark);
    }

    public void colorsChanged() {
        for (Bookmark bookmark : myBookmarks) {
            bookmark.updateHighlighter();
        }
    }

    private class MyEditorMouseListener extends EditorMouseAdapter {
        @Override
        public void mouseClicked(final EditorMouseEvent e) {
            if (e.getArea() != EditorMouseEventArea.LINE_MARKERS_AREA)
                return;
            if (e.getMouseEvent().isPopupTrigger())
                return;
            if ((e.getMouseEvent().getModifiers()
                    & (SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK)) == 0)
                return;

            Editor editor = e.getEditor();
            int line = editor
                    .xyToLogicalPosition(new Point(e.getMouseEvent().getX(), e.getMouseEvent().getY())).line;
            if (line < 0)
                return;

            Document document = editor.getDocument();

            Bookmark bookmark = findEditorBookmark(document, line);
            if (bookmark == null) {
                addEditorBookmark(editor, line);
            } else {
                removeBookmark(bookmark);
            }
            e.consume();
        }
    }

    private class MyDocumentListener extends DocumentAdapter {
        @Override
        public void documentChanged(DocumentEvent e) {
            List<Bookmark> bookmarksToRemove = null;
            for (Bookmark bookmark : myBookmarks) {
                if (!bookmark.isValid()) {
                    if (bookmarksToRemove == null) {
                        bookmarksToRemove = new ArrayList<Bookmark>();
                    }
                    bookmarksToRemove.add(bookmark);
                }
            }

            if (bookmarksToRemove != null) {
                for (Bookmark bookmark : bookmarksToRemove) {
                    removeBookmark(bookmark);
                }
            }
        }
    }
}