From ef4cb629f9f3afdcfb6eb8f14865802b2aea152c Mon Sep 17 00:00:00 2001 From: Rustam Vishnyakov Date: Wed, 27 Jun 2012 19:24:41 +0400 Subject: [PATCH] Custom folding region navigation (IDEA-81770) --- .../lang/folding/CustomFoldingBuilder.java | 3 +- .../customFolding/GotoCustomRegionAction.java | 88 +++++++++ .../customFolding/GotoCustomRegionDialog.form | 25 +++ .../customFolding/GotoCustomRegionDialog.java | 169 ++++++++++++++++++ .../src/messages/IdeBundle.properties | 3 + .../src/idea/Keymap_Default.xml | 3 + .../src/idea/LangActions.xml | 1 + 7 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionAction.java create mode 100644 platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionDialog.form create mode 100644 platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionDialog.java diff --git a/platform/lang-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java b/platform/lang-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java index 7f7567838869..b215fad80285 100644 --- a/platform/lang-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java +++ b/platform/lang-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java @@ -6,7 +6,6 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiComment; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; import com.intellij.util.containers.Stack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -132,7 +131,7 @@ public abstract class CustomFoldingBuilder extends FoldingBuilderEx implements D * @param node The node which may contain custom region start. * @return True if the node marks a custom region start. */ - protected final boolean isCustomRegionStart(ASTNode node) { + public final boolean isCustomRegionStart(ASTNode node) { if (isCustomFoldingCandidate(node)) { String nodeText = node.getText(); CustomFoldingProvider defaultProvider = getDefaultProvider(nodeText); diff --git a/platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionAction.java b/platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionAction.java new file mode 100644 index 000000000000..9a45520091ef --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionAction.java @@ -0,0 +1,88 @@ +/* + * 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.lang.customFolding; + +import com.intellij.ide.IdeBundle; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; + +/** + * @author Rustam Vishnyakov + */ +public class GotoCustomRegionAction extends AnAction implements DumbAware { + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = e.getProject(); + final Editor editor = e.getData(PlatformDataKeys.EDITOR); + if (Boolean.TRUE.equals(e.getData(PlatformDataKeys.IS_MODAL_CONTEXT))) { + return; + } + if (project != null && editor != null) { + if (DumbService.getInstance(project).isDumb()) { + DumbService.getInstance(project).showDumbModeNotification(IdeBundle.message("goto.custom.region.message.dumb.mode")); + return; + } + CommandProcessor processor = CommandProcessor.getInstance(); + processor.executeCommand( + project, + new Runnable() { + public void run() { + GotoCustomRegionDialog dialog = new GotoCustomRegionDialog(project, editor); + dialog.show(); + if (dialog.isOK()) { + PsiElement navigationElement = dialog.getNavigationElement(); + if (navigationElement != null) { + navigateTo(editor, navigationElement); + IdeDocumentHistory.getInstance(project).includeCurrentCommandAsNavigation(); + } + } + } + }, + IdeBundle.message("goto.custom.region.command"), + null); + } + } + + @Override + public void update(AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setText("Custom Region..."); + final Editor editor = e.getData(PlatformDataKeys.EDITOR); + final Project project = e.getProject(); + boolean isAvailable = editor != null && project != null; + presentation.setEnabled(isAvailable); + presentation.setVisible(isAvailable); + } + + private static void navigateTo(Editor editor, PsiElement element) { + int offset = element.getTextRange().getStartOffset(); + if (offset >= 0 && offset < editor.getDocument().getTextLength()) { + editor.getCaretModel().moveToOffset(offset); + editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); + editor.getSelectionModel().removeSelection(); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionDialog.form b/platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionDialog.form new file mode 100644 index 000000000000..cf1369bd01a7 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionDialog.form @@ -0,0 +1,25 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + +
diff --git a/platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionDialog.java b/platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionDialog.java new file mode 100644 index 000000000000..8873074402e3 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionDialog.java @@ -0,0 +1,169 @@ +/* + * 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.lang.customFolding; + +import com.intellij.ide.IdeBundle; +import com.intellij.lang.Language; +import com.intellij.lang.folding.*; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.ui.components.JBList; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.*; +import java.util.List; + +/** + * @author Rustam Vishnyakov + */ +public class GotoCustomRegionDialog extends DialogWrapper { + private JBList myRegionsList; + private JPanel myContentPane; + private JBScrollPane myScrollPane; + private final Editor myEditor; + private final Project myProject; + + protected GotoCustomRegionDialog(@Nullable Project project, @NotNull Editor editor) { + super(project); + myEditor = editor; + myProject = project; + Collection descriptors = getCustomFoldingDescriptors(); + init(); + if (descriptors.size() == 0) { + myScrollPane.setVisible(false); + myContentPane.add(new JLabel(IdeBundle.message("goto.custom.region.message.unavailable")), BorderLayout.NORTH); + setOKActionEnabled(false); + } + else { + myRegionsList.setModel(new MyListModel(orderByPosition(descriptors))); + myRegionsList.setSelectedIndex(0); + } + setTitle(IdeBundle.message("goto.custom.region.command")); + } + + @Override + public JComponent getPreferredFocusedComponent() { + if (!myRegionsList.isEmpty()) { + return myRegionsList; + } + return super.getPreferredFocusedComponent(); + } + + @Override + protected JComponent createCenterPanel() { + return myContentPane; + } + + private Collection getCustomFoldingDescriptors() { + Set foldingDescriptors = new HashSet(); + final Document document = myEditor.getDocument(); + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); + PsiFile file = documentManager != null ? documentManager.getPsiFile(document) : null; + if (file != null) { + final FileViewProvider viewProvider = file.getViewProvider(); + for (final Language language : viewProvider.getLanguages()) { + final PsiFile psi = viewProvider.getPsi(language); + final FoldingBuilder foldingBuilder = LanguageFolding.INSTANCE.forLanguage(language); + if (psi != null) { + for (FoldingDescriptor descriptor : LanguageFolding.buildFoldingDescriptors(foldingBuilder, psi, document, false)) { + CustomFoldingBuilder customFoldingBuilder = getCustomFoldingBuilder(foldingBuilder, descriptor); + if (customFoldingBuilder != null) { + if (customFoldingBuilder.isCustomRegionStart(descriptor.getElement())) { + foldingDescriptors.add(descriptor); + } + } + } + } + } + } + return foldingDescriptors; + } + + private static Collection orderByPosition(Collection descriptors) { + List sorted = new ArrayList(descriptors.size()); + sorted.addAll(descriptors); + Collections.sort(sorted, new Comparator() { + @Override + public int compare(FoldingDescriptor descriptor1, FoldingDescriptor descriptor2) { + int pos1 = descriptor1.getElement().getTextRange().getStartOffset(); + int pos2 = descriptor2.getElement().getTextRange().getStartOffset(); + return pos1 - pos2; + } + }); + return sorted; + } + + private void createUIComponents() { + myRegionsList = new JBList(); + myRegionsList.setPreferredSize(new Dimension(200, 100)); + } + + @Nullable + private static CustomFoldingBuilder getCustomFoldingBuilder(FoldingBuilder builder, FoldingDescriptor descriptor) { + if (builder instanceof CustomFoldingBuilder) return (CustomFoldingBuilder)builder; + FoldingBuilder originalBuilder = descriptor.getElement().getUserData(CompositeFoldingBuilder.FOLDING_BUILDER); + if (originalBuilder instanceof CustomFoldingBuilder) return (CustomFoldingBuilder)originalBuilder; + return null; + } + + + private static class MyListModel extends DefaultListModel { + private MyListModel(Collection descriptors) { + for (FoldingDescriptor descriptor : descriptors) { + super.addElement(new MyFoldingDescriptorWrapper(descriptor)); + } + } + } + + private static class MyFoldingDescriptorWrapper { + private final @NotNull FoldingDescriptor myDescriptor; + + private MyFoldingDescriptorWrapper(@NotNull FoldingDescriptor descriptor) { + myDescriptor = descriptor; + } + + @NotNull + public FoldingDescriptor getDescriptor() { + return myDescriptor; + } + + @Nullable + @Override + public String toString() { + return myDescriptor.getPlaceholderText(); + } + } + + @Nullable + public PsiElement getNavigationElement() { + Object selection = myRegionsList.getSelectedValue(); + if (selection instanceof MyFoldingDescriptorWrapper) { + return ((MyFoldingDescriptorWrapper)selection).getDescriptor().getElement().getPsi(); + } + return null; + } +} diff --git a/platform/platform-resources-en/src/messages/IdeBundle.properties b/platform/platform-resources-en/src/messages/IdeBundle.properties index 1801a40ee8e3..3bbfc016da00 100644 --- a/platform/platform-resources-en/src/messages/IdeBundle.properties +++ b/platform/platform-resources-en/src/messages/IdeBundle.properties @@ -1090,3 +1090,6 @@ whatsnew.action.custom.text=What''s _New in {0} whatsnew.action.custom.description=Find out about the new features in this version of {0} diff.dialog.title=Diff Between ''{0}'' and ''{1}'' +goto.custom.region.command=Go to Custom Region +goto.custom.region.message.dumb.mode=Go to Custom Region action is not available until indices are built. +goto.custom.region.message.unavailable=There are no custom folding regions in the current file. \ No newline at end of file diff --git a/platform/platform-resources/src/idea/Keymap_Default.xml b/platform/platform-resources/src/idea/Keymap_Default.xml index 801ea6a0fbfb..e73cf4b5b320 100644 --- a/platform/platform-resources/src/idea/Keymap_Default.xml +++ b/platform/platform-resources/src/idea/Keymap_Default.xml @@ -406,6 +406,9 @@ + + + diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index 7422ec6184f8..19469e244507 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -178,6 +178,7 @@ +