Custom folding region navigation (IDEA-81770)

This commit is contained in:
Rustam Vishnyakov
2012-06-28 11:24:28 +04:00
parent fc92beb383
commit ef4cb629f9
7 changed files with 290 additions and 2 deletions
@@ -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);
@@ -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();
}
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.lang.customFolding.GotoCustomRegionDialog">
<grid id="27dc6" binding="myContentPane" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<scrollpane id="7557d" class="com.intellij.ui.components.JBScrollPane" binding="myScrollPane">
<constraints border-constraint="Center"/>
<properties/>
<border type="none"/>
<children>
<component id="2f5d5" class="com.intellij.ui.components.JBList" binding="myRegionsList" custom-create="true">
<constraints/>
<properties>
<selectionMode value="0"/>
</properties>
</component>
</children>
</scrollpane>
</children>
</grid>
</form>
@@ -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<FoldingDescriptor> 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<FoldingDescriptor> getCustomFoldingDescriptors() {
Set<FoldingDescriptor> foldingDescriptors = new HashSet<FoldingDescriptor>();
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<FoldingDescriptor> orderByPosition(Collection<FoldingDescriptor> descriptors) {
List<FoldingDescriptor> sorted = new ArrayList<FoldingDescriptor>(descriptors.size());
sorted.addAll(descriptors);
Collections.sort(sorted, new Comparator<FoldingDescriptor>() {
@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<FoldingDescriptor> 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;
}
}
@@ -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.
@@ -406,6 +406,9 @@
<action id="GotoLine">
<keyboard-shortcut first-keystroke="control G"/>
</action>
<action id="GotoCustomRegion">
<keyboard-shortcut first-keystroke="control shift G"/>
</action>
<action id="FindInPath">
<keyboard-shortcut first-keystroke="control shift F"/>
</action>
@@ -178,6 +178,7 @@
<action id="GotoClass" class="com.intellij.ide.actions.GotoClassAction"/>
<action id="GotoFile" class="com.intellij.ide.actions.GotoFileAction"/>
<action id="GotoSymbol" class="com.intellij.ide.actions.GotoSymbolAction"/>
<action id="GotoCustomRegion" class="com.intellij.lang.customFolding.GotoCustomRegionAction"/>
<add-to-group group-id="GoToMenu" anchor="first"/>
</group>