| /* |
| * 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); |
| } |
| } |
| } |
| } |
| } |
| |