diff options
Diffstat (limited to 'platform/lang-impl/src')
123 files changed, 4630 insertions, 2414 deletions
diff --git a/platform/lang-impl/src/com/intellij/application/options/CodeCompletionPanel.java b/platform/lang-impl/src/com/intellij/application/options/CodeCompletionPanel.java index 8e8eba54cbf9..6c65209d1122 100644 --- a/platform/lang-impl/src/com/intellij/application/options/CodeCompletionPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/CodeCompletionPanel.java @@ -28,8 +28,10 @@ import com.intellij.openapi.application.ApplicationBundle; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.util.text.StringUtilRt; import com.intellij.ui.components.JBCheckBox; import org.intellij.lang.annotations.MagicConstant; +import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.event.ActionEvent; @@ -56,7 +58,7 @@ public class CodeCompletionPanel { private static final String CASE_SENSITIVE_FIRST_LETTER = ApplicationBundle.message("combobox.autocomplete.case.sensitive.first.letter"); private static final String[] CASE_VARIANTS = {CASE_SENSITIVE_ALL, CASE_SENSITIVE_NONE, CASE_SENSITIVE_FIRST_LETTER}; - public CodeCompletionPanel(){ + public CodeCompletionPanel() { //noinspection unchecked myCaseSensitiveCombo.setModel(new DefaultComboBoxModel(CASE_VARIANTS)); @@ -73,7 +75,7 @@ public class CodeCompletionPanel { myCbAutocompletion.addActionListener( new ActionListener() { @Override - public void actionPerformed(ActionEvent event) { + public void actionPerformed(@NotNull ActionEvent event) { boolean selected = myCbAutocompletion.isSelected(); myCbSelectByChars.setEnabled(selected); } @@ -83,7 +85,7 @@ public class CodeCompletionPanel { myCbAutopopupJavaDoc.addActionListener( new ActionListener() { @Override - public void actionPerformed(ActionEvent event) { + public void actionPerformed(@NotNull ActionEvent event) { myAutopopupJavaDocField.setEnabled(myCbAutopopupJavaDoc.isSelected()); } } @@ -92,7 +94,7 @@ public class CodeCompletionPanel { myCbParameterInfoPopup.addActionListener( new ActionListener() { @Override - public void actionPerformed(ActionEvent event) { + public void actionPerformed(@NotNull ActionEvent event) { myParameterInfoDelayField.setEnabled(myCbParameterInfoPopup.isSelected()); } } @@ -205,16 +207,8 @@ public class CodeCompletionPanel { } private static int getIntegerValue(String s, int defaultValue) { - int value = defaultValue; - try { - value = Integer.parseInt(s); - if(value < 0) { - return defaultValue; - } - } - catch (NumberFormatException ignored) { - } - return value; + int value = StringUtilRt.parseInt(s, defaultValue); + return value < 0 ? defaultValue : value; } @MagicConstant(intValues = {CodeInsightSettings.ALL, CodeInsightSettings.NONE, CodeInsightSettings.FIRST_LETTER}) diff --git a/platform/lang-impl/src/com/intellij/application/options/CodeStyleAbstractPanel.java b/platform/lang-impl/src/com/intellij/application/options/CodeStyleAbstractPanel.java index f7c1fd4c7e2d..2b142a4bffb2 100644 --- a/platform/lang-impl/src/com/intellij/application/options/CodeStyleAbstractPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/CodeStyleAbstractPanel.java @@ -201,7 +201,7 @@ public abstract class CodeStyleAbstractPanel implements Disposable { private int getAdjustedRightMargin() { int result = getRightMargin(); - return result > 0 ? result : CodeStyleFacade.getInstance(ProjectUtil.guessCurrentProject(getPanel())).getRightMargin(); + return result > 0 ? result : CodeStyleFacade.getInstance(ProjectUtil.guessCurrentProject(getPanel())).getRightMargin(getDefaultLanguage()); } protected abstract int getRightMargin(); @@ -226,7 +226,7 @@ public abstract class CodeStyleAbstractPanel implements Disposable { catch (ConfigurationException ignore) { } CodeStyleSettings clone = mySettings.clone(); - clone.RIGHT_MARGIN = getAdjustedRightMargin(); + clone.setRightMargin(getDefaultLanguage(), getAdjustedRightMargin()); CodeStyleSettingsManager.getInstance(project).setTemporarySettings(clone); PsiFile formatted; try { @@ -267,7 +267,7 @@ public abstract class CodeStyleAbstractPanel implements Disposable { Document document = documentManager.getDocument(psiFile); if (document != null) { CodeStyleSettings clone = mySettings.clone(); - clone.RIGHT_MARGIN = getAdjustedRightMargin(); + clone.setRightMargin(getDefaultLanguage(), getAdjustedRightMargin()); CodeStyleSettingsManager.getInstance(project).setTemporarySettings(clone); try { CodeStyleManager.getInstance(project).reformat(psiFile); diff --git a/platform/lang-impl/src/com/intellij/application/options/CodeStyleSettingsUtilImpl.java b/platform/lang-impl/src/com/intellij/application/options/CodeStyleSettingsUtilImpl.java index 640fcac38652..418cc3ccef6d 100644 --- a/platform/lang-impl/src/com/intellij/application/options/CodeStyleSettingsUtilImpl.java +++ b/platform/lang-impl/src/com/intellij/application/options/CodeStyleSettingsUtilImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -23,7 +23,7 @@ import com.intellij.psi.codeStyle.CodeStyleSettingsManager; public class CodeStyleSettingsUtilImpl extends CodeStyleSettingsUtil { /** - * Shows code style settings sutable for the project passed. I.e. it shows project code style page if one + * Shows code style settings suitable for the project passed. I.e. it shows project code style page if one * is configured to use own code style scheme or global one in other case. * @param project * @return Returns true if settings were modified during editing session. diff --git a/platform/lang-impl/src/com/intellij/application/options/ModuleAwareProjectConfigurable.java b/platform/lang-impl/src/com/intellij/application/options/ModuleAwareProjectConfigurable.java index f7d8ab8e7df1..b005b5e158fd 100644 --- a/platform/lang-impl/src/com/intellij/application/options/ModuleAwareProjectConfigurable.java +++ b/platform/lang-impl/src/com/intellij/application/options/ModuleAwareProjectConfigurable.java @@ -35,7 +35,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.border.EmptyBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; @@ -116,8 +115,7 @@ public abstract class ModuleAwareProjectConfigurable<T extends UnnamedConfigurab for (Module module : modules) { final T configurable = createModuleConfigurable(module); myModuleConfigurables.put(module, configurable); - final JComponent component = new JBScrollPane(configurable.createComponent()); - component.setBorder(new EmptyBorder(0, 0, 0, 0)); + final JComponent component = configurable.createComponent(); cardPanel.add(component, module.getName()); } moduleList.addListSelectionListener(new ListSelectionListener() { diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/OptionTableWithPreviewPanel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/OptionTableWithPreviewPanel.java index 099830e15902..f978d5f6dcdb 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/OptionTableWithPreviewPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/OptionTableWithPreviewPanel.java @@ -340,6 +340,16 @@ public abstract class OptionTableWithPreviewPanel extends MultilanguageCodeStyle addOption(fieldName, title, null, options, values); } + protected void addOption(@NotNull String fieldName, + @NotNull String title, + @Nullable String groupName, + int minValue, + int maxValue, + int defaultValue, + String defaultValueText) { + myOptions.add(new IntOption(null, fieldName, title, groupName, null, null, minValue, maxValue, defaultValue, defaultValueText)); + } + protected void addOption(@NotNull String fieldName, @NotNull String title, @Nullable String groupName) { myOptions.add(new BooleanOption(null, fieldName, title, groupName, null, null)); } @@ -469,6 +479,78 @@ public abstract class OptionTableWithPreviewPanel extends MultilanguageCodeStyle } } + private class IntOption extends Option { + + private final int myMinValue; + private final int myMaxValue; + private final int myDefaultValue; + @Nullable private String myDefaultValueText; + + public IntOption(Class<? extends CustomCodeStyleSettings> clazz, + @NotNull String fieldName, + @NotNull String title, + @Nullable String groupName, + @Nullable OptionAnchor anchor, + @Nullable String anchorFiledName, + int minValue, + int maxValue, + int defaultValue, + @Nullable String defaultValueText) { + super(clazz, fieldName, title, groupName, anchor, anchorFiledName); + myMinValue = minValue; + myMaxValue = maxValue; + myDefaultValue = defaultValue; + myDefaultValueText = defaultValueText; + } + + @Override + public Object getValue(CodeStyleSettings settings) { + try { + int value = field.getInt(getSettings(settings)); + return value == myDefaultValue && myDefaultValueText != null ? myDefaultValueText : value; + } + catch (IllegalAccessException e) { + return null; + } + } + + @Override + public void setValue(Object value, CodeStyleSettings settings) { + //noinspection EmptyCatchBlock + try { + if (myDefaultValueText != null && !myDefaultValueText.equals(value)) { + field.setInt(getSettings(settings), ((Integer)value).intValue()); + } + else { + field.setInt(getSettings(settings), -1); + } + } + catch (IllegalAccessException e) { + } + } + + public int getMinValue() { + return myMinValue; + } + + public int getMaxValue() { + return myMaxValue; + } + + public int getDefaultValue() { + return myDefaultValue; + } + + public boolean isDefaultText(Object value) { + return myDefaultValueText != null && myDefaultValueText.equals(value); + } + + @Nullable + public String getDefaultValueText() { + return myDefaultValueText; + } + } + @SuppressWarnings({"HardCodedStringLiteral"}) public final ColumnInfo TITLE = new ColumnInfo("TITLE") { @Override @@ -604,6 +686,7 @@ public abstract class OptionTableWithPreviewPanel extends MultilanguageCodeStyle private final JLabel myComboBox = new JLabel(); private final JCheckBox myCheckBox = new JCheckBox(); private final JPanel myEmptyLabel = new JPanel(); + private final JLabel myIntLabel = new JLabel(); @Override public Component getTableCellRendererComponent(JTable table, @@ -636,6 +719,10 @@ public abstract class OptionTableWithPreviewPanel extends MultilanguageCodeStyle myComboBox.setEnabled(isEnabled); return myComboBox; } + else if (value instanceof Integer) { + myIntLabel.setText(value.toString()); + return myIntLabel; + } myCheckBox.putClientProperty("JComponent.sizeVariant", "small"); myComboBox.putClientProperty("JComponent.sizeVariant", "small"); @@ -645,12 +732,55 @@ public abstract class OptionTableWithPreviewPanel extends MultilanguageCodeStyle } } + private static class MyIntOptionEditor extends JTextField { + private int myMinValue; + private int myMaxValue; + private int myDefaultValue; + private String myDefaultValueText; + + private MyIntOptionEditor() { + super(); + } + + public Object getPresentableValue() { + int value = validateAndGetIntOption(); + return value == myDefaultValue && myDefaultValueText != null ? myDefaultValueText : value; + } + + private int validateAndGetIntOption() { + try { + int value = Integer.parseInt(getText()); + return value >= myMinValue && value <= myMaxValue ? value : myDefaultValue; + } + catch (NumberFormatException nfe) { + return myDefaultValue; + } + } + + public void setMinValue(int minValue) { + myMinValue = minValue; + } + + public void setMaxValue(int maxValue) { + myMaxValue = maxValue; + } + + public void setDefaultValue(int defaultValue) { + myDefaultValue = defaultValue; + } + + public void setDefaultValueText(String defaultValueText) { + myDefaultValueText = defaultValueText; + } + } + /** * @author Konstantin Bulenkov */ private class MyValueEditor extends AbstractTableCellEditor { private final JCheckBox myBooleanEditor = new JCheckBox(); private JBComboBoxTableCellEditorComponent myOptionsEditor = new JBComboBoxTableCellEditorComponent(); + private MyIntOptionEditor myIntOptionsEditor = new MyIntOptionEditor(); private Component myCurrentEditor = null; private MyTreeNode myCurrentNode = null; @@ -684,6 +814,9 @@ public abstract class OptionTableWithPreviewPanel extends MultilanguageCodeStyle else if (myCurrentEditor == myBooleanEditor) { return myBooleanEditor.isSelected() ? Boolean.TRUE : Boolean.FALSE; } + else if (myCurrentEditor == myIntOptionsEditor) { + return myIntOptionsEditor.getPresentableValue(); + } return null; } @@ -702,6 +835,15 @@ public abstract class OptionTableWithPreviewPanel extends MultilanguageCodeStyle myBooleanEditor.setSelected(node.getValue() == Boolean.TRUE); myBooleanEditor.setEnabled(node.isEnabled()); } + else if (node.getKey() instanceof IntOption) { + IntOption intOption = (IntOption)node.getKey(); + myCurrentEditor = myIntOptionsEditor; + myIntOptionsEditor.setText(intOption.isDefaultText(node.getValue()) ? "" : node.getValue().toString()); + myIntOptionsEditor.setMinValue(intOption.getMinValue()); + myIntOptionsEditor.setMaxValue(intOption.getMaxValue()); + myIntOptionsEditor.setDefaultValue(intOption.getDefaultValue()); + myIntOptionsEditor.setDefaultValueText(intOption.getDefaultValueText()); + } else { myCurrentEditor = myOptionsEditor; myOptionsEditor.setCell(table, row, column); diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java index 1b270874101e..c192d0e2893a 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java @@ -32,6 +32,8 @@ public class WrappingAndBracesPanel extends OptionTableWithPreviewPanel { @Override protected void initTables() { + addOption("RIGHT_MARGIN", ApplicationBundle.message("editbox.right.margin.columns"), null, 0, 999, -1, ApplicationBundle.message("settings.code.style.default.general")); + addOption("KEEP_LINE_BREAKS", ApplicationBundle.message("wrapping.keep.line.breaks"), WRAPPING_KEEP); addOption("KEEP_FIRST_COLUMN_COMMENT", ApplicationBundle.message("wrapping.keep.comment.at.first.column"), WRAPPING_KEEP); addOption("KEEP_CONTROL_STATEMENT_IN_ONE_LINE", ApplicationBundle.message("checkbox.keep.when.reformatting.control.statement.in.one.line"), WRAPPING_KEEP); diff --git a/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.java b/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.java index 2cabcabf74ad..a5551086daaf 100644 --- a/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.java +++ b/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.java @@ -161,7 +161,7 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable<UnnamedCo myCbSurroundSelectionOnTyping.setSelected(codeInsightSettings.SURROUND_SELECTION_ON_QUOTE_TYPED); - myCbIndentingBackspace.setSelected(codeInsightSettings.INDENTING_BACKSPACE); + myCbIndentingBackspace.setSelected(codeInsightSettings.SMART_BACKSPACE == CodeInsightSettings.AUTOINDENT); super.reset(); } @@ -182,7 +182,7 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable<UnnamedCo codeInsightSettings.SURROUND_SELECTION_ON_QUOTE_TYPED = myCbSurroundSelectionOnTyping.isSelected(); editorSettings.setCamelWords(myCbCamelWords.isSelected()); codeInsightSettings.REFORMAT_ON_PASTE = getReformatPastedBlockValue(); - codeInsightSettings.INDENTING_BACKSPACE = myCbIndentingBackspace.isSelected(); + codeInsightSettings.SMART_BACKSPACE = myCbIndentingBackspace.isSelected() ? CodeInsightSettings.AUTOINDENT : CodeInsightSettings.OFF; super.apply(); } @@ -208,7 +208,7 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable<UnnamedCo isModified |= isModified(myCbSurroundSelectionOnTyping, codeInsightSettings.SURROUND_SELECTION_ON_QUOTE_TYPED); - isModified |= isModified(myCbIndentingBackspace, codeInsightSettings.INDENTING_BACKSPACE); + isModified |= isModified(myCbIndentingBackspace, codeInsightSettings.SMART_BACKSPACE == CodeInsightSettings.AUTOINDENT); return isModified; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java b/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java index 013b0808faca..942cbfa17f1e 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java @@ -100,7 +100,10 @@ public class CodeInsightSettings implements PersistentStateComponent<Element>, C public boolean SHOW_FULL_SIGNATURES_IN_PARAMETER_INFO = false; - public boolean INDENTING_BACKSPACE = true; + @MagicConstant(intValues = {OFF, AUTOINDENT}) + public int SMART_BACKSPACE = AUTOINDENT; + public static final int OFF = 0; + public static final int AUTOINDENT = 1; public boolean SMART_INDENT_ON_ENTER = true; public boolean INSERT_BRACE_ON_ENTER = true; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/MultiCaretCodeInsightAction.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/MultiCaretCodeInsightAction.java new file mode 100644 index 000000000000..568e17b58bd6 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/MultiCaretCodeInsightAction.java @@ -0,0 +1,153 @@ +/* + * Copyright 2000-2014 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.codeInsight.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.CaretAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.editor.actionSystem.DocCommandGroupId; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.psi.util.PsiUtilBase; +import org.jetbrains.annotations.NotNull; + +/** + * Base class for PSI-aware editor actions that need to support multiple carets. + * Recognizes multi-root PSI and injected fragments, so different carets might be processed in context of different + * {@link com.intellij.openapi.editor.Editor} and {@link com.intellij.psi.PsiFile} instances. + * <p> + * Implementations should implement {@link #getHandler()} method, and might override {@link + * #isValidFor(com.intellij.openapi.project.Project, com.intellij.openapi.editor.Editor, com.intellij.openapi.editor.Caret, com.intellij.psi.PsiFile)} method. + * + * @see com.intellij.codeInsight.actions.MultiCaretCodeInsightActionHandler + */ +public abstract class MultiCaretCodeInsightAction extends AnAction { + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = e.getProject(); + if (project == null) { + return; + } + final Editor hostEditor = CommonDataKeys.EDITOR.getData(e.getDataContext()); + if (hostEditor == null) { + return; + } + + actionPerformedImpl(project, hostEditor); + } + + public void actionPerformedImpl(final Project project, final Editor hostEditor) { + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + MultiCaretCodeInsightActionHandler handler = getHandler(); + try { + iterateOverCarets(project, hostEditor, handler); + } + finally { + handler.postInvoke(); + } + } + }); + } + }, getCommandName(), DocCommandGroupId.noneGroupId(hostEditor.getDocument())); + + hostEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + } + + @Override + public void update(AnActionEvent e) { + final Presentation presentation = e.getPresentation(); + + Project project = e.getProject(); + if (project == null) { + presentation.setEnabled(false); + return; + } + + Editor hostEditor = CommonDataKeys.EDITOR.getData(e.getDataContext()); + if (hostEditor == null) { + presentation.setEnabled(false); + return; + } + + final Ref<Boolean> enabled = new Ref<Boolean>(Boolean.FALSE); + iterateOverCarets(project, hostEditor, new MultiCaretCodeInsightActionHandler() { + @Override + public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, @NotNull PsiFile file) { + if (isValidFor(project, editor, caret, file)) { + enabled.set(Boolean.TRUE); + } + } + }); + presentation.setEnabled(enabled.get()); + } + + private static void iterateOverCarets(@NotNull final Project project, + @NotNull final Editor hostEditor, + @NotNull final MultiCaretCodeInsightActionHandler handler) { + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + final PsiFile psiFile = documentManager.getCachedPsiFile(hostEditor.getDocument()); + documentManager.commitAllDocuments(); + + hostEditor.getCaretModel().runForEachCaret(new CaretAction() { + @Override + public void perform(Caret caret) { + Editor editor = hostEditor; + if (psiFile != null) { + Caret injectedCaret = InjectedLanguageUtil.getCaretForInjectedLanguageNoCommit(caret, psiFile); + if (injectedCaret != null) { + caret = injectedCaret; + editor = caret.getEditor(); + } + } + final PsiFile file = PsiUtilBase.getPsiFileInEditor(caret, project); + if (file != null) { + handler.invoke(project, editor, caret, file); + } + } + }, true); + } + + /** + * During action status update this method is invoked for each caret in editor. If at least for a single caret it returns + * <code>true</code>, action is considered enabled. + */ + protected boolean isValidFor(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, @NotNull PsiFile file) { + return true; + } + + @NotNull + protected abstract MultiCaretCodeInsightActionHandler getHandler(); + + protected String getCommandName() { + String text = getTemplatePresentation().getText(); + return text == null ? "" : text; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/MultiCaretCodeInsightActionHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/MultiCaretCodeInsightActionHandler.java new file mode 100644 index 000000000000..bfe7e325b8eb --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/MultiCaretCodeInsightActionHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2014 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.codeInsight.actions; + +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +/** + * Delegate class that performs actual work for {@link com.intellij.codeInsight.actions.MultiCaretCodeInsightAction} + */ +public abstract class MultiCaretCodeInsightActionHandler { + /** + * Invoked for each caret in editor (in bottom-to-top order). <code>project</code> value is the same for all carets, <code>editor</code> + * and <code>file</code> values can be different in presence of multi-root PSI and injected fragments. For injected fragments + * caret instance will belong to corresponding injected editor. + */ + public abstract void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, @NotNull PsiFile file); + + /** + * Invoked after processing all carets. + */ + public void postInvoke() {} +}
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java index 50942cb06c6b..4966a9466a26 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java @@ -174,7 +174,8 @@ public class ReformatCodeAction extends AnAction implements DumbAware { } final TextRange range; - if (!processWholeFile && editor != null && editor.getSelectionModel().hasSelection()){ + final boolean processSelectedText = !processWholeFile && hasSelection; + if (processSelectedText) { range = TextRange.create(editor.getSelectionModel().getSelectionStart(), editor.getSelectionModel().getSelectionEnd()); } else{ @@ -185,7 +186,7 @@ public class ReformatCodeAction extends AnAction implements DumbAware { new OptimizeImportsProcessor(new ReformatCodeProcessor(project, file, null, processChangedTextOnly)).run(); } else { - new ReformatCodeProcessor(project, file, range, processChangedTextOnly).run(); + new ReformatCodeProcessor(project, file, range, !processSelectedText && processChangedTextOnly).run(); } if (rearrangeEntries && file != null && editor != null) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java index e039cec3a424..e0a3be5f423d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java @@ -772,11 +772,8 @@ public class CodeCompletionHandlerBase { if (context.getCompletionChar() == Lookup.COMPLETE_STATEMENT_SELECT_CHAR) { final Language language = PsiUtilBase.getLanguageInEditor(editor, project); if (language != null) { - final List<SmartEnterProcessor> processors = SmartEnterProcessors.INSTANCE.forKey(language); - if (processors.size() > 0) { - for (SmartEnterProcessor processor : processors) { - processor.process(project, editor, indicator.getParameters().getOriginalFile()); - } + for (SmartEnterProcessor processor : SmartEnterProcessors.INSTANCE.forKey(language)) { + if (processor.processAfterCompletion(editor, indicator.getParameters().getOriginalFile())) break; } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java index 0143e55a4e98..e25f050c8d7b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java @@ -17,6 +17,7 @@ package com.intellij.codeInsight.completion; import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.TargetElementUtilBase; import com.intellij.codeInsight.completion.impl.CompletionServiceImpl; import com.intellij.codeInsight.completion.impl.CompletionSorterImpl; import com.intellij.codeInsight.editorActions.CompletionAutoPopupHandler; @@ -216,7 +217,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement if (!initContext.getOffsetMap().wasModified(CompletionInitializationContext.IDENTIFIER_END_OFFSET)) { try { final int selectionEndOffset = initContext.getSelectionEndOffset(); - final PsiReference reference = initContext.getFile().findReferenceAt(selectionEndOffset); + final PsiReference reference = TargetElementUtilBase.findReference(myEditor, selectionEndOffset); if (reference != null) { initContext.setReplacementOffset(findReplacementOffset(selectionEndOffset, reference)); } @@ -252,7 +253,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement } } - return reference.getElement().getTextRange().getStartOffset() + reference.getRangeInElement().getEndOffset(); + return selectionEndOffset; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/FilePathCompletionContributor.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/FilePathCompletionContributor.java index f23a7a6f6cca..4a34e9460718 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/FilePathCompletionContributor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/FilePathCompletionContributor.java @@ -48,6 +48,7 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.ProjectScope; import com.intellij.util.ArrayUtil; import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -71,9 +72,7 @@ public class FilePathCompletionContributor extends CompletionContributor { final PsiReference psiReference = parameters.getPosition().getContainingFile().findReferenceAt(parameters.getOffset()); if (getReference(psiReference) != null && parameters.getInvocationCount() == 1) { final String shortcut = getActionShortcut(IdeActions.ACTION_CODE_COMPLETION); - if (shortcut != null) { - result.addLookupAdvertisement(CodeInsightBundle.message("class.completion.file.path", shortcut)); - } + result.addLookupAdvertisement(CodeInsightBundle.message("class.completion.file.path", shortcut)); } } }); @@ -134,24 +133,30 @@ public class FilePathCompletionContributor extends CompletionContributor { final PsiFile[] files = FilenameIndex.getFilesByName(project, name, scope); - if (files.length > 0) { - for (final PsiFile file : files) { + if (files.length <= 0) { + continue; + } + for (final PsiFile file : files) { + ProgressManager.checkCanceled(); + + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null || !virtualFile.isValid() || Comparing.equal(virtualFile, contextFile)) { + continue; + } + List<FileReferenceHelper> helperList = new ArrayList<FileReferenceHelper>(); + for (FileReferenceHelper contextHelper : helpers) { ProgressManager.checkCanceled(); - final VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile != null && virtualFile.isValid() && !Comparing.equal(virtualFile, contextFile)) { - for (FileReferenceHelper contextHelper : helpers) { - ProgressManager.checkCanceled(); - - if (contextHelper.isMine(project, virtualFile)) { - if (pathPrefixParts == null || - fileMatchesPathPrefix(contextHelper.getPsiFileSystemItem(project, virtualFile), pathPrefixParts)) { - __result.addElement(new FilePathLookupItem(file, contextHelper)); - } - } + if (contextHelper.isMine(project, virtualFile)) { + if (pathPrefixParts == null || + fileMatchesPathPrefix(contextHelper.getPsiFileSystemItem(project, virtualFile), pathPrefixParts)) { + helperList.add(contextHelper); } } } + if (!helperList.isEmpty()) { + __result.addElement(new FilePathLookupItem(file, helperList)); + } } } } @@ -159,9 +164,7 @@ public class FilePathCompletionContributor extends CompletionContributor { if (set.getSuitableFileTypes().length > 0 && parameters.getInvocationCount() == 1) { final String shortcut = getActionShortcut(IdeActions.ACTION_CODE_COMPLETION); - if (shortcut != null) { - result.addLookupAdvertisement(CodeInsightBundle.message("class.completion.file.path.all.variants", shortcut)); - } + result.addLookupAdvertisement(CodeInsightBundle.message("class.completion.file.path.all.variants", shortcut)); } if (fileReferencePair.getSecond()) result.stopHere(); @@ -203,7 +206,7 @@ public class FilePathCompletionContributor extends CompletionContributor { final String path = StringUtil.join(contextParts, "/"); int nextIndex = 0; - for (final String s : pathPrefix) { + for (@NonNls final String s : pathPrefix) { if ((nextIndex = path.indexOf(s.toLowerCase(), nextIndex)) == -1) return false; } @@ -252,19 +255,19 @@ public class FilePathCompletionContributor extends CompletionContributor { return null; } - public class FilePathLookupItem extends LookupElement { + public static class FilePathLookupItem extends LookupElement { private final String myName; private final String myPath; private final String myInfo; private final Icon myIcon; private final PsiFile myFile; - private final FileReferenceHelper myReferenceHelper; + private final List<FileReferenceHelper> myHelpers; - public FilePathLookupItem(@NotNull final PsiFile file, @NotNull final FileReferenceHelper referenceHelper) { + public FilePathLookupItem(@NotNull final PsiFile file, @NotNull final List<FileReferenceHelper> helpers) { myName = file.getName(); myPath = file.getVirtualFile().getPath(); - myReferenceHelper = referenceHelper; + myHelpers = helpers; myInfo = FileInfoManager.getFileAdditionalInfo(file); myIcon = file.getFileType().getIcon(); @@ -306,10 +309,7 @@ public class FilePathCompletionContributor extends CompletionContributor { @Override public void renderElement(LookupElementPresentation presentation) { - final VirtualFile virtualFile = myFile.getVirtualFile(); - LOG.assertTrue(virtualFile != null); - final PsiFileSystemItem root = myReferenceHelper.findRoot(myFile.getProject(), virtualFile); - final String relativePath = PsiFileSystemItemUtil.getRelativePath(root, myReferenceHelper.getPsiFileSystemItem(myFile.getProject(), virtualFile)); + final String relativePath = getRelativePath(); final StringBuilder sb = new StringBuilder(); if (myInfo != null) { @@ -340,6 +340,18 @@ public class FilePathCompletionContributor extends CompletionContributor { presentation.setIcon(myIcon); } + @Nullable + private String getRelativePath() { + final VirtualFile virtualFile = myFile.getVirtualFile(); + LOG.assertTrue(virtualFile != null); + for (FileReferenceHelper helper : myHelpers) { + final PsiFileSystemItem root = helper.findRoot(myFile.getProject(), virtualFile); + String path = PsiFileSystemItemUtil.getRelativePath(root, helper.getPsiFileSystemItem(myFile.getProject(), virtualFile)); + if (path != null) return path; + } + return null; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java index 1984230809c5..8ceca4c94a31 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java @@ -25,7 +25,6 @@ import com.intellij.codeInsight.intention.IntentionManager; import com.intellij.codeInsight.intention.impl.IntentionHintComponent; import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler; import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings; -import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.codeInsight.template.impl.TemplateState; import com.intellij.codeInspection.actions.CleanupAllIntention; @@ -208,8 +207,6 @@ public class ShowIntentionsPass extends TextEditorHighlightingPass { } private void getIntentionActionsToShow() { - if (LookupManager.getInstance(myProject).getActiveLookup() != null) return; - getActionsToShow(myEditor, myFile, myIntentionsInfo, myPassIdToShowIntentionsFor); if (myFile instanceof IntentionFilterOwner) { final IntentionFilterOwner.IntentionActionsFilter actionsFilter = ((IntentionFilterOwner)myFile).getIntentionActionsFilter(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/analysis/encoding/EncodingReferenceInjector.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/analysis/encoding/EncodingReferenceInjector.java new file mode 100644 index 000000000000..6594a5a0866d --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/analysis/encoding/EncodingReferenceInjector.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2014 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.codeInsight.daemon.impl.analysis.encoding; + +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.injection.ReferenceInjector; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; + +/** + * @author peter + */ +public class EncodingReferenceInjector extends ReferenceInjector { + @NotNull + @Override + public PsiReference[] getReferences(@NotNull PsiElement element, @NotNull ProcessingContext context, @NotNull TextRange range) { + return new PsiReference[]{new EncodingReference(element, range.substring(element.getText()), range)}; + } + + @NotNull + @Override + public String getId() { + return "encoding-reference"; + } + + @NotNull + @Override + public String getDisplayName() { + return "Encoding Name"; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java index df55cbad9346..c27ce400049e 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java @@ -54,13 +54,17 @@ import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.psi.*; import com.intellij.psi.presentation.java.SymbolPresentationUtil; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.ListScrollingUtil; import com.intellij.ui.content.Content; import com.intellij.ui.popup.AbstractPopup; import com.intellij.ui.popup.NotLookupOrSearchCondition; import com.intellij.ui.popup.PopupPositionManager; import com.intellij.ui.popup.PopupUpdateProcessor; -import com.intellij.util.*; +import com.intellij.util.Alarm; +import com.intellij.util.BooleanFunction; +import com.intellij.util.Consumer; +import com.intellij.util.Processor; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -572,7 +576,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp final PsiReference ref = TargetElementUtilBase.findReference(editor, offset); if (ref != null) { element = assertSameProject(util.adjustReference(ref)); - if (element == null && ref instanceof PsiPolyVariantReference) { + if (ref instanceof PsiPolyVariantReference) { element = assertSameProject(ref.getElement()); } } @@ -595,16 +599,17 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp int offset = editor.getCaretModel().getOffset(); if (offset > 0 && offset == editor.getDocument().getTextLength()) offset--; - final PsiElement contextElement = file == null? null : file.findElementAt(offset); - final PsiReference ref = TargetElementUtilBase.findReference(editor, offset); + PsiReference ref = TargetElementUtilBase.findReference(editor, offset); + PsiElement contextElement = file == null? null : file.findElementAt(offset); + PsiElement targetElement = ref != null ? ref.getElement() : contextElement; + if (targetElement != null) { + PsiUtilCore.ensureValid(targetElement); + } - final DocumentationProvider documentationProvider = getProviderFromElement(file); + DocumentationProvider documentationProvider = getProviderFromElement(file); - return documentationProvider.getDocumentationElementForLookupItem( - PsiManager.getInstance(myProject), - item.getObject(), - ref != null ? ref.getElement():contextElement - ); + PsiManager psiManager = PsiManager.getInstance(myProject); + return documentationProvider.getDocumentationElementForLookupItem(psiManager, item.getObject(), targetElement); } } return null; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java index a68a1fdeee5a..2a46c08fc2dd 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java @@ -37,8 +37,6 @@ import java.util.ArrayList; import java.util.List; public class CopyHandler extends EditorActionHandler { - private static final Logger LOG = Logger.getInstance(CopyHandler.class); - private final EditorActionHandler myOriginalAction; public CopyHandler(final EditorActionHandler originalHandler) { @@ -93,7 +91,10 @@ public class CopyHandler extends EditorActionHandler { transferableDatas.addAll(processor.collectTransferableData(file, editor, startOffsets, endOffsets)); } - String rawText = TextBlockTransferable.convertLineSeparators(selectionModel.getSelectedText(true), "\n", transferableDatas); + String text = editor.getCaretModel().supportsMultipleCarets() + ? CopyPasteSupport.getSelectedTextForClipboard(editor, transferableDatas) + : selectionModel.getSelectedText(); + String rawText = TextBlockTransferable.convertLineSeparators(text, "\n", transferableDatas); String escapedText = null; for (CopyPastePreProcessor processor : Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) { escapedText = processor.preprocessOnCopy(file, startOffsets, endOffsets, rawText); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java index 634b8f5570a3..345cb80bae69 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java @@ -45,7 +45,7 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { @Override public boolean charDeleted(char c, PsiFile file, Editor editor) { - if (!CodeInsightSettings.getInstance().INDENTING_BACKSPACE || !StringUtil.isWhiteSpace(c)) { + if (CodeInsightSettings.getInstance().SMART_BACKSPACE != CodeInsightSettings.AUTOINDENT || !StringUtil.isWhiteSpace(c)) { return false; } LanguageCodeStyleSettingsProvider codeStyleSettingsProvider = LanguageCodeStyleSettingsProvider.forLanguage(file.getLanguage()); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferable.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferable.java deleted file mode 100644 index 075f7618eddd..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferable.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2000-2014 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.codeInsight.editorActions; - -import com.intellij.openapi.editor.RawText; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.text.StringUtil; - -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -class TextBlockTransferable implements Transferable { - private final Collection<TextBlockTransferableData> myExtraData; - private final RawText myRawText; - private final String myText; - private final DataFlavor[] myTransferDataFlavors; - - public TextBlockTransferable(String text, Collection<TextBlockTransferableData> extraData, RawText rawText) { - myText = text; - myExtraData = extraData; - myRawText = rawText; - - List<DataFlavor> dataFlavors = new ArrayList<DataFlavor>(); - Collections.addAll(dataFlavors, DataFlavor.stringFlavor, DataFlavor.plainTextFlavor); - final DataFlavor flavor = RawText.getDataFlavor(); - if (myRawText != null && flavor != null) { - dataFlavors.add(flavor); - } - for(TextBlockTransferableData data: extraData) { - final DataFlavor blockFlavor = data.getFlavor(); - if (blockFlavor != null) { - dataFlavors.add(blockFlavor); - } - } - myTransferDataFlavors = dataFlavors.toArray(new DataFlavor[dataFlavors.size()]); - } - - @Override - public DataFlavor[] getTransferDataFlavors() { - return myTransferDataFlavors; - } - - @Override - public boolean isDataFlavorSupported(DataFlavor flavor) { - DataFlavor[] flavors = getTransferDataFlavors(); - for (DataFlavor flavor1 : flavors) { - if (flavor.equals(flavor1)) { - return true; - } - } - return false; - } - - @Override - public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { - try { - for(TextBlockTransferableData data: myExtraData) { - if (Comparing.equal(data.getFlavor(), flavor)) { - return data; - } - } - if (myRawText != null && Comparing.equal(RawText.getDataFlavor(), flavor)) { - return myRawText; - } - else if (DataFlavor.stringFlavor.equals(flavor)) { - return myText; - } - else if (DataFlavor.plainTextFlavor.equals(flavor)) { - return new StringReader(myText); - } - } - catch(NoClassDefFoundError e) { - // ignore - } - throw new UnsupportedFlavorException(flavor); - } - - public static String convertLineSeparators(String text, - String newSeparator, - Collection<TextBlockTransferableData> transferableDatas) { - if (transferableDatas.size() > 0){ - int size = 0; - for(TextBlockTransferableData data: transferableDatas) { - size += data.getOffsetCount(); - } - - int[] offsets = new int[size]; - int index = 0; - for(TextBlockTransferableData data: transferableDatas) { - index = data.getOffsets(offsets, index); - } - - text = StringUtil.convertLineSeparators(text, newSeparator, offsets); - - index = 0; - for(TextBlockTransferableData data: transferableDatas) { - index = data.setOffsets(offsets, index); - } - - return text; - } - else{ - return StringUtil.convertLineSeparators(text, newSeparator); - } - } -}
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferableData.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferableData.java deleted file mode 100644 index c15a50e3019a..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferableData.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2000-2009 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.codeInsight.editorActions; - -import java.awt.datatransfer.DataFlavor; - -/** - * @author yole - */ -public interface TextBlockTransferableData { - DataFlavor getFlavor(); - - int getOffsetCount(); - int getOffsets(final int[] offsets, final int index); - int setOffsets(final int[] offsets, final int index); -} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/fillParagraph/ParagraphFillHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/fillParagraph/ParagraphFillHandler.java index 03a72378838c..3aeefefd55ec 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/fillParagraph/ParagraphFillHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/fillParagraph/ParagraphFillHandler.java @@ -54,7 +54,7 @@ public class ParagraphFillHandler { document.replaceString(textRange.getStartOffset(), textRange.getEndOffset(), replacementText); final CodeFormatterFacade codeFormatter = new CodeFormatterFacade( - CodeStyleSettingsManager.getSettings(element.getProject())); + CodeStyleSettingsManager.getSettings(element.getProject()), element.getLanguage()); codeFormatter.doWrapLongLinesIfNecessary(editor, element.getProject(), document, textRange.getStartOffset(), textRange.getStartOffset() + replacementText.length() + 1); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByBlockCommentHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByBlockCommentHandler.java index ca915119388b..3704425eb54d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByBlockCommentHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByBlockCommentHandler.java @@ -16,9 +16,9 @@ package com.intellij.codeInsight.generation; -import com.intellij.codeInsight.CodeInsightActionHandler; import com.intellij.codeInsight.CodeInsightUtilBase; import com.intellij.codeInsight.CommentUtil; +import com.intellij.codeInsight.actions.MultiCaretCodeInsightActionHandler; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.ide.highlighter.custom.CustomFileTypeLexer; import com.intellij.lang.Commenter; @@ -52,18 +52,20 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -public class CommentByBlockCommentHandler implements CodeInsightActionHandler { +public class CommentByBlockCommentHandler extends MultiCaretCodeInsightActionHandler { private Project myProject; private Editor myEditor; + private Caret myCaret; private @NotNull PsiFile myFile; private Document myDocument; private CommenterDataHolder mySelfManagedCommenterData; @Override - public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { + public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, @NotNull PsiFile file) { if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) return; myProject = project; myEditor = editor; + myCaret = caret; myFile = file; myDocument = editor.getDocument(); @@ -72,19 +74,17 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { return; } FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.comment.block"); - final Commenter commenter = findCommenter(myFile, myEditor); + final Commenter commenter = findCommenter(myFile, myEditor, caret); if (commenter == null) return; - final SelectionModel selectionModel = myEditor.getSelectionModel(); - final String prefix; final String suffix; if (commenter instanceof SelfManagingCommenter) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; mySelfManagedCommenterData = selfManagingCommenter.createBlockCommentingState( - selectionModel.getSelectionStart(), - selectionModel.getSelectionEnd(), + caret.getSelectionStart(), + caret.getSelectionEnd(), myDocument, myFile ); @@ -94,12 +94,12 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { } prefix = selfManagingCommenter.getBlockCommentPrefix( - selectionModel.getSelectionStart(), + caret.getSelectionStart(), myDocument, mySelfManagedCommenterData ); suffix = selfManagingCommenter.getBlockCommentSuffix( - selectionModel.getSelectionEnd(), + caret.getSelectionEnd(), myDocument, mySelfManagedCommenterData ); @@ -117,9 +117,9 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { final int commentEnd = commentedRange.getEndOffset(); int selectionStart = commentStart; int selectionEnd = commentEnd; - if (selectionModel.hasSelection()) { - selectionStart = selectionModel.getSelectionStart(); - selectionEnd = selectionModel.getSelectionEnd(); + if (myCaret.hasSelection()) { + selectionStart = myCaret.getSelectionStart(); + selectionEnd = myCaret.getSelectionEnd(); } if ((commentStart < selectionStart || commentStart >= selectionEnd) && (commentEnd <= selectionStart || commentEnd > selectionEnd)) { commentRange(selectionStart, selectionEnd, prefix, suffix, commenter); @@ -129,9 +129,9 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { } } else { - if (selectionModel.hasBlockSelection()) { - final LogicalPosition start = selectionModel.getBlockStart(); - final LogicalPosition end = selectionModel.getBlockEnd(); + if (myEditor.getSelectionModel().hasBlockSelection()) { + final LogicalPosition start = myEditor.getSelectionModel().getBlockStart(); + final LogicalPosition end = myEditor.getSelectionModel().getBlockEnd(); assert start != null; assert end != null; @@ -151,9 +151,9 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { EditorModificationUtil.insertStringAtCaret(editor, prefix, true, true); } } - else if (selectionModel.hasSelection()) { - int selectionStart = selectionModel.getSelectionStart(); - int selectionEnd = selectionModel.getSelectionEnd(); + else if (myCaret.hasSelection()) { + int selectionStart = myCaret.getSelectionStart(); + int selectionEnd = myCaret.getSelectionEnd(); if (commenter instanceof IndentedCommenter) { final Boolean value = ((IndentedCommenter)commenter).forceIndentedLineComment(); if (value != null && value == Boolean.TRUE) { @@ -165,7 +165,7 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { } else { EditorUtil.fillVirtualSpaceUntilCaret(editor); - int caretOffset = myEditor.getCaretModel().getOffset(); + int caretOffset = myCaret.getOffset(); if (commenter instanceof IndentedCommenter) { final Boolean value = ((IndentedCommenter)commenter).forceIndentedLineComment(); if (value != null && value == Boolean.TRUE) { @@ -177,7 +177,7 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { } } myDocument.insertString(caretOffset, prefix + suffix); - myEditor.getCaretModel().moveToOffset(caretOffset + prefix.length()); + myCaret.moveToOffset(caretOffset + prefix.length()); } } } @@ -188,12 +188,11 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { } private boolean testSelectionForNonComments() { - SelectionModel model = myEditor.getSelectionModel(); - if (!model.hasSelection()) { + if (!myCaret.hasSelection()) { return true; } TextRange range - = new TextRange(model.getSelectionStart(), model.getSelectionEnd() - 1); + = new TextRange(myCaret.getSelectionStart(), myCaret.getSelectionEnd() - 1); for (PsiElement element = myFile.findElementAt(range.getStartOffset()); element != null && range.intersects(element.getTextRange()); element = element.getNextSibling()) { if (element instanceof OuterLanguageElement) { @@ -247,7 +246,7 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { final FileType fileType = myFile.getFileType(); if (fileType instanceof CustomSyntaxTableFileType) { Lexer lexer = new CustomFileTypeLexer(((CustomSyntaxTableFileType)fileType).getSyntaxTable()); - final int caretOffset = myEditor.getCaretModel().getOffset(); + final int caretOffset = myCaret.getOffset(); int commentStart = CharArrayUtil.lastIndexOf(text, commenter.getBlockCommentPrefix(), caretOffset); if (commentStart == -1) return null; @@ -261,17 +260,16 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { final String prefix; final String suffix; - final SelectionModel selectionModel = myEditor.getSelectionModel(); if (commenter instanceof SelfManagingCommenter) { SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; prefix = selfManagingCommenter.getBlockCommentPrefix( - selectionModel.getSelectionStart(), + myCaret.getSelectionStart(), myDocument, mySelfManagedCommenterData ); suffix = selfManagingCommenter.getBlockCommentSuffix( - selectionModel.getSelectionEnd(), + myCaret.getSelectionEnd(), myDocument, mySelfManagedCommenterData ); @@ -286,8 +284,8 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { if (commenter instanceof SelfManagingCommenter) { commentedRange = ((SelfManagingCommenter)commenter).getBlockCommentRange( - selectionModel.getSelectionStart(), - selectionModel.getSelectionEnd(), + myCaret.getSelectionStart(), + myCaret.getSelectionEnd(), myDocument, mySelfManagedCommenterData ); @@ -316,11 +314,10 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { @Nullable private TextRange getSelectedComments(CharSequence text, String prefix, String suffix) { TextRange commentedRange = null; - final SelectionModel selectionModel = myEditor.getSelectionModel(); - if (selectionModel.hasSelection()) { - int selectionStart = selectionModel.getSelectionStart(); + if (myCaret.hasSelection()) { + int selectionStart = myCaret.getSelectionStart(); selectionStart = CharArrayUtil.shiftForward(text, selectionStart, " \t\n"); - int selectionEnd = selectionModel.getSelectionEnd() - 1; + int selectionEnd = myCaret.getSelectionEnd() - 1; selectionEnd = CharArrayUtil.shiftBackward(text, selectionEnd, " \t\n") + 1; if (selectionEnd - selectionStart >= prefix.length() + suffix.length() && CharArrayUtil.regionMatches(text, selectionStart, prefix) && @@ -332,13 +329,13 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { } @Nullable - private static Commenter findCommenter(PsiFile file, Editor editor) { + private static Commenter findCommenter(PsiFile file, Editor editor, Caret caret) { final FileType fileType = file.getFileType(); if (fileType instanceof AbstractFileType) { return ((AbstractFileType)fileType).getCommenter(); } - Language lang = PsiUtilBase.getLanguageInEditor(editor, file.getProject()); + Language lang = PsiUtilBase.getLanguageInEditor(caret, file.getProject()); return getCommenter(file, editor, lang, lang); } @@ -371,9 +368,8 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { @Nullable private PsiElement findCommentAtCaret() { - int offset = myEditor.getCaretModel().getOffset(); - SelectionModel selectionModel = myEditor.getSelectionModel(); - TextRange range = new TextRange(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()); + int offset = myCaret.getOffset(); + TextRange range = new TextRange(myCaret.getSelectionStart(), myCaret.getSelectionEnd()); if (offset == range.getEndOffset()) { offset--; } @@ -383,21 +379,16 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { PsiElement elt = myFile.getViewProvider().findElementAt(offset); if (elt == null) return null; PsiElement comment = PsiTreeUtil.getParentOfType(elt, PsiComment.class, false); - if (comment == null || selectionModel.hasSelection() && !range.contains(comment.getTextRange())) { + if (comment == null || myCaret.hasSelection() && !range.contains(comment.getTextRange())) { return null; } return comment; } - @Override - public boolean startInWriteAction() { - return true; - } - public void commentRange(int startOffset, int endOffset, String commentPrefix, String commentSuffix, Commenter commenter) { CharSequence chars = myDocument.getCharsSequence(); - LogicalPosition caretPosition = myEditor.getCaretModel().getLogicalPosition(); + LogicalPosition caretPosition = myCaret.getLogicalPosition(); if (startOffset == 0 || chars.charAt(startOffset - 1) == '\n') { if (endOffset == myDocument.getTextLength() || endOffset > 0 && chars.charAt(endOffset - 1) == '\n') { @@ -426,21 +417,17 @@ public class CommentByBlockCommentHandler implements CodeInsightActionHandler { nestingSuffix.append("\n"); TextRange range = insertNestedComments(chars, startOffset, endOffset, nestingPrefix.toString(), nestingSuffix.toString(), commenter); - myEditor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset()); - //myEditor.getSelectionModel().removeSelection(); + myCaret.setSelection(range.getStartOffset(), range.getEndOffset()); LogicalPosition pos = new LogicalPosition(caretPosition.line + 1, caretPosition.column); - myEditor.getCaretModel().moveToLogicalPosition(pos); - myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + myCaret.moveToLogicalPosition(pos); return; } } TextRange range = insertNestedComments(chars, startOffset, endOffset, commentPrefix, commentSuffix, commenter); - myEditor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset()); - //myEditor.getSelectionModel().removeSelection(); + myCaret.setSelection(range.getStartOffset(), range.getEndOffset()); LogicalPosition pos = new LogicalPosition(caretPosition.line, caretPosition.column + commentPrefix.length()); - myEditor.getCaretModel().moveToLogicalPosition(pos); - myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + myCaret.moveToLogicalPosition(pos); } private int doBoundCommentingAndGetShift(int offset, diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java index ba73ac14d715..d62f51bb2e02 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -16,12 +16,13 @@ package com.intellij.codeInsight.generation; -import com.intellij.codeInsight.CodeInsightActionHandler; import com.intellij.codeInsight.CodeInsightUtilBase; import com.intellij.codeInsight.CommentUtil; +import com.intellij.codeInsight.actions.MultiCaretCodeInsightActionHandler; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.ide.highlighter.custom.SyntaxTable; import com.intellij.injected.editor.EditorWindow; +import com.intellij.injected.editor.InjectedCaret; import com.intellij.lang.Commenter; import com.intellij.lang.Language; import com.intellij.lang.LanguageCommenters; @@ -38,7 +39,6 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleManager; @@ -53,181 +53,245 @@ import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -public class CommentByLineCommentHandler implements CodeInsightActionHandler { - +public class CommentByLineCommentHandler extends MultiCaretCodeInsightActionHandler { private Project myProject; - private PsiFile myFile; - private Document myDocument; - private Editor myEditor; - private int myStartOffset; - private int myEndOffset; - private int myStartLine; - private int myEndLine; - private int[] myStartOffsets; - private int[] myEndOffsets; - private Commenter[] myCommenters; - private Map<SelfManagingCommenter, CommenterDataHolder> myCommenterStateMap; private CodeStyleManager myCodeStyleManager; + private final List<Block> myBlocks = new ArrayList<Block>(); + @Override - public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { + // first pass - adjacent carets are grouped into blocks + public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, @NotNull PsiFile file) { if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) return; myProject = project; - myFile = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage()); - myEditor = editor; + file = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage()); - PsiElement context = InjectedLanguageManager.getInstance(myFile.getProject()).getInjectionHost(myFile); + PsiElement context = InjectedLanguageManager.getInstance(file.getProject()).getInjectionHost(file); if (context != null && (context.textContains('\'') || context.textContains('\"'))) { String s = context.getText(); if (StringUtil.startsWith(s, "\"") || StringUtil.startsWith(s, "\'")) { - myFile = context.getContainingFile(); - myEditor = editor instanceof EditorWindow ? ((EditorWindow)editor).getDelegate() : editor; + file = context.getContainingFile(); + editor = editor instanceof EditorWindow ? ((EditorWindow)editor).getDelegate() : editor; + caret = caret instanceof InjectedCaret ? ((InjectedCaret)caret).getDelegate() : caret; } } - myDocument = myEditor.getDocument(); - if (!FileDocumentManager.getInstance().requestWriting(myDocument, project)) { + Document document = editor.getDocument(); + if (!FileDocumentManager.getInstance().requestWriting(document, project)) { return; } - PsiDocumentManager.getInstance(project).commitDocument(myDocument); - - FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.comment.line"); - - myCodeStyleManager = CodeStyleManager.getInstance(myProject); - - final SelectionModel selectionModel = myEditor.getSelectionModel(); - - boolean hasSelection = selectionModel.hasSelection(); - myStartOffset = selectionModel.getSelectionStart(); - myEndOffset = selectionModel.getSelectionEnd(); + boolean hasSelection = caret.hasSelection(); + int startOffset = caret.getSelectionStart(); + int endOffset = caret.getSelectionEnd(); - FoldRegion fold = myEditor.getFoldingModel().getCollapsedRegionAtOffset(myStartOffset); - if (fold != null && fold.shouldNeverExpand() && fold.getStartOffset() == myStartOffset && fold.getEndOffset() == myEndOffset) { - // Foldings that never expand are automatically selected, so the fact it is selected must not interfer with commenter's logic + FoldRegion fold = editor.getFoldingModel().getCollapsedRegionAtOffset(startOffset); + if (fold != null && fold.shouldNeverExpand() && fold.getStartOffset() == startOffset && fold.getEndOffset() == endOffset) { + // Foldings that never expand are automatically selected, so the fact it is selected must not interfere with commenter's logic hasSelection = false; } - if (myDocument.getTextLength() == 0) return; + if (document.getTextLength() == 0) return; while (true) { - int lastLineEnd = myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset)); - FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(lastLineEnd); + int lastLineEnd = document.getLineEndOffset(document.getLineNumber(endOffset)); + FoldRegion collapsedAt = editor.getFoldingModel().getCollapsedRegionAtOffset(lastLineEnd); if (collapsedAt != null) { - final int endOffset = collapsedAt.getEndOffset(); - if (endOffset <= myEndOffset) { + final int regionEndOffset = collapsedAt.getEndOffset(); + if (regionEndOffset <= endOffset) { break; } - myEndOffset = endOffset; + endOffset = regionEndOffset; } else { break; } } - boolean wholeLinesSelected = !hasSelection || - myStartOffset == myDocument.getLineStartOffset(myDocument.getLineNumber(myStartOffset)) && - myEndOffset == myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset - 1)) + 1; - - boolean startingNewLineComment = !hasSelection && isLineEmpty(myDocument.getLineNumber(myStartOffset)) && !Comparing - .equal(IdeActions.ACTION_COMMENT_LINE, ActionManagerEx.getInstanceEx().getPrevPreformedActionId()); - doComment(); - - if (startingNewLineComment) { - final Commenter commenter = myCommenters[0]; - if (commenter != null) { - String prefix; - if (commenter instanceof SelfManagingCommenter) { - prefix = ((SelfManagingCommenter)commenter).getCommentPrefix( - myStartLine, - myDocument, - myCommenterStateMap.get((SelfManagingCommenter)commenter) - ); - if (prefix == null) prefix = ""; // TODO - } - else { - prefix = commenter.getLineCommentPrefix(); - if (prefix == null) prefix = commenter.getBlockCommentPrefix(); - } + int startLine = document.getLineNumber(startOffset); + int endLine = document.getLineNumber(endOffset); - int lineStart = myDocument.getLineStartOffset(myStartLine); - lineStart = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), lineStart, " \t"); - lineStart += prefix.length(); - lineStart = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), lineStart, " \t"); - if (lineStart > myDocument.getTextLength()) lineStart = myDocument.getTextLength(); - myEditor.getCaretModel().moveToOffset(lineStart); - myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - } + if (endLine > startLine && document.getLineStartOffset(endLine) == endOffset) { + endLine--; + } + + Block lastBlock = myBlocks.isEmpty() ? null : myBlocks.get(myBlocks.size() - 1); + Block currentBlock; + if (lastBlock == null || lastBlock.editor != editor || lastBlock.psiFile != file || endLine < (lastBlock.startLine - 1)) { + currentBlock = new Block(); + currentBlock.editor = editor; + currentBlock.psiFile = file; + currentBlock.endLine = endLine; + myBlocks.add(currentBlock); } else { - if (!hasSelection) { - // Don't tweak caret position if we're already located on the last document line. - LogicalPosition position = myEditor.getCaretModel().getLogicalPosition(); - if (position.line < myDocument.getLineCount() - 1) { - int verticalShift = 1 + myEditor.getSoftWrapModel().getSoftWrapsForLine(position.line).size() - - position.softWrapLinesOnCurrentLogicalLine; - myEditor.getCaretModel().moveCaretRelatively(0, verticalShift, false, false, true); - } - } - else { - if (wholeLinesSelected) { - selectionModel.setSelection(myStartOffset, selectionModel.getSelectionEnd()); - } - } + currentBlock = lastBlock; } - } + currentBlock.carets.add(caret); + currentBlock.startLine = startLine; - private boolean isLineEmpty(final int line) { - final CharSequence chars = myDocument.getCharsSequence(); - int start = myDocument.getLineStartOffset(line); - int end = Math.min(myDocument.getLineEndOffset(line), myDocument.getTextLength() - 1); - for (int i = start; i <= end; i++) { - if (!Character.isWhitespace(chars.charAt(i))) return false; + boolean wholeLinesSelected = !hasSelection || + startOffset == document.getLineStartOffset(document.getLineNumber(startOffset)) && + endOffset == document.getLineEndOffset(document.getLineNumber(endOffset - 1)) + 1; + boolean startingNewLineComment = !hasSelection + && isLineEmpty(document, document.getLineNumber(startOffset)) + && !Comparing.equal(IdeActions.ACTION_COMMENT_LINE, + ActionManagerEx.getInstanceEx().getPrevPreformedActionId()); + currentBlock.caretUpdate = startingNewLineComment ? CaretUpdate.PUT_AT_COMMENT_START : + !hasSelection ? CaretUpdate.SHIFT_DOWN : + wholeLinesSelected ? CaretUpdate.RESTORE_SELECTION : null; } - return true; - } @Override - public boolean startInWriteAction() { - return true; - } + public void postInvoke() { + FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.comment.line"); + + myCodeStyleManager = CodeStyleManager.getInstance(myProject); - private void doComment() { - myStartLine = myDocument.getLineNumber(myStartOffset); - myEndLine = myDocument.getLineNumber(myEndOffset); + // second pass - determining whether we need to comment or to uncomment + boolean allLinesCommented = true; + for (Block block : myBlocks) { + int startLine = block.startLine; + int endLine = block.endLine; + Document document = block.editor.getDocument(); + PsiFile psiFile = block.psiFile; + + block.startOffsets = new int[endLine - startLine + 1]; + block.endOffsets = new int[endLine - startLine + 1]; + block.commenters = new Commenter[endLine - startLine + 1]; + block.commenterStateMap = new THashMap<SelfManagingCommenter, CommenterDataHolder>(); + CharSequence chars = document.getCharsSequence(); + + boolean singleline = startLine == endLine; + int offset = document.getLineStartOffset(startLine); + offset = CharArrayUtil.shiftForward(chars, offset, " \t"); - if (myEndLine > myStartLine && myDocument.getLineStartOffset(myEndLine) == myEndOffset) { - myEndLine--; - } + int endOffset = CharArrayUtil.shiftBackward(chars, document.getLineEndOffset(endLine), " \t\n"); - myStartOffsets = new int[myEndLine - myStartLine + 1]; - myEndOffsets = new int[myEndLine - myStartLine + 1]; - myCommenters = new Commenter[myEndLine - myStartLine + 1]; - myCommenterStateMap = new THashMap<SelfManagingCommenter, CommenterDataHolder>(); - CharSequence chars = myDocument.getCharsSequence(); + block.blockSuitableCommenter = getBlockSuitableCommenter(psiFile, offset, endOffset); + block.commentWithIndent = + !CodeStyleSettingsManager.getSettings(myProject).getCommonSettings(psiFile.getLanguage()).LINE_COMMENT_AT_FIRST_COLUMN; + + for (int line = startLine; line <= endLine; line++) { + Commenter commenter = block.blockSuitableCommenter != null ? block.blockSuitableCommenter : findCommenter(block.editor, psiFile, line); + if (commenter == null || commenter.getLineCommentPrefix() == null + && (commenter.getBlockCommentPrefix() == null || commenter.getBlockCommentSuffix() == null)) { + block.skip = true; + break; + } - boolean singleline = myStartLine == myEndLine; - int offset = myDocument.getLineStartOffset(myStartLine); - offset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), offset, " \t"); + if (commenter instanceof SelfManagingCommenter && block.commenterStateMap.get(commenter) == null) { + final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; + CommenterDataHolder state = selfManagingCommenter.createLineCommentingState(startLine, endLine, document, psiFile); + if (state == null) state = SelfManagingCommenter.EMPTY_STATE; + block.commenterStateMap.put(selfManagingCommenter, state); + } - int endOffset = CharArrayUtil.shiftBackward(myDocument.getCharsSequence(), myDocument.getLineEndOffset(myEndLine), " \t\n"); + block.commenters[line - startLine] = commenter; + if (!isLineCommented(block, line, commenter) && (singleline || !isLineEmpty(document, line))) { + allLinesCommented = false; + if (commenter instanceof IndentedCommenter) { + final Boolean value = ((IndentedCommenter)commenter).forceIndentedLineComment(); + if (value != null) { + block.commentWithIndent = value; + } + } + break; + } + } + } + boolean moveCarets = true; + for (Block block : myBlocks) { + if (block.carets.size() > 1 && block.startLine != block.endLine) { + moveCarets = false; + break; + } + } + // third pass - actual change + for (Block block : myBlocks) { + if (!block.skip) { + if (!allLinesCommented) { + if (!block.commentWithIndent) { + doDefaultCommenting(block); + } + else { + doIndentCommenting(block); + } + } + else { + for (int line = block.endLine; line >= block.startLine; line--) { + uncommentLine(block, line); + } + } + } + + if (!moveCarets || block.caretUpdate == null) { + continue; + } + Document document = block.editor.getDocument(); + for (Caret caret : block.carets) { + switch (block.caretUpdate) { + case PUT_AT_COMMENT_START: + final Commenter commenter = block.commenters[0]; + if (commenter != null) { + String prefix; + if (commenter instanceof SelfManagingCommenter) { + prefix = ((SelfManagingCommenter)commenter).getCommentPrefix(block.startLine, + document, + block.commenterStateMap.get((SelfManagingCommenter)commenter)); + if (prefix == null) prefix = ""; // TODO + } + else { + prefix = commenter.getLineCommentPrefix(); + if (prefix == null) prefix = commenter.getBlockCommentPrefix(); + } + + int lineStart = document.getLineStartOffset(block.startLine); + lineStart = CharArrayUtil.shiftForward(document.getCharsSequence(), lineStart, " \t"); + lineStart += prefix.length(); + lineStart = CharArrayUtil.shiftForward(document.getCharsSequence(), lineStart, " \t"); + if (lineStart > document.getTextLength()) lineStart = document.getTextLength(); + caret.moveToOffset(lineStart); + } + break; + case SHIFT_DOWN: + // Don't tweak caret position if we're already located on the last document line. + LogicalPosition position = caret.getLogicalPosition(); + if (position.line < document.getLineCount() - 1) { + int verticalShift = 1 + block.editor.getSoftWrapModel().getSoftWrapsForLine(position.line).size() + - position.softWrapLinesOnCurrentLogicalLine; + caret.moveCaretRelatively(0, verticalShift, false, true); + } + break; + case RESTORE_SELECTION: + caret.setSelection(document.getLineStartOffset(document.getLineNumber(caret.getSelectionStart())), caret.getSelectionEnd()); + } + } + } + } + + private static Commenter getBlockSuitableCommenter(final PsiFile file, int offset, int endOffset) { final Language languageSuitableForCompleteFragment; if (offset >= endOffset) { // we are on empty line - PsiElement element = myFile.findElementAt(offset); + PsiElement element = file.findElementAt(offset); if (element != null) languageSuitableForCompleteFragment = element.getParent().getLanguage(); else languageSuitableForCompleteFragment = null; - } else { - languageSuitableForCompleteFragment = PsiUtilBase.reallyEvaluateLanguageInRange(offset, endOffset, myFile); } + else { + languageSuitableForCompleteFragment = PsiUtilBase.reallyEvaluateLanguageInRange(offset, endOffset, file); + } + Commenter blockSuitableCommenter = - languageSuitableForCompleteFragment == null ? LanguageCommenters.INSTANCE.forLanguage(myFile.getLanguage()) : null; - if (blockSuitableCommenter == null && myFile.getFileType() instanceof CustomSyntaxTableFileType) { + languageSuitableForCompleteFragment == null ? LanguageCommenters.INSTANCE.forLanguage(file.getLanguage()) : null; + if (blockSuitableCommenter == null && file.getFileType() instanceof CustomSyntaxTableFileType) { blockSuitableCommenter = new Commenter() { - final SyntaxTable mySyntaxTable = ((CustomSyntaxTableFileType)myFile.getFileType()).getSyntaxTable(); + final SyntaxTable mySyntaxTable = ((CustomSyntaxTableFileType)file.getFileType()).getSyntaxTable(); @Override @Nullable @@ -259,96 +323,30 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { }; } - boolean allLineCommented = true; - boolean commentWithIndent = - !CodeStyleSettingsManager.getSettings(myProject).getCommonSettings(myFile.getLanguage()).LINE_COMMENT_AT_FIRST_COLUMN; - - for (int line = myStartLine; line <= myEndLine; line++) { - Commenter commenter = blockSuitableCommenter != null ? blockSuitableCommenter : findCommenter(line); - if (commenter == null) return; - if (commenter.getLineCommentPrefix() == null && - (commenter.getBlockCommentPrefix() == null || commenter.getBlockCommentSuffix() == null)) { - return; - } - - if (commenter instanceof SelfManagingCommenter && - myCommenterStateMap.get(commenter) == null) { - final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; - CommenterDataHolder state = - selfManagingCommenter.createLineCommentingState(myStartLine, myEndLine, myDocument, myFile); - if (state == null) state = SelfManagingCommenter.EMPTY_STATE; - myCommenterStateMap.put(selfManagingCommenter, state); - } - - myCommenters[line - myStartLine] = commenter; - if (!isLineCommented(line, chars, commenter) && (singleline || !isLineEmpty(line))) { - allLineCommented = false; - if (commenter instanceof IndentedCommenter) { - final Boolean value = ((IndentedCommenter)commenter).forceIndentedLineComment(); - if (value != null) { - commentWithIndent = value; - } - } - break; - } - } + return blockSuitableCommenter; + } - if (!allLineCommented) { - if (!commentWithIndent) { - doDefaultCommenting(blockSuitableCommenter); - } - else { - doIndentCommenting(blockSuitableCommenter); - } - } - else { - for (int line = myEndLine; line >= myStartLine; line--) { - uncommentLine(line); - //int offset1 = myStartOffsets[line - myStartLine]; - //int offset2 = myEndOffsets[line - myStartLine]; - //if (offset1 == offset2) continue; - //Commenter commenter = myCommenters[line - myStartLine]; - //String prefix = commenter.getBlockCommentPrefix(); - //if (prefix == null || !myDocument.getText().substring(offset1, myDocument.getTextLength()).startsWith(prefix)) { - // prefix = commenter.getLineCommentPrefix(); - //} - // - //String suffix = commenter.getBlockCommentSuffix(); - //if (suffix == null && prefix != null) suffix = ""; - // - //if (prefix != null && suffix != null) { - // final int suffixLen = suffix.length(); - // final int prefixLen = prefix.length(); - // if (offset2 >= 0) { - // if (!CharArrayUtil.regionMatches(chars, offset1 + prefixLen, prefix)) { - // myDocument.deleteString(offset2 - suffixLen, offset2); - // } - // } - // if (offset1 >= 0) { - // for (int i = offset2 - suffixLen - 1; i > offset1 + prefixLen; --i) { - // if (CharArrayUtil.regionMatches(chars, i, suffix)) { - // myDocument.deleteString(i, i + suffixLen); - // } - // else if (CharArrayUtil.regionMatches(chars, i, prefix)) { - // myDocument.deleteString(i, i + prefixLen); - // } - // } - // myDocument.deleteString(offset1, offset1 + prefixLen); - // } - //} - } + private static boolean isLineEmpty(Document document, final int line) { + final CharSequence chars = document.getCharsSequence(); + int start = document.getLineStartOffset(line); + int end = Math.min(document.getLineEndOffset(line), document.getTextLength() - 1); + for (int i = start; i <= end; i++) { + if (!Character.isWhitespace(chars.charAt(i))) return false; } + return true; } - private boolean isLineCommented(final int line, final CharSequence chars, final Commenter commenter) { + private static boolean isLineCommented(Block block, final int line, final Commenter commenter) { boolean commented; int lineEndForBlockCommenting = -1; - int lineStart = myDocument.getLineStartOffset(line); + Document document = block.editor.getDocument(); + int lineStart = document.getLineStartOffset(line); + CharSequence chars = document.getCharsSequence(); lineStart = CharArrayUtil.shiftForward(chars, lineStart, " \t"); if (commenter instanceof SelfManagingCommenter) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; - commented = selfManagingCommenter.isLineCommented(line, lineStart, myDocument, myCommenterStateMap.get(selfManagingCommenter)); + commented = selfManagingCommenter.isLineCommented(line, lineStart, document, block.commenterStateMap.get(selfManagingCommenter)); } else { String prefix = commenter.getLineCommentPrefix(); @@ -359,8 +357,8 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { else { prefix = commenter.getBlockCommentPrefix(); String suffix = commenter.getBlockCommentSuffix(); - final int textLength = myDocument.getTextLength(); - lineEndForBlockCommenting = myDocument.getLineEndOffset(line); + final int textLength = document.getTextLength(); + lineEndForBlockCommenting = document.getLineEndOffset(line); if (lineEndForBlockCommenting == textLength) { final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t"); if (shifted < textLength - 1) lineEndForBlockCommenting = shifted; @@ -368,59 +366,61 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { else { lineEndForBlockCommenting = CharArrayUtil.shiftBackward(chars, lineEndForBlockCommenting, " \t"); } - commented = lineStart == lineEndForBlockCommenting && myStartLine != myEndLine || + commented = lineStart == lineEndForBlockCommenting && block.startLine != block.endLine || CharArrayUtil.regionMatches(chars, lineStart, prefix) && CharArrayUtil.regionMatches(chars, lineEndForBlockCommenting - suffix.length(), suffix); } } if (commented) { - myStartOffsets[line - myStartLine] = lineStart; - myEndOffsets[line - myStartLine] = lineEndForBlockCommenting; + block.startOffsets[line - block.startLine] = lineStart; + block.endOffsets[line - block.startLine] = lineEndForBlockCommenting; } return commented; } @Nullable - private Commenter findCommenter(final int line) { - final FileType fileType = myFile.getFileType(); + private static Commenter findCommenter(Editor editor, PsiFile file, final int line) { + final FileType fileType = file.getFileType(); if (fileType instanceof AbstractFileType) { return ((AbstractFileType)fileType).getCommenter(); } - int lineStartOffset = myDocument.getLineStartOffset(line); - int lineEndOffset = myDocument.getLineEndOffset(line) - 1; - final CharSequence charSequence = myDocument.getCharsSequence(); + Document document = editor.getDocument(); + int lineStartOffset = document.getLineStartOffset(line); + int lineEndOffset = document.getLineEndOffset(line) - 1; + final CharSequence charSequence = document.getCharsSequence(); lineStartOffset = CharArrayUtil.shiftForward(charSequence, lineStartOffset, " \t"); lineEndOffset = CharArrayUtil.shiftBackward(charSequence, lineEndOffset < 0 ? 0 : lineEndOffset, " \t"); - final Language lineStartLanguage = PsiUtilCore.getLanguageAtOffset(myFile, lineStartOffset); - final Language lineEndLanguage = PsiUtilCore.getLanguageAtOffset(myFile, lineEndOffset); - return CommentByBlockCommentHandler.getCommenter(myFile, myEditor, lineStartLanguage, lineEndLanguage); + final Language lineStartLanguage = PsiUtilCore.getLanguageAtOffset(file, lineStartOffset); + final Language lineEndLanguage = PsiUtilCore.getLanguageAtOffset(file, lineEndOffset); + return CommentByBlockCommentHandler.getCommenter(file, editor, lineStartLanguage, lineEndLanguage); } - private Indent computeMinIndent(int line1, int line2, CharSequence chars, CodeStyleManager codeStyleManager, FileType fileType) { - Indent minIndent = CommentUtil.getMinLineIndent(myProject, myDocument, line1, line2, fileType); + private Indent computeMinIndent(Editor editor, PsiFile psiFile, int line1, int line2, FileType fileType) { + Document document = editor.getDocument(); + Indent minIndent = CommentUtil.getMinLineIndent(myProject, document, line1, line2, fileType); if (line1 > 0) { - int commentOffset = getCommentStart(line1 - 1); + int commentOffset = getCommentStart(editor, psiFile, line1 - 1); if (commentOffset >= 0) { - int lineStart = myDocument.getLineStartOffset(line1 - 1); - String space = chars.subSequence(lineStart, commentOffset).toString(); - Indent indent = codeStyleManager.getIndent(space, fileType); + int lineStart = document.getLineStartOffset(line1 - 1); + String space = document.getCharsSequence().subSequence(lineStart, commentOffset).toString(); + Indent indent = myCodeStyleManager.getIndent(space, fileType); minIndent = minIndent != null ? indent.min(minIndent) : indent; } } if (minIndent == null) { - minIndent = codeStyleManager.zeroIndent(); + minIndent = myCodeStyleManager.zeroIndent(); } return minIndent; } - private int getCommentStart(int line) { - int offset = myDocument.getLineStartOffset(line); - CharSequence chars = myDocument.getCharsSequence(); + private static int getCommentStart(Editor editor, PsiFile psiFile, int line) { + int offset = editor.getDocument().getLineStartOffset(line); + CharSequence chars = editor.getDocument().getCharsSequence(); offset = CharArrayUtil.shiftForward(chars, offset, " \t"); - final Commenter commenter = findCommenter(line); + final Commenter commenter = findCommenter(editor, psiFile, line); if (commenter == null) return -1; String prefix = commenter.getLineCommentPrefix(); if (prefix == null) prefix = commenter.getBlockCommentPrefix(); @@ -428,53 +428,55 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { return CharArrayUtil.regionMatches(chars, offset, prefix) ? offset : -1; } - public void doDefaultCommenting(final Commenter commenter) { + public void doDefaultCommenting(final Block block) { + final Document document = block.editor.getDocument(); DocumentUtil.executeInBulk( - myDocument, myEndLine - myStartLine >= Registry.intValue("comment.by.line.bulk.lines.trigger"), new Runnable() { + document, block.endLine - block.startLine >= Registry.intValue("comment.by.line.bulk.lines.trigger"), new Runnable() { @Override public void run() { - for (int line = myEndLine; line >= myStartLine; line--) { - int offset = myDocument.getLineStartOffset(line); - commentLine(line, offset, commenter); + for (int line = block.endLine; line >= block.startLine; line--) { + int offset = document.getLineStartOffset(line); + commentLine(block, line, offset); } } }); } - private void doIndentCommenting(final Commenter commenter) { - final CharSequence chars = myDocument.getCharsSequence(); - final FileType fileType = myFile.getFileType(); - final Indent minIndent = computeMinIndent(myStartLine, myEndLine, chars, myCodeStyleManager, fileType); + private void doIndentCommenting(final Block block) { + final Document document = block.editor.getDocument(); + final CharSequence chars = document.getCharsSequence(); + final FileType fileType = block.psiFile.getFileType(); + final Indent minIndent = computeMinIndent(block.editor, block.psiFile, block.startLine, block.endLine, fileType); DocumentUtil.executeInBulk( - myDocument, myEndLine - myStartLine > Registry.intValue("comment.by.line.bulk.lines.trigger"), new Runnable() { - @Override - public void run() { - for (int line = myEndLine; line >= myStartLine; line--) { - int lineStart = myDocument.getLineStartOffset(line); - int offset = lineStart; - final StringBuilder buffer = new StringBuilder(); - while (true) { - String space = buffer.toString(); - Indent indent = myCodeStyleManager.getIndent(space, fileType); - if (indent.isGreaterThan(minIndent) || indent.equals(minIndent)) break; - char c = chars.charAt(offset); - if (c != ' ' && c != '\t') { - String newSpace = myCodeStyleManager.fillIndent(minIndent, fileType); - myDocument.replaceString(lineStart, offset, newSpace); - offset = lineStart + newSpace.length(); - break; + document, block.endLine - block.startLine > Registry.intValue("comment.by.line.bulk.lines.trigger"), new Runnable() { + @Override + public void run() { + for (int line = block.endLine; line >= block.startLine; line--) { + int lineStart = document.getLineStartOffset(line); + int offset = lineStart; + final StringBuilder buffer = new StringBuilder(); + while (true) { + String space = buffer.toString(); + Indent indent = myCodeStyleManager.getIndent(space, fileType); + if (indent.isGreaterThan(minIndent) || indent.equals(minIndent)) break; + char c = chars.charAt(offset); + if (c != ' ' && c != '\t') { + String newSpace = myCodeStyleManager.fillIndent(minIndent, fileType); + document.replaceString(lineStart, offset, newSpace); + offset = lineStart + newSpace.length(); + break; + } + buffer.append(c); + offset++; } - buffer.append(c); - offset++; + commentLine(block, line, offset); } - commentLine(line, offset, commenter); } - } - }); + }); } - private void uncommentRange(int startOffset, int endOffset, @NotNull Commenter commenter) { + private static void uncommentRange(Document document, int startOffset, int endOffset, @NotNull Commenter commenter) { final String commentedSuffix = commenter.getCommentedBlockCommentSuffix(); final String commentedPrefix = commenter.getCommentedBlockCommentPrefix(); final String prefix = commenter.getBlockCommentPrefix(); @@ -482,52 +484,53 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { if (prefix == null || suffix == null) { return; } - if (endOffset >= suffix.length() && CharArrayUtil.regionMatches(myDocument.getCharsSequence(), endOffset - suffix.length(), suffix)) { - myDocument.deleteString(endOffset - suffix.length(), endOffset); - endOffset = myDocument.getTextLength(); + if (endOffset >= suffix.length() && CharArrayUtil.regionMatches(document.getCharsSequence(), endOffset - suffix.length(), suffix)) { + document.deleteString(endOffset - suffix.length(), endOffset); + endOffset = document.getTextLength(); } if (commentedPrefix != null && commentedSuffix != null) { - CommentByBlockCommentHandler.commentNestedComments(myDocument, new TextRange(startOffset, endOffset), commenter); + CommentByBlockCommentHandler.commentNestedComments(document, new TextRange(startOffset, endOffset), commenter); } - myDocument.deleteString(startOffset, startOffset + prefix.length()); + document.deleteString(startOffset, startOffset + prefix.length()); } - private void uncommentLine(int line) { - Commenter commenter = myCommenters[line - myStartLine]; - if (commenter == null) commenter = findCommenter(line); + private static void uncommentLine(Block block, int line) { + Document document = block.editor.getDocument(); + Commenter commenter = block.commenters[line - block.startLine]; + if (commenter == null) commenter = findCommenter(block.editor, block.psiFile, line); if (commenter == null) return; - final int startOffset = myStartOffsets[line - myStartLine]; + final int startOffset = block.startOffsets[line - block.startLine]; if (commenter instanceof SelfManagingCommenter) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; - selfManagingCommenter.uncommentLine(line, startOffset, myDocument, myCommenterStateMap.get(selfManagingCommenter)); + selfManagingCommenter.uncommentLine(line, startOffset, document, block.commenterStateMap.get(selfManagingCommenter)); return; } - final int endOffset = myEndOffsets[line - myStartLine]; + final int endOffset = block.endOffsets[line - block.startLine]; if (startOffset == endOffset) { return; } String prefix = commenter.getLineCommentPrefix(); if (prefix != null) { - CharSequence chars = myDocument.getCharsSequence(); + CharSequence chars = document.getCharsSequence(); if (commenter instanceof CommenterWithLineSuffix) { CommenterWithLineSuffix commenterWithLineSuffix = (CommenterWithLineSuffix)commenter; String suffix = commenterWithLineSuffix.getLineCommentSuffix(); - int theEnd = endOffset > 0 ? endOffset : myDocument.getLineEndOffset(line); + int theEnd = endOffset > 0 ? endOffset : document.getLineEndOffset(line); while (theEnd > startOffset && Character.isWhitespace(chars.charAt(theEnd - 1))) { theEnd--; } - String lineText = myDocument.getText(new TextRange(startOffset, theEnd)); + String lineText = document.getText(new TextRange(startOffset, theEnd)); if (lineText.indexOf(suffix) != -1) { int start = startOffset + lineText.indexOf(suffix); - myDocument.deleteString(start, start + suffix.length()); + document.deleteString(start, start + suffix.length()); } } @@ -544,10 +547,10 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { charsToDelete++; } } - myDocument.deleteString(startOffset, startOffset + charsToDelete); + document.deleteString(startOffset, startOffset + charsToDelete); return; } - String text = myDocument.getCharsSequence().subSequence(startOffset, endOffset).toString(); + String text = document.getCharsSequence().subSequence(startOffset, endOffset).toString(); prefix = commenter.getBlockCommentPrefix(); final String suffix = commenter.getBlockCommentSuffix(); @@ -575,45 +578,47 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { assert prefixes.size() == suffixes.size(); for (int i = prefixes.size() - 1; i >= 0; i--) { - uncommentRange(startOffset + prefixes.get(i), Math.min(startOffset + suffixes.get(i) + suffix.length(), endOffset), commenter); + uncommentRange(document, startOffset + prefixes.get(i), Math.min(startOffset + suffixes.get(i) + suffix.length(), endOffset), commenter); } } - private void commentLine(int line, int offset, @Nullable Commenter commenter) { - if (commenter == null) commenter = findCommenter(line); + private static void commentLine(Block block, int line, int offset) { + Commenter commenter = block.blockSuitableCommenter; + Document document = block.editor.getDocument(); + if (commenter == null) commenter = findCommenter(block.editor, block.psiFile, line); if (commenter == null) return; if (commenter instanceof SelfManagingCommenter) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; - selfManagingCommenter.commentLine(line, offset, myDocument, myCommenterStateMap.get(selfManagingCommenter)); + selfManagingCommenter.commentLine(line, offset, document, block.commenterStateMap.get(selfManagingCommenter)); return; } String prefix = commenter.getLineCommentPrefix(); if (prefix != null) { if (commenter instanceof CommenterWithLineSuffix) { - int endOffset = myDocument.getLineEndOffset(line); - endOffset = CharArrayUtil.shiftBackward(myDocument.getCharsSequence(), endOffset, " \t"); - int shiftedStartOffset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), offset, " \t"); + int endOffset = document.getLineEndOffset(line); + endOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), endOffset, " \t"); + int shiftedStartOffset = CharArrayUtil.shiftForward(document.getCharsSequence(), offset, " \t"); String lineSuffix = ((CommenterWithLineSuffix)commenter).getLineCommentSuffix(); - if (!CharArrayUtil.regionMatches(myDocument.getCharsSequence(), shiftedStartOffset, prefix)) { - if (!CharArrayUtil.regionMatches(myDocument.getCharsSequence(), endOffset - lineSuffix.length(), lineSuffix)) { - myDocument.insertString(endOffset, lineSuffix); + if (!CharArrayUtil.regionMatches(document.getCharsSequence(), shiftedStartOffset, prefix)) { + if (!CharArrayUtil.regionMatches(document.getCharsSequence(), endOffset - lineSuffix.length(), lineSuffix)) { + document.insertString(endOffset, lineSuffix); } - myDocument.insertString(offset, prefix); + document.insertString(offset, prefix); } } else { - myDocument.insertString(offset, prefix); + document.insertString(offset, prefix); } } else { prefix = commenter.getBlockCommentPrefix(); String suffix = commenter.getBlockCommentSuffix(); if (prefix == null || suffix == null) return; - int endOffset = myDocument.getLineEndOffset(line); - if (endOffset == offset && myStartLine != myEndLine) return; - final int textLength = myDocument.getTextLength(); - final CharSequence chars = myDocument.getCharsSequence(); + int endOffset = document.getLineEndOffset(line); + if (endOffset == offset && block.startLine != block.endLine) return; + final int textLength = document.getTextLength(); + final CharSequence chars = document.getCharsSequence(); offset = CharArrayUtil.shiftForward(chars, offset, " \t"); if (endOffset == textLength) { final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t"); @@ -623,7 +628,7 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { endOffset = CharArrayUtil.shiftBackward(chars, endOffset, " \t"); } if (endOffset < offset || - offset == textLength - 1 && line != myDocument.getLineCount() - 1) { + offset == textLength - 1 && line != document.getLineCount() - 1) { return; } final String text = chars.subSequence(offset, endOffset).toString(); @@ -653,7 +658,7 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { } } if (!(commentedSuffix == null && !suffixes.isEmpty() && offset + suffixes.get(suffixes.size() - 1) + suffix.length() >= endOffset)) { - myDocument.insertString(endOffset, suffix); + document.insertString(endOffset, suffix); } int nearestPrefix = prefixes.size() - 1; int nearestSuffix = suffixes.size() - 1; @@ -662,26 +667,46 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { final int position = prefixes.get(nearestPrefix); nearestPrefix--; if (commentedPrefix != null) { - myDocument.replaceString(offset + position, offset + position + prefix.length(), commentedPrefix); + document.replaceString(offset + position, offset + position + prefix.length(), commentedPrefix); } else if (position != 0) { - myDocument.insertString(offset + position, suffix); + document.insertString(offset + position, suffix); } } else { final int position = suffixes.get(nearestSuffix); nearestSuffix--; if (commentedSuffix != null) { - myDocument.replaceString(offset + position, offset + position + suffix.length(), commentedSuffix); + document.replaceString(offset + position, offset + position + suffix.length(), commentedSuffix); } else if (offset + position + suffix.length() < endOffset) { - myDocument.insertString(offset + position + suffix.length(), prefix); + document.insertString(offset + position + suffix.length(), prefix); } } } if (!(commentedPrefix == null && !prefixes.isEmpty() && prefixes.get(0) == 0)) { - myDocument.insertString(offset, prefix); + document.insertString(offset, prefix); } } } + + private static class Block { + private Editor editor; + private PsiFile psiFile; + private List<Caret> carets = new ArrayList<Caret>(); + private int startLine; + private int endLine; + private int[] startOffsets; + private int[] endOffsets; + private Commenter blockSuitableCommenter; + private Commenter[] commenters; + private Map<SelfManagingCommenter, CommenterDataHolder> commenterStateMap; + private boolean commentWithIndent; + private CaretUpdate caretUpdate; + private boolean skip; + } + + private enum CaretUpdate { + PUT_AT_COMMENT_START, SHIFT_DOWN, RESTORE_SELECTION + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/CommentByBlockCommentAction.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/CommentByBlockCommentAction.java index ce0d3b63e6e0..bd24557744f5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/CommentByBlockCommentAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/CommentByBlockCommentAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -16,11 +16,12 @@ package com.intellij.codeInsight.generation.actions; -import com.intellij.codeInsight.CodeInsightActionHandler; -import com.intellij.codeInsight.actions.BaseCodeInsightAction; +import com.intellij.codeInsight.actions.MultiCaretCodeInsightAction; +import com.intellij.codeInsight.actions.MultiCaretCodeInsightActionHandler; import com.intellij.codeInsight.generation.CommentByBlockCommentHandler; import com.intellij.lang.Commenter; import com.intellij.lang.LanguageCommenters; +import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.impl.AbstractFileType; @@ -29,19 +30,19 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; -public class CommentByBlockCommentAction extends BaseCodeInsightAction implements DumbAware { +public class CommentByBlockCommentAction extends MultiCaretCodeInsightAction implements DumbAware { public CommentByBlockCommentAction() { setEnabledInModalContext(true); } @NotNull @Override - protected CodeInsightActionHandler getHandler() { + protected MultiCaretCodeInsightActionHandler getHandler() { return new CommentByBlockCommentHandler(); } @Override - protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull final PsiFile file) { + protected boolean isValidFor(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, @NotNull final PsiFile file) { final FileType fileType = file.getFileType(); if (fileType instanceof AbstractFileType) { return ((AbstractFileType)fileType).getCommenter() != null; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/CommentByLineCommentAction.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/CommentByLineCommentAction.java index 990e64701ce1..3ee4ecc4d65e 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/CommentByLineCommentAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/CommentByLineCommentAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -16,9 +16,10 @@ package com.intellij.codeInsight.generation.actions; -import com.intellij.codeInsight.CodeInsightActionHandler; -import com.intellij.codeInsight.actions.BaseCodeInsightAction; +import com.intellij.codeInsight.actions.MultiCaretCodeInsightAction; +import com.intellij.codeInsight.actions.MultiCaretCodeInsightActionHandler; import com.intellij.codeInsight.generation.CommentByLineCommentHandler; +import com.intellij.openapi.editor.Caret; import com.intellij.openapi.fileTypes.impl.AbstractFileType; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileTypes.FileType; @@ -28,19 +29,19 @@ import com.intellij.psi.PsiFile; import com.intellij.lang.LanguageCommenters; import org.jetbrains.annotations.NotNull; -public class CommentByLineCommentAction extends BaseCodeInsightAction implements DumbAware { +public class CommentByLineCommentAction extends MultiCaretCodeInsightAction implements DumbAware { public CommentByLineCommentAction() { setEnabledInModalContext(true); } @NotNull @Override - protected CodeInsightActionHandler getHandler() { + protected MultiCaretCodeInsightActionHandler getHandler() { return new CommentByLineCommentHandler(); } @Override - protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull final PsiFile file) { + protected boolean isValidFor(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, @NotNull final PsiFile file) { final FileType fileType = file.getFileType(); if (fileType instanceof AbstractFileType) { return ((AbstractFileType)fileType).getCommenter() != null; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java index 284a8ecb85bf..3a1f04b80414 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java @@ -623,10 +623,6 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, } public boolean performGuardedChange(Runnable change) { - return performGuardedChange(change, null); - } - - public boolean performGuardedChange(Runnable change, @Nullable final String debug) { checkValid(); assert !myChangeGuard : "already in change"; @@ -634,7 +630,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, myChangeGuard = true; boolean result; try { - result = myOffsets.performGuardedChange(change, debug); + result = myOffsets.performGuardedChange(change); } finally { myEditor.getDocument().stopGuardedBlockChecking(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupOffsets.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupOffsets.java index fe53f1796dbd..9d918cc0e175 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupOffsets.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupOffsets.java @@ -15,27 +15,26 @@ */ package com.intellij.codeInsight.lookup.impl; -import com.intellij.codeInsight.completion.RangeMarkerSpy; import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; -import org.jetbrains.annotations.Nullable; +import com.intellij.psi.impl.DebugUtil; +import org.jetbrains.annotations.NotNull; import java.util.Collection; /** * @author peter */ -public class LookupOffsets { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupOffsets"); +public class LookupOffsets extends DocumentAdapter { private String myAdditionalPrefix = ""; private String myInitialPrefix; private boolean myStableStart; - private RangeMarker myLookupStartMarker; + private String myStartDisposeTrace; + @NotNull private RangeMarker myLookupStartMarker; private int myRemovedPrefix; private final RangeMarker myLookupOriginalStartMarker; private final Editor myEditor; @@ -43,23 +42,22 @@ public class LookupOffsets { public LookupOffsets(Editor editor) { myEditor = editor; int caret = getPivotOffset(); - myLookupOriginalStartMarker = editor.getDocument().createRangeMarker(caret, caret); - myLookupOriginalStartMarker.setGreedyToLeft(true); - updateLookupStart(0); + myLookupOriginalStartMarker = createLeftGreedyMarker(caret); + myLookupStartMarker = createLeftGreedyMarker(caret); + myEditor.getDocument().addDocumentListener(this); } - private void updateLookupStart(int minPrefixLength) { - int offset = getPivotOffset(); - int start = offset - minPrefixLength - myAdditionalPrefix.length() + myRemovedPrefix; - start = Math.max(Math.min(start, myEditor.getDocument().getTextLength()), 0); - if (myLookupStartMarker != null) { - if (myLookupStartMarker.isValid() && myLookupStartMarker.getStartOffset() == start && myLookupStartMarker.getEndOffset() == start) { - return; - } - myLookupStartMarker.dispose(); + @Override + public void documentChanged(DocumentEvent e) { + if (myStartDisposeTrace == null && !myLookupStartMarker.isValid()) { + myStartDisposeTrace = e + "\n" + DebugUtil.currentStackTrace(); } - myLookupStartMarker = myEditor.getDocument().createRangeMarker(start, start); - myLookupStartMarker.setGreedyToLeft(true); + } + + private RangeMarker createLeftGreedyMarker(int start) { + RangeMarker marker = myEditor.getDocument().createRangeMarker(start, start); + marker.setGreedyToLeft(true); + return marker; } private int getPivotOffset() { @@ -101,15 +99,20 @@ public class LookupOffsets { } } - updateLookupStart(minPrefixLength); + int start = getPivotOffset() - minPrefixLength - myAdditionalPrefix.length() + myRemovedPrefix; + start = Math.max(Math.min(start, myEditor.getDocument().getTextLength()), 0); + if (myLookupStartMarker.isValid() && myLookupStartMarker.getStartOffset() == start && myLookupStartMarker.getEndOffset() == start) { + return; + } + + myLookupStartMarker.dispose(); + myLookupStartMarker = createLeftGreedyMarker(start); + myStartDisposeTrace = null; } int getLookupStart(String disposeTrace) { - if (myLookupStartMarker == null) { - LOG.error("disposed: " + disposeTrace); - } if (!myLookupStartMarker.isValid()) { - LOG.error("invalid marker: " + disposeTrace); + throw new AssertionError("Invalid lookup start: " + disposeTrace + ";\n" + myStartDisposeTrace); } return myLookupStartMarker.getStartOffset(); } @@ -118,33 +121,14 @@ public class LookupOffsets { return myLookupOriginalStartMarker.isValid() ? myLookupOriginalStartMarker.getStartOffset() : -1; } - boolean performGuardedChange(Runnable change, @Nullable final String debug) { - if (myLookupStartMarker == null) { - throw new AssertionError("null start before"); - } + boolean performGuardedChange(Runnable change) { if (!myLookupStartMarker.isValid()) { - throw new AssertionError("invalid start"); - } - final Document document = myEditor.getDocument(); - RangeMarkerSpy spy = new RangeMarkerSpy(myLookupStartMarker) { - @Override - protected void invalidated(DocumentEvent e) { - LOG.info("Lookup start marker invalidated, say thanks to the " + e + - ", doc=" + document + - ", debug=" + debug); - } - }; - document.addDocumentListener(spy); - try { - change.run(); - } - finally { - document.removeDocumentListener(spy); + throw new AssertionError("Invalid start: " + myStartDisposeTrace); } + change.run(); return myLookupStartMarker.isValid(); } - void setInitialPrefix(String presentPrefix, boolean explicitlyInvoked) { if (myAdditionalPrefix.length() == 0 && myInitialPrefix == null && !explicitlyInvoked) { myInitialPrefix = presentPrefix; @@ -165,11 +149,9 @@ public class LookupOffsets { } } - public void disposeMarkers() { - if (myLookupStartMarker != null) { - myLookupStartMarker.dispose(); - myLookupStartMarker = null; - } + void disposeMarkers() { + myEditor.getDocument().removeDocumentListener(this); + myLookupStartMarker.dispose(); myLookupOriginalStartMarker.dispose(); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java b/platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java index 512bfa1b27fb..9678ef213f45 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java @@ -247,8 +247,13 @@ public class LiveTemplateBuilder { else { newVarName = varName; } - Variable var = - new Variable(newVarName, template.getExpressionStringAt(i), template.getDefaultValueStringAt(i), template.isAlwaysStopAt(i)); + Variable var = new Variable(newVarName, template.getExpressionStringAt(i), template.getDefaultValueStringAt(i), template.isAlwaysStopAt(i)); + if (mySegmentLimit >= 0 && myVariables.size() >= mySegmentLimit) { + if (mySegmentLimit > 0) { + LOGGER.warn("Template with more than " + mySegmentLimit + " segments had been build. Text: " + myText); + } + break; + } myVariables.add(var); myVarNames.add(newVarName); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixDescriptionPanel.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixDescriptionPanel.java index 6032cbe63e66..c5c10ceefcb1 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixDescriptionPanel.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixDescriptionPanel.java @@ -42,6 +42,7 @@ class PostfixDescriptionPanel implements Disposable { private JEditorPane myDescriptionBrowser; public PostfixDescriptionPanel() { + myDescriptionBrowser.setMargin(new Insets(5, 5, 5, 5)); initializeExamplePanel(myAfterPanel); initializeExamplePanel(myBeforePanel); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixTemplatesConfigurable.form b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixTemplatesConfigurable.form index 9af302be1d97..2417e0b689d2 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixTemplatesConfigurable.form +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixTemplatesConfigurable.form @@ -13,7 +13,7 @@ <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="1" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/> </constraints> <properties> - <text value="&Enable postfix templates"/> + <text value="&Enable postfix completion"/> </properties> </component> <component id="9f784" class="com.intellij.ui.components.JBCheckBox" binding="myCompletionEnabledCheckbox"> diff --git a/platform/lang-impl/src/com/intellij/codeInspection/InspectionApplication.java b/platform/lang-impl/src/com/intellij/codeInspection/InspectionApplication.java index 391232cd2857..86cbe6f1d6b7 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/InspectionApplication.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/InspectionApplication.java @@ -105,7 +105,7 @@ public class InspectionApplication { LOG.error(e); } finally { - if (myErrorCodeRequired) application.exit(true); + if (myErrorCodeRequired) application.exit(true, true); } } }); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/Descriptor.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/Descriptor.java index c3d4e158c070..b27befd36b00 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/Descriptor.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/Descriptor.java @@ -40,6 +40,7 @@ public class Descriptor { private final InspectionToolWrapper myToolWrapper; private final HighlightDisplayLevel myLevel; private boolean myEnabled = false; + @Nullable private final NamedScope myScope; private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.Descriptor"); private final ScopeToolState myState; @@ -99,6 +100,13 @@ public class Descriptor { return myConfig; } + public void loadConfig() { + if (myConfig == null) { + InspectionToolWrapper toolWrapper = getToolWrapper(); + myConfig = createConfigElement(toolWrapper); + } + } + @NotNull public InspectionToolWrapper getToolWrapper() { return myToolWrapper; @@ -106,11 +114,7 @@ public class Descriptor { @Nullable public String loadDescription() { - if (myConfig == null) { - InspectionToolWrapper toolWrapper = getToolWrapper(); - myConfig = createConfigElement(toolWrapper); - } - + loadConfig(); return myToolWrapper.loadDescription(); } @@ -133,6 +137,7 @@ public class Descriptor { return myGroup; } + @Nullable public NamedScope getScope() { return myScope; } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/SeverityEditorDialog.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/SeverityEditorDialog.java index b5333e8bd63e..527235b5166c 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/SeverityEditorDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/SeverityEditorDialog.java @@ -301,6 +301,9 @@ public class SeverityEditorDialog extends DialogWrapper { } private void reset(SeverityBasedTextAttributes info) { + if (info == null) { + return; + } final MyTextAttributesDescription description = new MyTextAttributesDescription(info.getType().toString(), null, info.getAttributes(), info.getType().getAttributesKey()); @NonNls Element textAttributes = new Element("temp"); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/VisibleTreeState.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/VisibleTreeState.java index 8f81c0589c92..d81fbf692812 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/VisibleTreeState.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/VisibleTreeState.java @@ -16,7 +16,7 @@ package com.intellij.codeInspection.ex; -import com.intellij.profile.codeInspection.ui.InspectionConfigTreeNode; +import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionConfigTreeNode; import com.intellij.psi.search.scope.packageSet.NamedScope; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.ui.tree.TreeUtil; @@ -106,7 +106,7 @@ public class VisibleTreeState{ } private static State getState(InspectionConfigTreeNode node) { - Descriptor descriptor = node.getDescriptor(); + Descriptor descriptor = node.getDefaultDescriptor(); final State expandedNode; if (descriptor != null) { expandedNode = new State(descriptor); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewComparator.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewComparator.java index cab3fbeeedda..b36ab755a9ac 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewComparator.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewComparator.java @@ -36,7 +36,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.profile.codeInspection.ui.InspectionsConfigTreeComparator; +import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeComparator; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java index d50bddc322e8..d1c3c3a2d7f1 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java @@ -31,7 +31,7 @@ import com.intellij.codeInspection.reference.RefEntity; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.FileStatus; -import com.intellij.profile.codeInspection.ui.InspectionsConfigTreeComparator; +import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeComparator; import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.JBColor; import com.intellij.ui.SimpleTextAttributes; diff --git a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleViewImpl.java b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleViewImpl.java index bb738098f3b8..8a4b1f85125a 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleViewImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleViewImpl.java @@ -30,7 +30,7 @@ import javax.swing.*; */ public class LanguageConsoleViewImpl extends ConsoleViewImpl implements LanguageConsoleView { @NotNull - protected LanguageConsoleImpl myConsole; + protected final LanguageConsoleImpl myConsole; public LanguageConsoleViewImpl(Project project, String title, Language language) { this(new LanguageConsoleImpl(project, title, language)); diff --git a/platform/lang-impl/src/com/intellij/find/EditorSearchComponent.java b/platform/lang-impl/src/com/intellij/find/EditorSearchComponent.java index 8c5864c6569b..8a36638d993c 100644 --- a/platform/lang-impl/src/com/intellij/find/EditorSearchComponent.java +++ b/platform/lang-impl/src/com/intellij/find/EditorSearchComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -48,6 +48,7 @@ import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -68,11 +69,12 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data private final Project myProject; private ActionToolbar myActionsToolbar; - + @NotNull public Editor getEditor() { return myEditor; } + @NotNull private final Editor myEditor; public JTextComponent getSearchField() { @@ -173,7 +175,7 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data return findModel; } - public EditorSearchComponent(Editor editor, Project project) { + public EditorSearchComponent(@NotNull Editor editor, Project project) { this(editor, project, createDefaultFindModel(project, editor)); } @@ -225,7 +227,7 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data } @Override - public void cursorMoved(boolean toChangeSelection) { + public void cursorMoved() { updateExcludeStatus(); } @@ -233,10 +235,7 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data public void updateFinished() { } - @Override - public void editorChanged(SearchResults sr, Editor oldEditor) { } - - public EditorSearchComponent(final Editor editor, final Project project, FindModel findModel) { + public EditorSearchComponent(@NotNull final Editor editor, final Project project, FindModel findModel) { myFindModel = findModel; myProject = project; @@ -367,6 +366,9 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data actionGroup.add(new ShowHistoryAction(mySearchFieldGetter, this)); actionGroup.add(new PrevOccurrenceAction(this, mySearchFieldGetter)); actionGroup.add(new NextOccurrenceAction(this, mySearchFieldGetter)); + actionGroup.add(new AddOccurrenceAction(this)); + actionGroup.add(new RemoveOccurrenceAction(this)); + actionGroup.add(new SelectAllAction(this)); actionGroup.add(new FindAllAction(this)); actionGroup.add(new ToggleMultiline(this)); actionGroup.add(new ToggleMatchCase(this)); @@ -803,10 +805,6 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data } public void close() { - if (myEditor.getSelectionModel().hasSelection()) { - myEditor.getCaretModel().moveToOffset(myEditor.getSelectionModel().getSelectionStart()); - myEditor.getSelectionModel().removeSelection(); - } IdeFocusManager.getInstance(myProject).requestFocus(myEditor.getContentComponent(), false); myLivePreviewController.dispose(); @@ -937,6 +935,18 @@ public class EditorSearchComponent extends EditorHeaderComponent implements Data return insets; } + public void selectAllOccurrences() { + FindUtil.selectSearchResultsInEditor(myEditor, mySearchResults.getOccurrences().iterator(), -1); + } + + public void removeOccurrence() { + mySearchResults.prevOccurrence(true); + } + + public void addNextOccurrence() { + mySearchResults.nextOccurrence(true); + } + private static class MyUndoProvider extends TextComponentUndoProvider { private boolean myEnabled = true; public MyUndoProvider(JTextComponent textComponent) { diff --git a/platform/lang-impl/src/com/intellij/find/FindUtil.java b/platform/lang-impl/src/com/intellij/find/FindUtil.java index 0728ea07e2b3..edc906481398 100644 --- a/platform/lang-impl/src/com/intellij/find/FindUtil.java +++ b/platform/lang-impl/src/com/intellij/find/FindUtil.java @@ -31,6 +31,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.actionSystem.EditorActionManager; +import com.intellij.openapi.editor.actions.EditorActionUtil; import com.intellij.openapi.editor.actions.IncrementalFindAction; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; @@ -70,10 +71,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; public class FindUtil { private static final Key<Direction> KEY = Key.create("FindUtil.KEY"); @@ -385,7 +383,7 @@ public class FindUtil { } else { editor.putUserData(KEY, null); - offset = editor.getCaretModel().getOffset(); + offset = model.isGlobal() && model.isForward() ? editor.getSelectionModel().getSelectionEnd() : editor.getCaretModel().getOffset(); if (!model.isForward() && offset > 0) { offset--; } @@ -714,7 +712,18 @@ public class FindUtil { final ScrollType scrollType = forward ? ScrollType.CENTER_DOWN : ScrollType.CENTER_UP; if (model.isGlobal()) { - caretModel.moveToOffset(result.getEndOffset()); + int targetCaretPosition = result.getEndOffset(); + if (selection.getSelectionEnd() - selection.getSelectionStart() == result.getLength()) { + // keeping caret's position relative to selection + // use case: FindNext is used after SelectNextOccurrence action + targetCaretPosition = caretModel.getOffset() - selection.getSelectionStart() + result.getStartOffset(); + } + if (caretModel.getCaretAt(editor.offsetToVisualPosition(targetCaretPosition)) != null) { + // if there's a different caret at target position, don't move current caret/selection + // use case: FindNext is used after SelectNextOccurrence action + return result; + } + caretModel.moveToOffset(targetCaretPosition); selection.removeSelection(); scrollingModel.scrollToCaret(scrollType); scrollingModel.runActionOnScrollingFinished( @@ -829,17 +838,12 @@ public class FindUtil { editor.getCaretModel().addCaretListener(listener); } JComponent component = HintUtil.createInformationLabel(JDOMUtil.escapeText(message, false, false)); - - if (ApplicationManager.getApplication().isUnitTestMode()) { - LivePreview.processNotFound(); - } else { - final LightweightHint hint = new LightweightHint(component); - HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, position, - HintManager.HIDE_BY_ANY_KEY | - HintManager.HIDE_BY_TEXT_CHANGE | - HintManager.HIDE_BY_SCROLLING, - 0, false); - } + final LightweightHint hint = new LightweightHint(component); + HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, position, + HintManager.HIDE_BY_ANY_KEY | + HintManager.HIDE_BY_TEXT_CHANGE | + HintManager.HIDE_BY_SCROLLING, + 0, false); } public static TextRange doReplace(final Project project, @@ -954,4 +958,68 @@ public class FindUtil { }); return view; } + + /** + * Creates a selection in editor per each search result. Existing carets and selections in editor are discarded. + * + * @param caretShiftFromSelectionStart if non-negative, defines caret position relative to selection start, for each created selection. + * if negative, carets will be positioned at selection ends + */ + public static void selectSearchResultsInEditor(@NotNull Editor editor, + @NotNull Iterator<FindResult> resultIterator, + int caretShiftFromSelectionStart) { + if (!editor.getCaretModel().supportsMultipleCarets()) { + return; + } + ArrayList<CaretState> caretStates = new ArrayList<CaretState>(); + while (resultIterator.hasNext()) { + FindResult findResult = resultIterator.next(); + int caretOffset = getCaretPosition(findResult, caretShiftFromSelectionStart); + int selectionStartOffset = findResult.getStartOffset(); + int selectionEndOffset = findResult.getEndOffset(); + EditorActionUtil.makePositionVisible(editor, caretOffset); + EditorActionUtil.makePositionVisible(editor, selectionStartOffset); + EditorActionUtil.makePositionVisible(editor, selectionEndOffset); + caretStates.add(new CaretState(editor.offsetToLogicalPosition(caretOffset), + editor.offsetToLogicalPosition(selectionStartOffset), + editor.offsetToLogicalPosition(selectionEndOffset))); + } + if (caretStates.isEmpty()) { + return; + } + editor.getCaretModel().setCaretsAndSelections(caretStates); + } + + /** + * Attempts to add a new caret to editor, with selection corresponding to given search result. + * + * @param caretShiftFromSelectionStart if non-negative, defines caret position relative to selection start, for each created selection. + * if negative, caret will be positioned at selection end + * @return <code>true</code> if caret was added successfully, <code>false</code> if it cannot be done, e.g. because a caret already + * exists at target position + */ + public static boolean selectSearchResultInEditor(@NotNull Editor editor, @NotNull FindResult result, int caretShiftFromSelectionStart) { + if (!editor.getCaretModel().supportsMultipleCarets()) { + return false; + } + int caretOffset = getCaretPosition(result, caretShiftFromSelectionStart); + EditorActionUtil.makePositionVisible(editor, caretOffset); + Caret newCaret = editor.getCaretModel().addCaret(editor.offsetToVisualPosition(caretOffset)); + if (newCaret == null) { + return false; + } + else { + int selectionStartOffset = result.getStartOffset(); + int selectionEndOffset = result.getEndOffset(); + EditorActionUtil.makePositionVisible(editor, selectionStartOffset); + EditorActionUtil.makePositionVisible(editor, selectionEndOffset); + newCaret.setSelection(selectionStartOffset, selectionEndOffset); + return true; + } + } + + private static int getCaretPosition(FindResult findResult, int caretShiftFromSelectionStart) { + return caretShiftFromSelectionStart < 0 + ? findResult.getEndOffset() : Math.min(findResult.getStartOffset() + caretShiftFromSelectionStart, findResult.getEndOffset()); + } } diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/AddOccurrenceAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/AddOccurrenceAction.java new file mode 100644 index 000000000000..9064113d9c9b --- /dev/null +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/AddOccurrenceAction.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2014 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.find.editorHeaderActions; + +import com.intellij.find.EditorSearchComponent; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.project.DumbAware; + +import java.util.Arrays; + +public class AddOccurrenceAction extends EditorHeaderAction implements DumbAware { + public AddOccurrenceAction(EditorSearchComponent editorSearchComponent) { + super(editorSearchComponent); + + copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_SELECT_NEXT_OCCURENCE)); + getTemplatePresentation().setIcon(AllIcons.General.Add); + + registerShortcutsForComponent(Arrays.asList(getShortcutSet().getShortcuts()), editorSearchComponent.getSearchField()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + getEditorSearchComponent().addNextOccurrence(); + } + + @Override + public void update(AnActionEvent e) { + boolean isFind = !getEditorSearchComponent().getFindModel().isReplaceState(); + boolean hasMatches = getEditorSearchComponent().hasMatches(); + e.getPresentation().setVisible(isFind); + e.getPresentation().setEnabled(isFind && hasMatches); + }} diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderAction.java index 6cdba4b1e6eb..15348730a41d 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderAction.java @@ -1,21 +1,34 @@ +/* + * Copyright 2000-2014 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.find.editorHeaderActions; import com.intellij.find.EditorSearchComponent; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.CustomShortcutSet; -import com.intellij.openapi.actionSystem.KeyboardShortcut; import com.intellij.openapi.actionSystem.Shortcut; import javax.swing.*; -import java.util.ArrayList; import java.util.List; public abstract class EditorHeaderAction extends AnAction { private final EditorSearchComponent myEditorSearchComponent; - protected static void registerShortcutsForComponent(List<Shortcut> shortcuts, JComponent component, AnAction a) { - a.registerCustomShortcutSet( + protected void registerShortcutsForComponent(List<Shortcut> shortcuts, JComponent component) { + registerCustomShortcutSet( new CustomShortcutSet(shortcuts.toArray(new Shortcut[shortcuts.size()])), component); } diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/NextOccurrenceAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/NextOccurrenceAction.java index 1eb933cf2d03..0e6e67168521 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/NextOccurrenceAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/NextOccurrenceAction.java @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2014 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.find.editorHeaderActions; import com.intellij.find.EditorSearchComponent; @@ -35,7 +50,7 @@ public class NextOccurrenceAction extends EditorHeaderAction implements DumbAwar shortcuts.add(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), null)); } - registerShortcutsForComponent(shortcuts, editorTextField.get(), this); + registerShortcutsForComponent(shortcuts, editorTextField.get()); } @Override diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/PrevOccurrenceAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/PrevOccurrenceAction.java index f5a2d3911b43..586f90a835d1 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/PrevOccurrenceAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/PrevOccurrenceAction.java @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2014 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.find.editorHeaderActions; import com.intellij.find.EditorSearchComponent; @@ -34,7 +49,7 @@ public class PrevOccurrenceAction extends EditorHeaderAction implements DumbAwar shortcuts.add(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK), null)); } - registerShortcutsForComponent(shortcuts, editorTextField.get(), this); + registerShortcutsForComponent(shortcuts, editorTextField.get()); } @Override diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RemoveOccurrenceAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RemoveOccurrenceAction.java new file mode 100644 index 000000000000..b739f942991d --- /dev/null +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RemoveOccurrenceAction.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 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.find.editorHeaderActions; + +import com.intellij.find.EditorSearchComponent; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.project.DumbAware; + +import java.util.Arrays; + +public class RemoveOccurrenceAction extends EditorHeaderAction implements DumbAware { + public RemoveOccurrenceAction(EditorSearchComponent editorSearchComponent) { + super(editorSearchComponent); + + copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_UNSELECT_PREVIOUS_OCCURENCE)); + getTemplatePresentation().setIcon(AllIcons.General.Remove); + + registerShortcutsForComponent(Arrays.asList(getShortcutSet().getShortcuts()), editorSearchComponent.getSearchField()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + getEditorSearchComponent().removeOccurrence(); + } + + @Override + public void update(AnActionEvent e) { + boolean isFind = !getEditorSearchComponent().getFindModel().isReplaceState(); + boolean hasMatches = getEditorSearchComponent().hasMatches(); + e.getPresentation().setVisible(isFind); + e.getPresentation().setEnabled(isFind && hasMatches); + } +} diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RestorePreviousSettingsAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RestorePreviousSettingsAction.java index cad2be2ddc20..2607ebf0557b 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RestorePreviousSettingsAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/RestorePreviousSettingsAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -39,8 +39,7 @@ public class RestorePreviousSettingsAction extends EditorHeaderAction implements public RestorePreviousSettingsAction(EditorSearchComponent editorSearchComponent, JTextComponent textField) { super(editorSearchComponent); myTextField = textField; - registerShortcutsForComponent(Collections.<Shortcut>singletonList(SHORTCUT), - textField, this); + registerShortcutsForComponent(Collections.<Shortcut>singletonList(SHORTCUT), textField); } @Override diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/SelectAllAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/SelectAllAction.java new file mode 100644 index 000000000000..0e52566f6fbc --- /dev/null +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/SelectAllAction.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2014 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.find.editorHeaderActions; + +import com.intellij.find.EditorSearchComponent; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.project.DumbAware; +import com.intellij.util.containers.ContainerUtil; + +import java.util.ArrayList; +import java.util.List; + +public class SelectAllAction extends EditorHeaderAction implements DumbAware { + public SelectAllAction(EditorSearchComponent editorSearchComponent) { + super(editorSearchComponent); + + copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_SELECT_ALL_OCCURRENCES)); + getTemplatePresentation().setIcon(AllIcons.Actions.Selectall); + + List<Shortcut> shortcuts = new ArrayList<Shortcut>(); + ContainerUtil.addAll(shortcuts, getShortcutSet().getShortcuts()); + ContainerUtil.addAll(shortcuts, CommonShortcuts.ALT_ENTER.getShortcuts()); + registerShortcutsForComponent(shortcuts, editorSearchComponent.getSearchField()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + getEditorSearchComponent().selectAllOccurrences(); + getEditorSearchComponent().close(); + } + + @Override + public void update(AnActionEvent e) { + boolean isFind = !getEditorSearchComponent().getFindModel().isReplaceState(); + boolean hasMatches = getEditorSearchComponent().hasMatches(); + e.getPresentation().setVisible(isFind); + e.getPresentation().setEnabled(isFind && hasMatches); + } +} diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java index 23f14c5eb0b4..e4570b7379bf 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java @@ -47,6 +47,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.SearchScope; import com.intellij.ui.content.Content; @@ -54,6 +55,7 @@ import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewManager; import com.intellij.usages.ConfigurableUsageTarget; import com.intellij.usages.FindUsagesProcessPresentation; +import com.intellij.usages.UsageView; import com.intellij.usages.UsageViewPresentation; import com.intellij.util.Function; import com.intellij.util.PatternUtil; @@ -324,7 +326,7 @@ public class FindInProjectUtil { return processPresentation; } - public static class StringUsageTarget implements ConfigurableUsageTarget, ItemPresentation { + public static class StringUsageTarget implements ConfigurableUsageTarget, ItemPresentation, TypeSafeDataProvider { @NotNull protected final Project myProject; @NotNull protected final FindModel myFindModel; @@ -423,5 +425,12 @@ public class FindInProjectUtil { public KeyboardShortcut getShortcut() { return ActionManager.getInstance().getKeyboardShortcut("FindInPath"); } + + @Override + public void calcData(DataKey key, DataSink sink) { + if (key == UsageView.USAGE_SCOPE) { + sink.put(UsageView.USAGE_SCOPE, GlobalSearchScope.allScope(myProject)); + } + } } } diff --git a/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreview.java b/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreview.java index c20b5925721d..4ba97e0151e5 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreview.java +++ b/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreview.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -121,7 +121,7 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search } highlightUsages(); - updateCursorHighlighting(false); + updateCursorHighlighting(); if (myInSmartUpdate) { clearUnusedHightlighters(); myInSmartUpdate = false; @@ -218,9 +218,9 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search } @Override - public void cursorMoved(boolean toChangeSelection) { + public void cursorMoved() { updateInSelectionHighlighters(); - updateCursorHighlighting(toChangeSelection); + updateCursorHighlighting(); } @Override @@ -228,14 +228,7 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search dumpState(); } - @Override - public void editorChanged(SearchResults sr, Editor oldEditor) { - removeFromEditor(); - oldEditor.getDocument().removeDocumentListener(this); - mySearchResults.getEditor().getDocument().addDocumentListener(this); - } - - private void updateCursorHighlighting(boolean scroll) { + private void updateCursorHighlighting() { hideBalloon(); if (myCursorHighlighter != null) { @@ -245,7 +238,6 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search final FindResult cursor = mySearchResults.getCursor(); Editor editor = mySearchResults.getEditor(); - SelectionModel selection = editor.getSelectionModel(); if (cursor != null) { Set<RangeHighlighter> dummy = new HashSet<RangeHighlighter>(); highlightRange(cursor, new TextAttributes(null, null, Color.BLACK, EffectType.ROUNDED_BOX, 0), dummy); @@ -253,33 +245,6 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search myCursorHighlighter = dummy.iterator().next(); } - if (scroll) { - if (mySearchResults.getFindModel().isGlobal()) { - FoldingModel foldingModel = editor.getFoldingModel(); - final FoldRegion[] allRegions = editor.getFoldingModel().getAllFoldRegions(); - - foldingModel.runBatchFoldingOperation(new Runnable() { - @Override - public void run() { - for (FoldRegion region : allRegions) { - if (!region.isValid()) continue; - if (cursor.intersects(TextRange.create(region))) { - region.setExpanded(true); - } - } - } - }); - selection.setSelection(cursor.getStartOffset(), cursor.getEndOffset()); - - editor.getCaretModel().moveToOffset(cursor.getEndOffset()); - editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); - } else { - if (!SearchResults.insideVisibleArea(editor, cursor)) { - LogicalPosition pos = editor.offsetToLogicalPosition(cursor.getStartOffset()); - editor.getScrollingModel().scrollTo(pos, ScrollType.CENTER); - } - } - } editor.getScrollingModel().runActionOnScrollingFinished(new Runnable() { @Override public void run() { @@ -496,6 +461,8 @@ public class LivePreview extends DocumentAdapter implements SearchResults.Search new Processor<RangeHighlighterEx>() { @Override public boolean process(RangeHighlighterEx highlighter) { + if (!highlighter.getEditorFilter().avaliableIn(mySearchResults.getEditor())) return true; + TextAttributes textAttributes = highlighter.getTextAttributes(); if (highlighter.getUserData(SEARCH_MARKER) != null && diff --git a/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewController.java b/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewController.java index 8368f1b2f2aa..94a6c3f9c1bc 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewController.java +++ b/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewController.java @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2014 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.find.impl.livePreview; import com.intellij.find.*; @@ -83,9 +98,9 @@ public class LivePreviewController implements LivePreview.Delegate, FindUtil.Rep public void moveCursor(SearchResults.Direction direction) { if (direction == SearchResults.Direction.UP) { - mySearchResults.prevOccurrence(); + mySearchResults.prevOccurrence(false); } else { - mySearchResults.nextOccurrence(); + mySearchResults.nextOccurrence(false); } } diff --git a/platform/lang-impl/src/com/intellij/find/impl/livePreview/SearchResults.java b/platform/lang-impl/src/com/intellij/find/impl/livePreview/SearchResults.java index dff4989bb2f6..39b17dd2acc8 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/livePreview/SearchResults.java +++ b/platform/lang-impl/src/com/intellij/find/impl/livePreview/SearchResults.java @@ -21,9 +21,7 @@ import com.intellij.find.FindModel; import com.intellij.find.FindResult; import com.intellij.find.FindUtil; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.fileEditor.FileDocumentManager; @@ -38,6 +36,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import com.intellij.util.containers.Stack; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -70,6 +69,7 @@ public class SearchResults implements DocumentListener { private @Nullable FindResult myCursor; + @NotNull private List<FindResult> myOccurrences = new ArrayList<FindResult>(); private final Set<RangeMarker> myExcluded = new HashSet<RangeMarker>(); @@ -90,6 +90,8 @@ public class SearchResults implements DocumentListener { private final Stack<Pair<FindModel, FindResult>> myCursorPositions = new Stack<Pair<FindModel, FindResult>>(); + private final SelectionManager mySelectionManager = new SelectionManager(this); + public SearchResults(Editor editor, Project project) { myEditor = editor; myProject = project; @@ -127,9 +129,8 @@ public class SearchResults implements DocumentListener { public void exclude(FindResult occurrence) { boolean include = false; - final TextRange r = occurrence; for (RangeMarker rangeMarker : myExcluded) { - if (TextRange.areSegmentsEqual(rangeMarker, r)) { + if (TextRange.areSegmentsEqual(rangeMarker, occurrence)) { myExcluded.remove(rangeMarker); rangeMarker.dispose(); include = true; @@ -137,7 +138,7 @@ public class SearchResults implements DocumentListener { } } if (!include) { - myExcluded.add(myEditor.getDocument().createRangeMarker(r.getStartOffset(), r.getEndOffset(), true)); + myExcluded.add(myEditor.getDocument().createRangeMarker(occurrence.getStartOffset(), occurrence.getEndOffset(), true)); } notifyChanged(); } @@ -149,8 +150,7 @@ public class SearchResults implements DocumentListener { public interface SearchResultsListener { void searchResultsUpdated(SearchResults sr); - void editorChanged(SearchResults sr, Editor oldEditor); - void cursorMoved(boolean toChangeSelection); + void cursorMoved(); void updateFinished(); } @@ -175,6 +175,7 @@ public class SearchResults implements DocumentListener { return myCursor; } + @NotNull public List<FindResult> getOccurrences() { return myOccurrences; } @@ -184,19 +185,7 @@ public class SearchResults implements DocumentListener { return myProject; } - public synchronized void setEditor(Editor editor) { - Editor oldOne = myEditor; - myEditor = editor; - notifyEditorChanged(oldOne); - } - - private void notifyEditorChanged(Editor oldOne) { - for (SearchResultsListener listener : myListeners) { - listener.editorChanged(this, oldOne); - } - } - - public synchronized Editor getEditor() { + public Editor getEditor() { return myEditor; } @@ -334,7 +323,7 @@ public class SearchResults implements DocumentListener { myEditor.getDocument().removeDocumentListener(this); } - private void searchCompleted(List<FindResult> occurrences, Editor editor, @Nullable FindModel findModel, + private void searchCompleted(@NotNull List<FindResult> occurrences, Editor editor, @Nullable FindModel findModel, boolean toChangeSelection, @Nullable TextRange next, int stamp) { if (stamp < myLastUpdatedStamp){ return; @@ -344,7 +333,7 @@ public class SearchResults implements DocumentListener { return; } myOccurrences = occurrences; - final TextRange oldCursorRange = myCursor != null ? myCursor : null; + final TextRange oldCursorRange = myCursor; Collections.sort(myOccurrences, new Comparator<FindResult>() { @Override public int compare(FindResult findResult, FindResult findResult1) { @@ -357,7 +346,10 @@ public class SearchResults implements DocumentListener { updateExcluded(); notifyChanged(); if (oldCursorRange == null || myCursor == null || !myCursor.equals(oldCursorRange)) { - notifyCursorMoved(toChangeSelection); + if (toChangeSelection) { + mySelectionManager.updateSelection(true, true); + } + notifyCursorMoved(); } dumpIfNeeded(); } @@ -389,7 +381,7 @@ public class SearchResults implements DocumentListener { myCursor = firstOccurrenceAfterOffset(oldCursorRange.getEndOffset()); } else { if (justReplaced) { - nextOccurrence(false, next, false, justReplaced); + nextOccurrence(false, next, false, true, false); } else { FindResult afterCaret = oldCursorRange == null ? firstOccurrenceAtOrAfterCaret() : firstOccurrenceAfterCaret(); if (afterCaret != null) { @@ -405,7 +397,7 @@ public class SearchResults implements DocumentListener { } } if (!justReplaced && myCursor == null && hasMatches()) { - nextOccurrence(true, oldCursorRange, false, false); + nextOccurrence(true, oldCursorRange, false, false, false); } if (toPush && myCursor != null){ push(); @@ -445,6 +437,13 @@ public class SearchResults implements DocumentListener { return occurrence; } } + int selectionStartOffset = getEditor().getSelectionModel().getSelectionStart(); + int selectionEndOffset = getEditor().getSelectionModel().getSelectionEnd(); + for (FindResult occurrence : myOccurrences) { + if (selectionEndOffset >= occurrence.getEndOffset() && selectionStartOffset <= occurrence.getStartOffset()) { + return occurrence; + } + } return firstOccurrenceAfterCaret(); } @@ -462,25 +461,6 @@ public class SearchResults implements DocumentListener { } @Nullable - private FindResult firstVisibleOccurrence() { - int offset = Integer.MAX_VALUE; - FindResult firstOccurrence = null; - FindResult firstVisibleOccurrence = null; - for (FindResult o : getOccurrences()) { - if (insideVisibleArea(myEditor, o)) { - if (firstVisibleOccurrence == null || o.getStartOffset() < firstVisibleOccurrence.getStartOffset()) { - firstVisibleOccurrence = o; - } - } - if (o.getStartOffset() < offset) { - offset = o.getStartOffset(); - firstOccurrence = o; - } - } - return firstVisibleOccurrence != null ? firstVisibleOccurrence : firstOccurrence; - } - - @Nullable private FindResult firstOccurrenceBeforeCaret() { int offset = getEditor().getCaretModel().getOffset(); return firstOccurrenceBeforeOffset(offset); @@ -554,32 +534,45 @@ public class SearchResults implements DocumentListener { return null; } - public void prevOccurrence() { - FindResult next = null; - if (myFindModel == null) return; - boolean processFromTheBeginning = false; - if (myNotFoundState) { - myNotFoundState = false; - processFromTheBeginning = true; - } - if (!myFindModel.isGlobal()) { - if (myCursor != null) { - next = prevOccurrence(myCursor); + public void prevOccurrence(boolean findSelected) { + if (findSelected) { + if (mySelectionManager.removeCurrentSelection()) { + myCursor = firstOccurrenceAtOrAfterCaret(); } - } else { - next = firstOccurrenceBeforeCaret(); - } - if (next == null) { - if (processFromTheBeginning) { - if (hasMatches()) { - next = getOccurrences().get(getOccurrences().size()-1); + else { + myCursor = null; + } + notifyCursorMoved(); + } + else { + FindResult next = null; + if (myFindModel == null) return; + boolean processFromTheBeginning = false; + if (myNotFoundState) { + myNotFoundState = false; + processFromTheBeginning = true; + } + if (!myFindModel.isGlobal()) { + if (myCursor != null) { + next = prevOccurrence(myCursor); + } + } + else { + next = firstOccurrenceBeforeCaret(); + } + if (next == null) { + if (processFromTheBeginning) { + if (hasMatches()) { + next = getOccurrences().get(getOccurrences().size() - 1); + } + } + else { + setNotFoundState(false); } - } else { - setNotFoundState(false); } - } - moveCursorTo(next); + moveCursorTo(next, false); + } push(); } @@ -587,13 +580,13 @@ public class SearchResults implements DocumentListener { myCursorPositions.push(Pair.create(myFindModel, myCursor)); } - public void nextOccurrence() { + public void nextOccurrence(boolean retainOldSelection) { if (myFindModel == null) return; - nextOccurrence(false, myCursor != null ? myCursor : null, true, false); + nextOccurrence(false, myCursor, true, false, retainOldSelection); push(); } - private void nextOccurrence(boolean processFromTheBeginning, TextRange cursor, boolean toNotify, boolean justReplaced) { + private void nextOccurrence(boolean processFromTheBeginning, TextRange cursor, boolean toNotify, boolean justReplaced, boolean retainOldSelection) { FindResult next; if (myNotFoundState) { myNotFoundState = false; @@ -614,22 +607,24 @@ public class SearchResults implements DocumentListener { } } if (toNotify) { - moveCursorTo(next); + moveCursorTo(next, retainOldSelection); } else { myCursor = next; } } - public void moveCursorTo(FindResult next) { - if (next != null) { + public void moveCursorTo(FindResult next, boolean retainOldSelection) { + if (next != null && !mySelectionManager.isSelected(next)) { + retainOldSelection &= (myCursor != null && mySelectionManager.isSelected(myCursor)); myCursor = next; - notifyCursorMoved(true); + mySelectionManager.updateSelection(!retainOldSelection, false); + notifyCursorMoved(); } } - private void notifyCursorMoved(boolean toChangeSelection) { + private void notifyCursorMoved() { for (SearchResultsListener listener : myListeners) { - listener.cursorMoved(toChangeSelection); + listener.cursorMoved(); } } } diff --git a/platform/lang-impl/src/com/intellij/find/impl/livePreview/SelectionManager.java b/platform/lang-impl/src/com/intellij/find/impl/livePreview/SelectionManager.java new file mode 100644 index 000000000000..50f37927c506 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/find/impl/livePreview/SelectionManager.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2014 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.find.impl.livePreview; + +import com.intellij.find.FindResult; +import com.intellij.find.FindUtil; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NotNull; + +public class SelectionManager { + @NotNull private final SearchResults mySearchResults; + + public SelectionManager(@NotNull SearchResults results) { + mySearchResults = results; + } + + public void updateSelection(boolean removePreviousSelection, boolean removeAllPreviousSelections) { + Editor editor = mySearchResults.getEditor(); + if (removeAllPreviousSelections) { + editor.getCaretModel().removeSecondaryCarets(); + } + final FindResult cursor = mySearchResults.getCursor(); + if (cursor == null) { + return; + } + if (mySearchResults.getFindModel().isGlobal()) { + if (removePreviousSelection || removeAllPreviousSelections) { + FoldingModel foldingModel = editor.getFoldingModel(); + final FoldRegion[] allRegions = editor.getFoldingModel().getAllFoldRegions(); + + foldingModel.runBatchFoldingOperation(new Runnable() { + @Override + public void run() { + for (FoldRegion region : allRegions) { + if (!region.isValid()) continue; + if (cursor.intersects(TextRange.create(region))) { + region.setExpanded(true); + } + } + } + }); + editor.getSelectionModel().setSelection(cursor.getStartOffset(), cursor.getEndOffset()); + editor.getCaretModel().moveToOffset(cursor.getEndOffset()); + } + else { + FindUtil.selectSearchResultInEditor(editor, cursor, -1); + } + editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); + } else { + if (!SearchResults.insideVisibleArea(editor, cursor)) { + LogicalPosition pos = editor.offsetToLogicalPosition(cursor.getStartOffset()); + editor.getScrollingModel().scrollTo(pos, ScrollType.CENTER); + } + } + } + + public boolean removeCurrentSelection() { + Editor editor = mySearchResults.getEditor(); + CaretModel caretModel = editor.getCaretModel(); + Caret primaryCaret = caretModel.getPrimaryCaret(); + if (caretModel.getCaretCount() > 1) { + caretModel.removeCaret(primaryCaret); + return true; + } + else { + primaryCaret.moveToOffset(primaryCaret.getSelectionStart()); + primaryCaret.removeSelection(); + return false; + } + } + + public boolean isSelected(@NotNull FindResult result) { + Editor editor = mySearchResults.getEditor(); + return editor.getCaretModel().getCaretAt(editor.offsetToVisualPosition(result.getEndOffset())) != null; + } +} diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java b/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java index 90f2387a157a..7d3c7c649fd2 100644 --- a/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java +++ b/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java @@ -16,6 +16,7 @@ package com.intellij.formatting; +import com.intellij.lang.ASTNode; import com.intellij.lang.Language; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; @@ -25,6 +26,8 @@ import com.intellij.openapi.editor.impl.BulkChangesMerger; import com.intellij.openapi.editor.impl.TextChangeImpl; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.util.ui.UIUtil; @@ -145,6 +148,7 @@ class FormatProcessor { private WhiteSpace myLastWhiteSpace; private boolean myDisposed; private CommonCodeStyleSettings.IndentOptions myJavaIndentOptions; + private final int myRightMargin; @NotNull private State myCurrentState; @@ -172,6 +176,23 @@ class FormatProcessor { mySettings = settings; myDocument = docModel.getDocument(); myCurrentState = new WrapBlocksState(rootBlock, docModel, affectedRanges, interestingOffset); + myRightMargin = getRightMargin(rootBlock); + } + + private int getRightMargin(Block rootBlock) { + if (rootBlock instanceof ASTBlock) { + ASTNode node = ((ASTBlock)rootBlock).getNode(); + if (node != null) { + PsiElement psiElement = node.getPsi(); + if (psiElement.isValid()) { + PsiFile psiFile = psiElement.getContainingFile(); + if (psiFile != null) { + return mySettings.getRightMargin(psiFile.getViewProvider().getBaseLanguage()); + } + } + } + } + return mySettings.RIGHT_MARGIN; } private LeafBlockWrapper getLastBlock() { @@ -813,7 +834,7 @@ class FormatProcessor { */ private boolean lineOver() { return !myCurrentBlock.containsLineFeeds() && - CoreFormatterUtil.getStartColumn(myCurrentBlock) + myCurrentBlock.getLength() > mySettings.RIGHT_MARGIN; + CoreFormatterUtil.getStartColumn(myCurrentBlock) + myCurrentBlock.getLength() > myRightMargin; } private void defineAlignOffset(final LeafBlockWrapper block) { diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoActionAction.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoActionAction.java index c939787fb632..e57d1eba05ef 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/GotoActionAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoActionAction.java @@ -20,9 +20,8 @@ import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.ide.DataManager; import com.intellij.ide.ui.search.OptionDescription; import com.intellij.ide.util.gotoByName.ChooseByNamePopup; -import com.intellij.ide.util.gotoByName.DefaultChooseByNameItemProvider; +import com.intellij.ide.util.gotoByName.GotoActionItemProvider; import com.intellij.ide.util.gotoByName.GotoActionModel; -import com.intellij.ide.util.gotoByName.MatchResult; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionUtil; import com.intellij.openapi.application.ApplicationManager; @@ -33,27 +32,11 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiFile; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; public class GotoActionAction extends GotoActionBase implements DumbAware { - public static final Comparator<MatchResult> ELEMENTS_COMPARATOR = new Comparator<MatchResult>() { - @Override - public int compare(@NotNull MatchResult o1, @NotNull MatchResult o2) { - if (o1.elementName.equals(GotoActionModel.INTENTIONS_KEY)) return -1; - if (o2.elementName.equals(GotoActionModel.INTENTIONS_KEY)) return 1; - - if (o1.elementName.equals(GotoActionModel.SETTINGS_KEY)) return 1; - if (o2.elementName.equals(GotoActionModel.SETTINGS_KEY)) return -1; - - return o1.elementName.compareToIgnoreCase(o2.elementName); - } - }; @Override public void gotoActionPerformed(final AnActionEvent e) { @@ -68,7 +51,7 @@ public class GotoActionAction extends GotoActionBase implements DumbAware { @Override public void elementChosen(ChooseByNamePopup popup, final Object element) { final String enteredText = popup.getEnteredText(); - openOptionOrPerformAction(element, enteredText, project, component, e); + openOptionOrPerformAction(((GotoActionModel.MatchedValue)element).value, enteredText, project, component, e); } }; @@ -79,12 +62,7 @@ public class GotoActionAction extends GotoActionBase implements DumbAware { private static ChooseByNamePopup createPopup(Project project, GotoActionModel model, String initialText, int initialIndex) { return ChooseByNamePopup.createPopup(project, model, - new DefaultChooseByNameItemProvider(null) { - @Override - protected void sortNamesList(@NotNull String namePattern, @NotNull List<MatchResult> namesList) { - Collections.sort(namesList, ELEMENTS_COMPARATOR); - } - }, + new GotoActionItemProvider(model), initialText, false, initialIndex); diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedFileAction.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedFileAction.java new file mode 100644 index 000000000000..6d734d52a584 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedFileAction.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2014 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.actions; + +import com.intellij.navigation.GotoRelatedItem; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @deprecated API compatibility. Utility methods will be moved to NavigationUtil + * @author gregsh + */ +public class GotoRelatedFileAction { + + /** + * @deprecated This method will be moved to NavigationUtil + */ + public static JBPopup createPopup(List<? extends GotoRelatedItem> items, final String title) { + return GotoRelatedSymbolAction.createPopup(items, title); + } + + /** + * @deprecated This method will be moved to NavigationUtil + */ + public static List<GotoRelatedItem> getItems(@NotNull PsiElement contextElement, @Nullable DataContext dataContext) { + return GotoRelatedSymbolAction.getItems(contextElement, dataContext); + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedSymbolAction.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedSymbolAction.java index f333c0f35db8..2870f806ab8a 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedSymbolAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedSymbolAction.java @@ -42,6 +42,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.awt.*; @@ -56,26 +57,23 @@ public class GotoRelatedSymbolAction extends AnAction { @Override public void update(AnActionEvent e) { - PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); - PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(e.getDataContext()); - e.getPresentation().setEnabled(element != null || file != null); + PsiElement element = getContextElement(e.getDataContext()); + e.getPresentation().setEnabled(element != null); } @Override public void actionPerformed(AnActionEvent e) { - DataContext dataContext = e.getDataContext(); - PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); - Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext()); - PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext); - if (element == null && file == null) return; + PsiElement element = getContextElement(e.getDataContext()); + if (element == null) return; - List<GotoRelatedItem> items = element == null? getItems(file, editor, dataContext) : getItems(element, dataContext); + List<GotoRelatedItem> items = getItems(element, e.getDataContext()); if (items.isEmpty()) return; + if (items.size() == 1 && items.get(0).getElement() != null) { items.get(0).navigate(); return; } - createPopup(items, "Choose Target").showInBestPositionFor(dataContext); + createPopup(items, "Choose Target").showInBestPositionFor(e.getDataContext()); } public static JBPopup createPopup(final List<? extends GotoRelatedItem> items, final String title) { @@ -268,8 +266,25 @@ public class GotoRelatedSymbolAction extends AnAction { return popup; } + @TestOnly @NotNull public static List<GotoRelatedItem> getItems(@NotNull PsiFile psiFile, @Nullable Editor editor, @Nullable DataContext dataContext) { + return getItems(getContextElement(psiFile, editor), dataContext); + } + + @Nullable + private static PsiElement getContextElement(@NotNull DataContext dataContext) { + PsiFile file = CommonDataKeys.PSI_FILE.getData(dataContext); + Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext); + if (file != null && editor != null) { + return getContextElement(file, editor); + } + return element; + } + + @NotNull + private static PsiElement getContextElement(@NotNull PsiFile psiFile, @Nullable Editor editor) { PsiElement contextElement = psiFile; if (editor != null) { PsiElement element = psiFile.findElementAt(editor.getCaretModel().getOffset()); @@ -277,7 +292,7 @@ public class GotoRelatedSymbolAction extends AnAction { contextElement = element; } } - return getItems(contextElement, dataContext); + return contextElement; } @NotNull diff --git a/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java b/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java index 996601065278..ae7ee4363247 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java @@ -56,10 +56,9 @@ import com.intellij.openapi.fileEditor.impl.EditorHistoryManager; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.keymap.MacKeymapUtil; +import com.intellij.openapi.keymap.impl.ModifierKeyDoubleClickHandler; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.SearchableConfigurable; -import com.intellij.openapi.options.ex.IdeConfigurablesGroup; -import com.intellij.openapi.options.ex.ProjectConfigurablesGroup; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.util.ProgressIndicatorBase; @@ -96,6 +95,7 @@ import com.intellij.ui.components.panels.NonOpaquePanel; import com.intellij.ui.popup.AbstractPopup; import com.intellij.ui.popup.PopupPositionManager; import com.intellij.util.*; +import com.intellij.util.text.Matcher; import com.intellij.util.ui.EmptyIcon; import com.intellij.util.ui.StatusText; import com.intellij.util.ui.UIUtil; @@ -138,9 +138,8 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA MySearchTextField myPopupField; private volatile GotoClassModel2 myClassModel; private volatile GotoFileModel myFileModel; - private volatile GotoActionModel myActionModel; + private volatile GotoActionItemProvider myActionProvider; private volatile GotoSymbolModel2 mySymbolsModel; - private volatile String[] myActions; private Component myFocusComponent; private JBPopup myPopup; private Map<String, String> myConfigurables = new HashMap<String, String>(); @@ -153,106 +152,25 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA private Component myContextComponent; private CalcThread myCalcThread; private static AtomicBoolean ourShiftIsPressed = new AtomicBoolean(false); - private final static Couple<AtomicBoolean> ourPressed = Couple.of(new AtomicBoolean(false), new AtomicBoolean(false)); - private final static Couple<AtomicBoolean> ourReleased = Couple.of(new AtomicBoolean(false), new AtomicBoolean(false)); - private static AtomicBoolean ourOtherKeyWasPressed = new AtomicBoolean(false); - private static AtomicLong ourLastTimePressed = new AtomicLong(0); private static AtomicBoolean showAll = new AtomicBoolean(false); private volatile ActionCallback myCurrentWorker = ActionCallback.DONE; private int myHistoryIndex = 0; boolean mySkipFocusGain = false; static { + ModifierKeyDoubleClickHandler.getInstance().registerAction(IdeActions.ACTION_SEARCH_EVERYWHERE, KeyEvent.VK_SHIFT, -1); + IdeEventQueue.getInstance().addPostprocessor(new IdeEventQueue.EventDispatcher() { @Override public boolean dispatch(AWTEvent event) { if (event instanceof KeyEvent) { - final KeyEvent keyEvent = (KeyEvent)event; - final int keyCode = keyEvent.getKeyCode(); - + final int keyCode = ((KeyEvent)event).getKeyCode(); if (keyCode == KeyEvent.VK_SHIFT) { ourShiftIsPressed.set(event.getID() == KeyEvent.KEY_PRESSED); - - if (keyEvent.isControlDown() || keyEvent.isAltDown() || keyEvent.isMetaDown()) { - resetState(); - return false; - } - if (ourOtherKeyWasPressed.get() && System.currentTimeMillis() - ourLastTimePressed.get() < 500) { - resetState(); - return false; - } - ourOtherKeyWasPressed.set(false); - if (ourPressed.first.get() && System.currentTimeMillis() - ourLastTimePressed.get() > 500) { - resetState(); - } - handleShift((KeyEvent)event); - return false; - } else { - ourLastTimePressed.set(System.currentTimeMillis()); - ourOtherKeyWasPressed.set(true); - if (keyCode == KeyEvent.VK_ESCAPE || keyCode == KeyEvent.VK_TAB) { - ourLastTimePressed.set(0); - } } - resetState(); } return false; } - - private void resetState() { - ourPressed.first.set(false); - ourPressed.second.set(false); - ourReleased.first.set(false); - ourReleased.second.set(false); - } - - private void handleShift(KeyEvent event) { - if (ourPressed.first.get() && System.currentTimeMillis() - ourLastTimePressed.get() > 300) { - resetState(); - return; - } - - if (event.getID() == KeyEvent.KEY_PRESSED) { - if (!ourPressed.first.get()) { - resetState(); - ourPressed.first.set(true); - ourLastTimePressed.set(System.currentTimeMillis()); - return; - } else { - if (ourPressed.first.get() && ourReleased.first.get()) { - ourPressed.second.set(true); - ourLastTimePressed.set(System.currentTimeMillis()); - return; - } - } - } else if (event.getID() == KeyEvent.KEY_RELEASED) { - if (ourPressed.first.get() && !ourReleased.first.get()) { - ourReleased.first.set(true); - ourLastTimePressed.set(System.currentTimeMillis()); - return; - } else if (ourPressed.first.get() && ourReleased.first.get() && ourPressed.second.get()) { - resetState(); - run(event); - return; - } - } - resetState(); - } - - private void run(KeyEvent event) { - final ActionManager actionManager = ActionManager.getInstance(); - final AnAction action = actionManager.getAction(IdeActions.ACTION_SEARCH_EVERYWHERE); - if (KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_SEARCH_EVERYWHERE).length > 0) { - return; - } - final AnActionEvent anActionEvent = new AnActionEvent(event, - DataManager.getInstance().getDataContext(IdeFocusManager.findInstance().getFocusOwner()), - ActionPlaces.MAIN_MENU, - action.getTemplatePresentation(), - actionManager, - 0); - action.actionPerformed(anActionEvent); - } }, null); } @@ -840,7 +758,9 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA if (split.length != 3 || text.equals(split[0])) { continue; } - history.add(new HistoryItem(split[0], split[1], split[2])); + if (!StringUtil.isEmpty(split[0])) { + history.add(new HistoryItem(split[0], split[1], split[2])); + } } } history.add(0, new HistoryItem(text, type == null ? null : type.name(), fqn)); @@ -1008,14 +928,18 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA Component cmp; PsiFile file = null; myLocationString = null; + String pattern = "*" + myPopupField.getText(); + Matcher matcher = NameUtil.buildMatcher(pattern, 0, true, true, pattern.toLowerCase().equals(pattern)); if (isMoreItem(index)) { cmp = More.get(isSelected); } else if (value instanceof VirtualFile && myProject != null && (((VirtualFile)value).isDirectory() || (file = PsiManager.getInstance(myProject).findFile((VirtualFile)value)) != null)) { + myFileRenderer.setPatternMatcher(matcher); cmp = myFileRenderer.getListCellRendererComponent(list, file == null ? value : file, index, isSelected, cellHasFocus); } else if (value instanceof PsiElement) { + myPsiRenderer.setPatternMatcher(matcher); cmp = myPsiRenderer.getListCellRendererComponent(list, value, index, isSelected, isSelected); } else { cmp = super.getListCellRendererComponent(list, value, index, isSelected, isSelected); @@ -1185,16 +1109,6 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA return text; } - private void schedulePopupUpdate() { - myUpdateAlarm.cancelAllRequests(); - myUpdateAlarm.addRequest(new Runnable() { - @Override - public void run() { - updatePopupBounds(); - } - }, 50); - } - private static boolean isActionValue(Object o) { return o instanceof GotoActionModel.ActionWrapper || o instanceof AnAction; } @@ -1353,21 +1267,16 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA private SearchResult getActionsOrSettings(final String pattern, final int max, final boolean actions) { final SearchResult result = new SearchResult(); final MinusculeMatcher matcher = new MinusculeMatcher("*" +pattern, NameUtil.MatchingCaseSensitivity.NONE); - if (myActions == null) { - if (myActionModel == null) { - myActionModel = createActionModel(); - } - myActions = myActionModel.getNames(true); + if (myActionProvider == null) { + myActionProvider = createActionProvider(); } - List<MatchResult> matches = collectResults(pattern, myActions, myActionModel); - - for (MatchResult o : matches) { - check(); - Object[] objects = myActionModel.getElementsByName(o.elementName, true, pattern); - for (Object object : objects) { + myActionProvider.filterElements(pattern, true, new Processor<GotoActionModel.MatchedValue>() { + @Override + public boolean process(GotoActionModel.MatchedValue matched) { check(); - if (myListModel.contains(object)) continue; + Object object = matched.value; + if (myListModel.contains(object)) return true; if (!actions && isSetting(object)) { if (matcher.matches(getSettingText((OptionDescription)object))) { @@ -1376,9 +1285,10 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA } else if (actions && !isToolWindowAction(object) && isActionValue(object)) { result.add(object); } - if (result.size() == max) return result; + return result.size() <= max; } - } + }); + return result; } @@ -1411,7 +1321,7 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA } private synchronized void buildFiles(final String pattern) { - final SearchResult files = getFiles(pattern, MAX_FILES); + final SearchResult files = getFiles(pattern, MAX_FILES, myFileChooseByName); check(); @@ -1434,7 +1344,7 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA private synchronized void buildSymbols(final String pattern) { - final SearchResult symbols = getSymbols(pattern, MAX_SYMBOLS); + final SearchResult symbols = getSymbols(pattern, MAX_SYMBOLS, mySymbolsChooseByName); check(); if (symbols.size() > 0) { @@ -1522,7 +1432,7 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA } check(); - final SearchResult classes = getClasses(pattern, showAll.get(), MAX_CLASSES); + final SearchResult classes = getClasses(pattern, showAll.get(), MAX_CLASSES, myClassChooseByName); check(); @@ -1544,10 +1454,10 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA } } - private SearchResult getSymbols(String pattern, final int max) { + private SearchResult getSymbols(String pattern, final int max, ChooseByNamePopup chooseByNamePopup) { final SearchResult symbols = new SearchResult(); final GlobalSearchScope scope = GlobalSearchScope.projectScope(project); - mySymbolsChooseByName.getProvider().filterElements(mySymbolsChooseByName, pattern, false, + chooseByNamePopup.getProvider().filterElements(chooseByNamePopup, pattern, false, myProgressIndicator, new Processor<Object>() { @Override public boolean process(Object o) { @@ -1567,9 +1477,12 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA return symbols; } - private SearchResult getClasses(String pattern, boolean includeLibs, final int max) { + private SearchResult getClasses(String pattern, boolean includeLibs, final int max, ChooseByNamePopup chooseByNamePopup) { final SearchResult classes = new SearchResult(); - myClassChooseByName.getProvider().filterElements(myClassChooseByName, pattern, includeLibs, + if (chooseByNamePopup == null) { + return classes; + } + chooseByNamePopup.getProvider().filterElements(chooseByNamePopup, pattern, includeLibs, myProgressIndicator, new Processor<Object>() { @Override public boolean process(Object o) { @@ -1584,15 +1497,18 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA } }); if (!includeLibs && classes.isEmpty()) { - return getClasses(pattern, true, max); + return getClasses(pattern, true, max, chooseByNamePopup); } return classes; } - private SearchResult getFiles(final String pattern, final int max) { + private SearchResult getFiles(final String pattern, final int max, ChooseByNamePopup chooseByNamePopup) { final SearchResult files = new SearchResult(); + if (chooseByNamePopup == null) { + return files; + } final GlobalSearchScope scope = GlobalSearchScope.projectScope(project); - myFileChooseByName.getProvider().filterElements(myFileChooseByName, pattern, true, + chooseByNamePopup.getProvider().filterElements(chooseByNamePopup, pattern, true, myProgressIndicator, new Processor<Object>() { @Override public boolean process(Object o) { @@ -1786,10 +1702,9 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA myClassChooseByName = ChooseByNamePopup.createPopup(project, myClassModel, (PsiElement)null); mySymbolsChooseByName = ChooseByNamePopup.createPopup(project, mySymbolsModel, (PsiElement)null); project.putUserData(ChooseByNamePopup.CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY, null); - myActionModel = createActionModel(); + myActionProvider = createActionProvider(); myConfigurables.clear(); - fillConfigurablesIds(null, new IdeConfigurablesGroup().getConfigurables()); - fillConfigurablesIds(null, new ProjectConfigurablesGroup(project).getConfigurables()); + fillConfigurablesIds(null, ShowSettingsUtilImpl.getConfigurables(project, true)); } } @@ -1797,14 +1712,16 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA buildRecentFiles(""); } - private GotoActionModel createActionModel() { - return new GotoActionModel(project, myFocusComponent, myEditor, myFile) { + private GotoActionItemProvider createActionProvider() { + GotoActionModel model = new GotoActionModel(project, myFocusComponent, myEditor, myFile) { @Override protected MatchMode actionMatches(String pattern, @NotNull AnAction anAction) { - return NameUtil.buildMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE) - .matches(anAction.getTemplatePresentation().getText()) ? MatchMode.NAME : MatchMode.NONE; + String text = anAction.getTemplatePresentation().getText(); + return text != null && NameUtil.buildMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE) + .matches(text) ? MatchMode.NAME : MatchMode.NONE; } }; + return new GotoActionItemProvider(model); } @SuppressWarnings("SSBasedInspection") @@ -1880,57 +1797,6 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA }); } - private List<MatchResult> collectResults(String pattern, String[] names, final ChooseByNameModel model) { - if (names == null) return Collections.emptyList(); - pattern = ChooseByNamePopup.getTransformedPattern(pattern, model); - pattern = DefaultChooseByNameItemProvider.getNamePattern(model, pattern); - if (model != myFileModel && model != myActionModel && !pattern.startsWith("*") && pattern.length() > 1) { - pattern = "*" + pattern; - } - final ArrayList<MatchResult> results = new ArrayList<MatchResult>(); - final String p = pattern; - MinusculeMatcher matcher = new MinusculeMatcher(pattern, NameUtil.MatchingCaseSensitivity.NONE) { - @Override - public boolean matches(@NotNull String name) { - if (!(model instanceof GotoActionModel) && p.indexOf(' ') > 0 && name.trim().indexOf(' ') < 0) { - return false; - } - return super.matches(name); - } - }; - MatchResult result; - - for (String name : names) { - check(); - result = null; - if (model instanceof CustomMatcherModel) { - try { - result = ((CustomMatcherModel)model).matches(name, pattern) ? new MatchResult(name, 0, true) : null; - if (result != null && model == myActionModel) { - ((CustomMatcherModel)model).matches(name, pattern); - } - } - catch (Exception ignore) { - } - } - else { - result = matcher.matches(name) ? new MatchResult(name, matcher.matchingDegree(name), matcher.isStartMatch(name)) : null; - } - - if (result != null) { - results.add(result); - } - } - - Collections.sort(results, new Comparator<MatchResult>() { - @Override - public int compare(MatchResult o1, MatchResult o2) { - return o1.compareTo(o2); - } - }); - return results; - } - public ActionCallback cancel() { myProgressIndicator.cancel(); myDone.setRejected(); @@ -1945,10 +1811,10 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA public void run() { try { final SearchResult result - = id == WidgetID.CLASSES ? getClasses(pattern, showAll.get(), DEFAULT_MORE_STEP_COUNT) - : id == WidgetID.FILES ? getFiles(pattern, DEFAULT_MORE_STEP_COUNT) + = id == WidgetID.CLASSES ? getClasses(pattern, showAll.get(), DEFAULT_MORE_STEP_COUNT, myClassChooseByName) + : id == WidgetID.FILES ? getFiles(pattern, DEFAULT_MORE_STEP_COUNT, myFileChooseByName) : id == WidgetID.RUN_CONFIGURATIONS ? getConfigurations(pattern, DEFAULT_MORE_STEP_COUNT) - : id == WidgetID.SYMBOLS ? getSymbols(pattern, DEFAULT_MORE_STEP_COUNT) + : id == WidgetID.SYMBOLS ? getSymbols(pattern, DEFAULT_MORE_STEP_COUNT, mySymbolsChooseByName) : id == WidgetID.ACTIONS ? getActionsOrSettings(pattern, DEFAULT_MORE_STEP_COUNT, true) : id == WidgetID.SETTINGS ? getActionsOrSettings(pattern, DEFAULT_MORE_STEP_COUNT, false) : new SearchResult(); @@ -2030,8 +1896,7 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA if (lock != null) { synchronized (lock) { myClassModel = null; - myActionModel = null; - myActions = null; + myActionProvider = null; mySymbolsModel = null; myConfigurables.clear(); myFocusComponent = null; diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/ProjectViewPsiTreeChangeListener.java b/platform/lang-impl/src/com/intellij/ide/projectView/ProjectViewPsiTreeChangeListener.java index 58bef64556c2..e2c79cffef46 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/ProjectViewPsiTreeChangeListener.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/ProjectViewPsiTreeChangeListener.java @@ -125,8 +125,10 @@ public abstract class ProjectViewPsiTreeChangeListener extends PsiTreeChangeAdap updater.addSubtreeToUpdate(rootNode); return; } - - updater.addSubtreeToUpdateByElement(element); + final PsiElement parent = element.getParent(); + if (parent == null || !updater.addSubtreeToUpdateByElement(parent)) { + updater.addSubtreeToUpdateByElement(element); + } } else if (propertyName.equals(PsiTreeChangeEvent.PROP_FILE_TYPES)){ updater.addSubtreeToUpdate(rootNode); diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkExcludeRootAction.java b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkExcludeRootAction.java index 85964857c5d0..2e55b4aee11a 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkExcludeRootAction.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkExcludeRootAction.java @@ -21,6 +21,7 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.roots.ContentEntry; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; @@ -32,11 +33,13 @@ public class MarkExcludeRootAction extends MarkRootActionBase { public void actionPerformed(AnActionEvent e) { VirtualFile[] files = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY); - String message = files.length == 1 ? FileUtil.toSystemDependentName(files[0].getPath()) : files.length + " selected files"; - final int rc = Messages.showOkCancelDialog(e.getData(CommonDataKeys.PROJECT), getPromptText(message), "Mark as Excluded", - Messages.getQuestionIcon()); - if (rc != Messages.OK) { - return; + if (Registry.is("ide.hide.excluded.files")) { + String message = files.length == 1 ? FileUtil.toSystemDependentName(files[0].getPath()) : files.length + " selected files"; + final int rc = Messages.showOkCancelDialog(e.getData(CommonDataKeys.PROJECT), getPromptText(message), "Mark as Excluded", + Messages.getQuestionIcon()); + if (rc != Messages.OK) { + return; + } } super.actionPerformed(e); } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootActionBase.java b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootActionBase.java index 815c3e8815ac..ac2326da6dac 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootActionBase.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootActionBase.java @@ -22,7 +22,9 @@ import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.impl.DirectoryIndex; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; @@ -38,23 +40,24 @@ import java.util.List; public abstract class MarkRootActionBase extends DumbAwareAction { @Override public void actionPerformed(AnActionEvent e) { - final Module module = e.getData(LangDataKeys.MODULE); - VirtualFile[] vFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY); - if (module == null || vFiles == null) { + VirtualFile[] files = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY); + final Module module = getModule(e, files); + if (module == null) { return; } + final ModifiableRootModel model = ModuleRootManager.getInstance(module).getModifiableModel(); - for (VirtualFile vFile : vFiles) { - ContentEntry entry = findContentEntry(model, vFile); + for (VirtualFile file : files) { + ContentEntry entry = findContentEntry(model, file); if (entry != null) { final SourceFolder[] sourceFolders = entry.getSourceFolders(); for (SourceFolder sourceFolder : sourceFolders) { - if (Comparing.equal(sourceFolder.getFile(), vFile)) { + if (Comparing.equal(sourceFolder.getFile(), file)) { entry.removeSourceFolder(sourceFolder); break; } } - modifyRoots(vFile, entry); + modifyRoots(file, entry); } } ApplicationManager.getApplication().runWriteAction(new Runnable() { @@ -66,7 +69,7 @@ public abstract class MarkRootActionBase extends DumbAwareAction { }); } - protected abstract void modifyRoots(VirtualFile vFile, ContentEntry entry); + protected abstract void modifyRoots(VirtualFile file, ContentEntry entry); @Nullable public static ContentEntry findContentEntry(@NotNull ModuleRootModel model, @NotNull VirtualFile vFile) { @@ -82,21 +85,22 @@ public abstract class MarkRootActionBase extends DumbAwareAction { @Override public void update(AnActionEvent e) { - Module module = e.getData(LangDataKeys.MODULE); RootsSelection selection = getSelection(e); - boolean enabled = module != null && (!selection.mySelectedRoots.isEmpty() || !selection.mySelectedDirectories.isEmpty()) && isEnabled(selection, module); - e.getPresentation().setVisible(enabled); - e.getPresentation().setEnabled(enabled); + doUpdate(e, e.getData(LangDataKeys.MODULE), selection); + } + + protected void doUpdate(@NotNull AnActionEvent e, @Nullable Module module, @NotNull RootsSelection selection) { + boolean enabled = module != null && (!selection.mySelectedRoots.isEmpty() || !selection.mySelectedDirectories.isEmpty()) + && selection.mySelectedExcludeRoots.isEmpty() && isEnabled(selection, module); + e.getPresentation().setEnabledAndVisible(enabled); } protected abstract boolean isEnabled(@NotNull RootsSelection selection, @NotNull Module module); protected static RootsSelection getSelection(AnActionEvent e) { - Module module = e.getData(LangDataKeys.MODULE); VirtualFile[] files = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY); - if (module == null || files == null) { - return RootsSelection.EMPTY; - } + Module module = getModule(e, files); + if (module == null) return RootsSelection.EMPTY; RootsSelection selection = new RootsSelection(); final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(module.getProject()).getFileIndex(); @@ -105,7 +109,14 @@ public abstract class MarkRootActionBase extends DumbAwareAction { return RootsSelection.EMPTY; } if (!fileIndex.isInContent(file)) { - return RootsSelection.EMPTY; + ExcludeFolder excludeFolder = ProjectRootsUtil.findExcludeFolder(module, file); + if (excludeFolder != null) { + selection.mySelectedExcludeRoots.add(excludeFolder); + continue; + } + else { + return RootsSelection.EMPTY; + } } SourceFolder folder; if (Comparing.equal(fileIndex.getSourceRootForFile(file), file) && ((folder = ProjectRootsUtil.findSourceFolder(module, file)) != null)) { @@ -121,10 +132,39 @@ public abstract class MarkRootActionBase extends DumbAwareAction { return selection; } + @Nullable + private static Module getModule(@NotNull AnActionEvent e, @Nullable VirtualFile[] files) { + if (files == null) return null; + Module module = e.getData(LangDataKeys.MODULE); + if (module == null) { + module = findParentModule(e.getProject(), files); + } + return module; + } + + @Nullable + private static Module findParentModule(@Nullable Project project, @NotNull VirtualFile[] files) { + if (project == null) return null; + Module result = null; + DirectoryIndex index = DirectoryIndex.getInstance(project); + for (VirtualFile file : files) { + Module module = index.getInfoForFile(file).getModule(); + if (module == null) return null; + if (result == null) { + result = module; + } + else if (!result.equals(module)) { + return null; + } + } + return result; + } + protected static class RootsSelection { public static final RootsSelection EMPTY = new RootsSelection(); public List<SourceFolder> mySelectedRoots = new ArrayList<SourceFolder>(); + public List<ExcludeFolder> mySelectedExcludeRoots = new ArrayList<ExcludeFolder>(); public List<VirtualFile> mySelectedDirectories = new ArrayList<VirtualFile>(); public boolean myHaveSelectedFilesUnderSourceRoots; } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/actions/UnmarkRootAction.java b/platform/lang-impl/src/com/intellij/ide/projectView/actions/UnmarkRootAction.java index cf2724dc039f..0689fe13c2d3 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/actions/UnmarkRootAction.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/actions/UnmarkRootAction.java @@ -18,12 +18,15 @@ package com.intellij.ide.projectView.actions; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.module.Module; import com.intellij.openapi.roots.ContentEntry; +import com.intellij.openapi.roots.ExcludeFolder; import com.intellij.openapi.roots.SourceFolder; import com.intellij.openapi.roots.ui.configuration.ModuleSourceRootEditHandler; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.HashSet; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; import java.util.Set; @@ -33,9 +36,16 @@ import java.util.Set; */ public class UnmarkRootAction extends MarkRootActionBase { @Override - public void update(AnActionEvent e) { - super.update(e); - RootsSelection selection = getSelection(e); + protected void doUpdate(@NotNull AnActionEvent e, @Nullable Module module, @NotNull RootsSelection selection) { + if (!Registry.is("ide.hide.excluded.files") && !selection.mySelectedExcludeRoots.isEmpty() + && selection.mySelectedDirectories.isEmpty() && selection.mySelectedRoots.isEmpty()) { + e.getPresentation().setEnabledAndVisible(true); + e.getPresentation().setText("Cancel Exclusion"); + return; + } + + super.doUpdate(e, module, selection); + Set<JpsModuleSourceRootType<?>> selectedRootTypes = new HashSet<JpsModuleSourceRootType<?>>(); for (SourceFolder root : selection.mySelectedRoots) { selectedRootTypes.add(root.getRootType()); @@ -60,6 +70,12 @@ public class UnmarkRootAction extends MarkRootActionBase { return selection.mySelectedDirectories.isEmpty() && !selection.mySelectedRoots.isEmpty(); } - protected void modifyRoots(VirtualFile vFile, ContentEntry entry) { + protected void modifyRoots(VirtualFile file, ContentEntry entry) { + for (ExcludeFolder excludeFolder : entry.getExcludeFolders()) { + if (file.equals(excludeFolder.getFile())) { + entry.removeExcludeFolder(excludeFolder); + break; + } + } } } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ProjectViewDirectoryHelper.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ProjectViewDirectoryHelper.java index ee05f0a10283..13ba91900521 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ProjectViewDirectoryHelper.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ProjectViewDirectoryHelper.java @@ -25,6 +25,7 @@ import com.intellij.ide.projectView.ViewSettings; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileTypeRegistry; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleFileIndex; @@ -32,7 +33,9 @@ import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.roots.impl.DirectoryIndex; +import com.intellij.openapi.roots.impl.DirectoryInfo; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; @@ -123,7 +126,7 @@ public class ProjectViewDirectoryHelper { final Module module = fileIndex.getModuleForFile(psiDirectory.getVirtualFile()); final ModuleFileIndex moduleFileIndex = module == null ? null : ModuleRootManager.getInstance(module).getFileIndex(); if (!settings.isFlattenPackages() || skipDirectory(psiDirectory)) { - processPsiDirectoryChildren(psiDirectory, directoryChildrenInProject(psiDirectory), + processPsiDirectoryChildren(psiDirectory, directoryChildrenInProject(fileIndex, psiDirectory), children, fileIndex, null, settings, withSubDirectories); } else { // source directory in "flatten packages" mode @@ -137,7 +140,13 @@ public class ProjectViewDirectoryHelper { continue; } VirtualFile directoryFile = subdir.getVirtualFile(); - if (fileIndex.isIgnored(directoryFile)) continue; + + if (Registry.is("ide.hide.excluded.files")) { + if (fileIndex.isExcluded(directoryFile)) continue; + } + else { + if (FileTypeRegistry.getInstance().isFileIgnored(directoryFile)) continue; + } if (withSubDirectories) { children.add(new PsiDirectoryNode(project, subdir, settings)); @@ -163,9 +172,9 @@ public class ProjectViewDirectoryHelper { return topLevelContentRoots; } - private PsiElement[] directoryChildrenInProject(PsiDirectory psiDirectory) { + private PsiElement[] directoryChildrenInProject(ProjectFileIndex fileIndex, PsiDirectory psiDirectory) { VirtualFile dir = psiDirectory.getVirtualFile(); - if (myIndex.getInfoForDirectory(dir) != null) { + if (isInProject(dir)) { return psiDirectory.getChildren(); } @@ -189,6 +198,16 @@ public class ProjectViewDirectoryHelper { return PsiUtilCore.toPsiElementArray(directoriesOnTheWayToContentRoots); } + private boolean isInProject(VirtualFile dir) { + DirectoryInfo directoryInfo = myIndex.getInfoForFile(dir); + if (directoryInfo.isInProject()) return true; + + if (!Registry.is("ide.hide.excluded.files")) { + return directoryInfo.isExcluded(); + } + return false; + } + // used only for non-flatten packages mode public void processPsiDirectoryChildren(final PsiDirectory psiDir, PsiElement[] children, @@ -211,7 +230,7 @@ public class ProjectViewDirectoryHelper { vFile = dir.getVirtualFile(); if (!vFile.equals(projectFileIndex.getSourceRootForFile(vFile))) { // if is not a source root if (viewSettings.isHideEmptyMiddlePackages() && !skipDirectory(psiDir) && isEmptyMiddleDirectory(dir, true)) { - processPsiDirectoryChildren(dir, directoryChildrenInProject(dir), + processPsiDirectoryChildren(dir, directoryChildrenInProject(projectFileIndex, dir), container, projectFileIndex, moduleFileIndex, viewSettings, withSubDirectories); // expand it recursively continue; } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java index a010828cd8ec..16aa930b806a 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java @@ -26,6 +26,7 @@ import com.intellij.ide.projectView.impl.ProjectRootsUtil; import com.intellij.ide.projectView.impl.ProjectViewImpl; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileTypes.FileTypeRegistry; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.project.Project; @@ -38,6 +39,7 @@ import com.intellij.openapi.roots.ui.configuration.ModuleSourceRootEditHandler; import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VFileProperty; import com.intellij.openapi.vfs.VfsUtilCore; @@ -183,9 +185,14 @@ public class PsiDirectoryNode extends BasePsiNode<PsiDirectory> implements Navig return false; } - final Project project = value.getProject(); - final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); - return !fileIndex.isIgnored(file); + if (Registry.is("ide.hide.excluded.files")) { + final Project project = value.getProject(); + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + return !fileIndex.isExcluded(file); + } + else { + return !FileTypeRegistry.getInstance().isFileIgnored(file); + } } @Override diff --git a/platform/lang-impl/src/com/intellij/ide/ui/search/TraverseUIStarter.java b/platform/lang-impl/src/com/intellij/ide/ui/search/TraverseUIStarter.java index b1c1e25e978c..eb22b72eb810 100644 --- a/platform/lang-impl/src/com/intellij/ide/ui/search/TraverseUIStarter.java +++ b/platform/lang-impl/src/com/intellij/ide/ui/search/TraverseUIStarter.java @@ -129,7 +129,7 @@ public class TraverseUIStarter implements ApplicationStarter { System.out.println("Searchable options index builder completed"); - ((ApplicationEx)ApplicationManager.getApplication()).exit(true); + ((ApplicationEx)ApplicationManager.getApplication()).exit(true, true); } private static void processFileTemplates(Element configurableElement) { diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionItemProvider.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionItemProvider.java new file mode 100644 index 000000000000..a6e6c8fdf658 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionItemProvider.java @@ -0,0 +1,169 @@ +/* + * Copyright 2000-2014 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.util.gotoByName; + +import com.intellij.ide.DataManager; +import com.intellij.ide.actions.ApplyIntentionAction; +import com.intellij.ide.ui.search.ActionFromOptionDescriptorProvider; +import com.intellij.ide.ui.search.OptionDescription; +import com.intellij.ide.ui.search.SearchableOptionsRegistrar; +import com.intellij.ide.ui.search.SearchableOptionsRegistrarImpl; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.impl.ActionManagerImpl; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.util.Function; +import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +import static com.intellij.ide.util.gotoByName.GotoActionModel.*; + +/** + * @author peter + */ +public class GotoActionItemProvider implements ChooseByNameItemProvider { + private final ActionManager myActionManager = ActionManager.getInstance(); + protected final SearchableOptionsRegistrar myIndex = SearchableOptionsRegistrar.getInstance(); + private final GotoActionModel myModel; + + public GotoActionItemProvider(GotoActionModel model) { + myModel = model; + } + + @NotNull + @Override + public List<String> filterNames(@NotNull ChooseByNameBase base, @NotNull String[] names, @NotNull String pattern) { + return Collections.emptyList(); // no common prefix insertion in goto action + } + + @Override + public boolean filterElements(@NotNull final ChooseByNameBase base, + @NotNull final String pattern, + boolean everywhere, + @NotNull ProgressIndicator cancelled, + @NotNull final Processor<Object> consumer) { + return filterElements(pattern, everywhere, new Processor<MatchedValue>() { + @Override + public boolean process(MatchedValue value) { + return consumer.process(value); + } + }); + } + + public boolean filterElements(String pattern, boolean everywhere, Processor<MatchedValue> consumer) { + DataContext dataContext = DataManager.getInstance().getDataContext(myModel.getContextComponent()); + + if (!processIntentions(pattern, consumer, dataContext)) return false; + if (!processActions(pattern, everywhere, consumer, dataContext)) return false; + if (!processOptions(pattern, consumer, dataContext)) return false; + + return true; + } + + private boolean processOptions(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) { + List<Comparable> options = ContainerUtil.newArrayList(); + final Set<String> words = myIndex.getProcessedWords(pattern); + Set<OptionDescription> optionDescriptions = null; + final String actionManagerName = myActionManager.getComponentName(); + for (String word : words) { + final Set<OptionDescription> descriptions = ((SearchableOptionsRegistrarImpl)myIndex).getAcceptableDescriptions(word); + if (descriptions != null) { + for (Iterator<OptionDescription> iterator = descriptions.iterator(); iterator.hasNext(); ) { + OptionDescription description = iterator.next(); + if (actionManagerName.equals(description.getPath())) { + iterator.remove(); + } + } + if (!descriptions.isEmpty()) { + if (optionDescriptions == null) { + optionDescriptions = descriptions; + } + else { + optionDescriptions.retainAll(descriptions); + } + } + } else { + optionDescriptions = null; + break; + } + } + if (optionDescriptions != null && !optionDescriptions.isEmpty()) { + Set<String> currentHits = new HashSet<String>(); + for (Iterator<OptionDescription> iterator = optionDescriptions.iterator(); iterator.hasNext(); ) { + OptionDescription description = iterator.next(); + final String hit = description.getHit(); + if (hit == null || !currentHits.add(hit.trim())) { + iterator.remove(); + } + } + for (OptionDescription description : optionDescriptions) { + for (ActionFromOptionDescriptorProvider converter : ActionFromOptionDescriptorProvider.EP.getExtensions()) { + AnAction action = converter.provide(description); + if (action != null) options.add(new ActionWrapper(action, null, MatchMode.NAME, dataContext)); + options.add(description); + } + } + } + return processItems(pattern, options, consumer); + } + + private boolean processActions(String pattern, boolean everywhere, Processor<MatchedValue> consumer, DataContext dataContext) { + List<AnAction> actions = ContainerUtil.newArrayList(); + if (everywhere) { + for (String id : ((ActionManagerImpl)myActionManager).getActionIds()) { + ContainerUtil.addIfNotNull(actions, myActionManager.getAction(id)); + } + } else { + actions.addAll(myModel.myActionsMap.keySet()); + } + + List<ActionWrapper> actionWrappers = ContainerUtil.newArrayList(); + for (AnAction action : actions) { + MatchMode mode = myModel.actionMatches(pattern, action); + if (mode != MatchMode.NONE) { + actionWrappers.add(new ActionWrapper(action, myModel.myActionsMap.get(action), mode, dataContext)); + } + } + return processItems(pattern, actionWrappers, consumer); + } + + private boolean processIntentions(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) { + List<ActionWrapper> intentions = ContainerUtil.newArrayList(); + for (String intentionText : myModel.myIntentions.keySet()) { + final ApplyIntentionAction intentionAction = myModel.myIntentions.get(intentionText); + if (myModel.actionMatches(pattern, intentionAction) != MatchMode.NONE) { + intentions.add(new ActionWrapper(intentionAction, intentionText, MatchMode.INTENTION, dataContext)); + } + } + return processItems(pattern, intentions, consumer); + } + + private static boolean processItems(final String pattern, List<? extends Comparable> items, Processor<MatchedValue> consumer) { + List<MatchedValue> matched = ContainerUtil.map(items, new Function<Comparable, MatchedValue>() { + @Override + public MatchedValue fun(Comparable comparable) { + return new MatchedValue(comparable, pattern); + } + }); + Collections.sort(matched); + return ContainerUtil.process(matched, consumer); + } + +} diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java index 45fe6a9a074c..91f0d3368521 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java @@ -16,34 +16,28 @@ package com.intellij.ide.util.gotoByName; -import com.intellij.ide.DataManager; import com.intellij.ide.IdeBundle; import com.intellij.ide.actions.ApplyIntentionAction; -import com.intellij.ide.ui.search.ActionFromOptionDescriptorProvider; +import com.intellij.ide.actions.ShowSettingsUtilImpl; import com.intellij.ide.ui.search.OptionDescription; import com.intellij.ide.ui.search.SearchableOptionsRegistrar; -import com.intellij.ide.ui.search.SearchableOptionsRegistrarImpl; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionUtil; -import com.intellij.openapi.actionSystem.impl.ActionManagerImpl; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.SearchableConfigurable; -import com.intellij.openapi.options.ex.IdeConfigurablesGroup; -import com.intellij.openapi.options.ex.ProjectConfigurablesGroup; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiFile; -import com.intellij.ui.ColorUtil; -import com.intellij.ui.IdeBorderFactory; -import com.intellij.ui.LayeredIcon; -import com.intellij.ui.LightColors; +import com.intellij.ui.*; import com.intellij.ui.components.JBLabel; +import com.intellij.ui.speedSearch.SpeedSearchUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.EmptyIcon; @@ -58,9 +52,10 @@ import java.awt.*; import java.util.*; import java.util.List; +import static com.intellij.ui.SimpleTextAttributes.STYLE_PLAIN; +import static com.intellij.ui.SimpleTextAttributes.STYLE_SEARCH_MATCH; + public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, Comparator<Object>, EdtSortingModel { - @NonNls public static final String SETTINGS_KEY = "$$$SETTINGS$$$"; - @NonNls public static final String INTENTIONS_KEY = "$$$INTENTIONS_KEY$$$"; @Nullable private final Project myProject; private final Component myContextComponent; @@ -103,10 +98,7 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C } } myIndex = SearchableOptionsRegistrar.getInstance(); - fillConfigurablesNames(new IdeConfigurablesGroup().getConfigurables()); - if (project != null) { - fillConfigurablesNames(new ProjectConfigurablesGroup(project).getConfigurables()); - } + fillConfigurablesNames(ShowSettingsUtilImpl.getConfigurables(project, true)); } private void fillConfigurablesNames(Configurable[] configurables) { @@ -151,13 +143,61 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C public void saveInitialCheckBoxState(boolean state) { } + public static class MatchedValue implements Comparable<MatchedValue> { + @NotNull public final Comparable value; + @NotNull final String pattern; + + MatchedValue(@NotNull Comparable value, @NotNull String pattern) { + this.value = value; + this.pattern = pattern; + } + + @Nullable + private String getValueText() { + if (value instanceof OptionDescription) return ((OptionDescription)value).getHit(); + if (!(value instanceof ActionWrapper)) return null; + return ((ActionWrapper)value).getAction().getTemplatePresentation().getText(); + } + + private int getMatchingDegree() { + String text = getValueText(); + if (text != null) { + if (StringUtil.equalsIgnoreCase(StringUtil.trimEnd(text, "..."), pattern)) return 3; + if (StringUtil.startsWithIgnoreCase(text, pattern)) return 2; + if (StringUtil.containsIgnoreCase(text, pattern)) return 1; + } + return 0; + } + + @Override + public int compareTo(@NotNull MatchedValue o) { + if (value instanceof OptionDescription && !(o.value instanceof OptionDescription)) { + return 1; + } + if (o.value instanceof OptionDescription && !(value instanceof OptionDescription)) { + return -1; + } + + if (value instanceof ActionWrapper && o.value instanceof ActionWrapper && ApplicationManager.getApplication().isDispatchThread()) { + boolean p1Enable = ((ActionWrapper)value).isAvailable(); + boolean p2enable = ((ActionWrapper)o.value).isAvailable(); + if (p1Enable && !p2enable) return -1; + if (!p1Enable && p2enable) return 1; + } + + int diff = o.getMatchingDegree() - getMatchingDegree(); + //noinspection unchecked + return diff != 0 ? diff : value.compareTo(o.value); + } + } + @Override public ListCellRenderer getListCellRenderer() { return new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(@NotNull final JList list, - final Object value, + final Object matchedValue, final int index, final boolean isSelected, final boolean cellHasFocus) { final JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(IdeBorderFactory.createEmptyBorder(2)); @@ -165,21 +205,40 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C Color bg = UIUtil.getListBackground(isSelected); panel.setBackground(bg); + if (matchedValue instanceof String) { //... + final JBLabel label = new JBLabel((String)matchedValue); + label.setIcon(EMPTY_ICON); + panel.add(label, BorderLayout.WEST); + return panel; + } + Color groupFg = isSelected ? UIUtil.getListSelectionForeground() : UIUtil.getLabelDisabledForeground(); + Object value = ((MatchedValue) matchedValue).value; + String pattern = ((MatchedValue)matchedValue).pattern; + + SimpleColoredComponent nameComponent = new SimpleColoredComponent(); + nameComponent.setBackground(bg); + panel.add(nameComponent, BorderLayout.CENTER); + if (value instanceof ActionWrapper) { final ActionWrapper actionWithParentGroup = (ActionWrapper)value; final AnAction anAction = actionWithParentGroup.getAction(); final Presentation templatePresentation = anAction.getTemplatePresentation(); - final Icon icon = templatePresentation.getIcon(); final Color fg = defaultActionForeground(isSelected, actionWithParentGroup.getPresentation()); - final JLabel actionLabel = createActionLabel(anAction, templatePresentation.getText(), fg, bg, icon); - panel.add(actionLabel, BorderLayout.WEST); + panel.add(createIconLabel(templatePresentation.getIcon()), BorderLayout.WEST); + + appendWithColoredMatches(nameComponent, templatePresentation.getText(), pattern, fg, isSelected); - final String groupName = actionWithParentGroup.getGroupName(); + final Shortcut shortcut = preferKeyboardShortcut(KeymapManager.getInstance().getActiveKeymap().getShortcuts(getActionId(anAction))); + if (shortcut != null) { + nameComponent.append(" (" + KeymapUtil.getShortcutText(shortcut) + ")", new SimpleTextAttributes(STYLE_PLAIN, groupFg)); + } + + String groupName = actionWithParentGroup.getAction() instanceof ApplyIntentionAction ? null : actionWithParentGroup.getGroupName(); if (groupName != null) { final JLabel groupLabel = new JLabel(groupName); groupLabel.setBackground(bg); @@ -189,7 +248,9 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C } else if (value instanceof OptionDescription) { if (!isSelected) { - panel.setBackground(UIUtil.isUnderDarcula() ? ColorUtil.brighter(UIUtil.getListBackground(), 1) : LightColors.SLIGHTLY_GRAY); + Color descriptorBg = UIUtil.isUnderDarcula() ? ColorUtil.brighter(UIUtil.getListBackground(), 1) : LightColors.SLIGHTLY_GRAY; + panel.setBackground(descriptorBg); + nameComponent.setBackground(descriptorBg); } String hit = ((OptionDescription)value).getHit(); if (hit == null) { @@ -200,23 +261,31 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C hit = hit.replace(" ", " "); //avoid extra spaces from mnemonics and xml conversion final Color fg = UIUtil.getListForeground(isSelected); - final JLabel label = new JLabel(hit.trim()); - label.setIcon(EMPTY_ICON); - label.setForeground(fg); - label.setBackground(bg); - panel.add(label, BorderLayout.WEST); + + appendWithColoredMatches(nameComponent, hit.trim(), pattern, fg, isSelected); + + panel.add(new JLabel(EMPTY_ICON), BorderLayout.WEST); + final JLabel settingsLabel = new JLabel(getGroupName((OptionDescription)value)); settingsLabel.setForeground(groupFg); settingsLabel.setBackground(bg); panel.add(settingsLabel, BorderLayout.EAST); } - else if (value instanceof String) { - final JBLabel label = new JBLabel((String)value); - label.setIcon(EMPTY_ICON); - panel.add(label, BorderLayout.WEST); - } return panel; } + + private void appendWithColoredMatches(SimpleColoredComponent nameComponent, String name, String pattern, Color fg, boolean selected) { + final SimpleTextAttributes plain = new SimpleTextAttributes(STYLE_PLAIN, fg); + final SimpleTextAttributes highlighted = new SimpleTextAttributes(null, fg, null, STYLE_SEARCH_MATCH); + List<TextRange> fragments = ContainerUtil.newArrayList(); + if (selected) { + int matchStart = StringUtil.indexOfIgnoreCase(name, pattern, 0); + if (matchStart >= 0) { + fragments.add(TextRange.from(matchStart, pattern.length())); + } + } + SpeedSearchUtil.appendColoredFragments(nameComponent, name, fragments, plain, highlighted); + } }; } @@ -224,6 +293,18 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C return myActionManager.getId(anAction); } + private static JLabel createIconLabel(final Icon icon) { + final LayeredIcon layeredIcon = new LayeredIcon(2); + layeredIcon.setIcon(EMPTY_ICON, 0); + if (icon != null && icon.getIconWidth() <= EMPTY_ICON.getIconWidth() && icon.getIconHeight() <= EMPTY_ICON.getIconHeight()) { + layeredIcon + .setIcon(icon, 1, (-icon.getIconWidth() + EMPTY_ICON.getIconWidth()) / 2, (EMPTY_ICON.getIconHeight() - icon.getIconHeight()) / 2); + } + + return new JLabel(layeredIcon); + } + + protected JLabel createActionLabel(final AnAction anAction, final String anActionName, final Color fg, final Color bg, final Icon icon) { @@ -256,23 +337,7 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C public int compare(@NotNull Object o1, @NotNull Object o2) { if (ChooseByNameBase.EXTRA_ELEM.equals(o1)) return 1; if (ChooseByNameBase.EXTRA_ELEM.equals(o2)) return -1; - - if (o1 instanceof OptionDescription && !(o2 instanceof OptionDescription)) { - return 1; - } - if (o2 instanceof OptionDescription && !(o1 instanceof OptionDescription)) { - return -1; - } - - if (o1 instanceof OptionDescription) { - return ((OptionDescription)o1).compareTo(o2); - } - - if (o1 instanceof ActionWrapper && o2 instanceof ActionWrapper) { - return ((ActionWrapper)o1).compareTo((ActionWrapper)o2); - } - - return StringUtil.compare(getFullName(o1), getFullName(o2), true); + return ((MatchedValue) o1).compareTo((MatchedValue)o2); } public static AnActionEvent updateActionBeforeShow(AnAction anAction, DataContext dataContext) { @@ -293,107 +358,17 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C @Override @NotNull public String[] getNames(boolean checkBoxState) { - final LinkedHashSet<String> result = new LinkedHashSet<String>(); - result.add(INTENTIONS_KEY); - for (AnAction action : myActionsMap.keySet()) { - result.add(getActionId(action)); - } - if (checkBoxState) { - final Set<String> ids = ((ActionManagerImpl)myActionManager).getActionIds(); - for (String id : ids) { - result.add(id); - } - } - result.add(SETTINGS_KEY); - return ArrayUtil.toStringArray(result); + return ArrayUtil.EMPTY_STRING_ARRAY; } @Override @NotNull public Object[] getElementsByName(final String id, final boolean checkBoxState, final String pattern) { - List<Object> objects = new ArrayList<Object>(); - final AnAction act = myActionManager.getAction(id); - DataContext dataContext = DataManager.getInstance().getDataContext(myContextComponent); - if (act != null) { - final HashMap<AnAction, String> map = new HashMap<AnAction, String>(); - final MatchMode matchMode = actionMatches(pattern, act); - final String groupName = myActionsMap.get(act); - if (map.put(act, groupName) == null) { - objects.add(new ActionWrapper(act, groupName, matchMode, dataContext)); - } - if (checkBoxState) { - final Set<String> ids = ((ActionManagerImpl)myActionManager).getActionIds(); - for (AnAction action : map.keySet()) { //do not add already included actions - ids.remove(getActionId(action)); - } - if (ids.contains(id)) { - final AnAction anAction = myActionManager.getAction(id); - map.put(anAction, null); - if (anAction != null) { - objects.add(new ActionWrapper(anAction, null, MatchMode.NON_MENU, dataContext)); - } - } - } - } else if (Comparing.strEqual(id, INTENTIONS_KEY)) { - for (String intentionText : myIntentions.keySet()) { - final ApplyIntentionAction intentionAction = myIntentions.get(intentionText); - if (actionMatches(pattern, intentionAction) != MatchMode.NONE) { - objects.add(new ActionWrapper(intentionAction, intentionText, MatchMode.INTENTION, dataContext)); - } - } - } - if (Comparing.strEqual(id, SETTINGS_KEY)) { - final Set<String> words = myIndex.getProcessedWords(pattern); - Set<OptionDescription> optionDescriptions = null; - final String actionManagerName = myActionManager.getComponentName(); - for (String word : words) { - final Set<OptionDescription> descriptions = ((SearchableOptionsRegistrarImpl)myIndex).getAcceptableDescriptions(word); - if (descriptions != null) { - for (Iterator<OptionDescription> iterator = descriptions.iterator(); iterator.hasNext(); ) { - OptionDescription description = iterator.next(); - if (actionManagerName.equals(description.getPath())) { - iterator.remove(); - } - } - if (!descriptions.isEmpty()) { - if (optionDescriptions == null) { - optionDescriptions = descriptions; - } - else { - optionDescriptions.retainAll(descriptions); - } - } - } else { - optionDescriptions = null; - break; - } - } - if (optionDescriptions != null && !optionDescriptions.isEmpty()) { - Set<String> currentHits = new HashSet<String>(); - for (Iterator<OptionDescription> iterator = optionDescriptions.iterator(); iterator.hasNext(); ) { - OptionDescription description = iterator.next(); - final String hit = description.getHit(); - if (hit == null || !currentHits.add(hit.trim())) { - iterator.remove(); - } - } - for (OptionDescription description : optionDescriptions) { - for (ActionFromOptionDescriptorProvider converter : ActionFromOptionDescriptorProvider.EP.getExtensions()) { - AnAction action = converter.provide(description); - if (action != null) { - String title = getGroupName(description); - objects.add(new ActionWrapper(action, title, MatchMode.NAME, dataContext)); - } - objects.add(description); - } - } - } - } - return objects.toArray(new Object[objects.size()]); + return ArrayUtil.EMPTY_OBJECT_ARRAY; } @NotNull - private String getGroupName(@NotNull OptionDescription description) { + String getGroupName(@NotNull OptionDescription description) { String id = description.getConfigurableId(); String name = myConfigurablesNames.get(id); String settings = SystemInfo.isMac ? "Preferences" : "Settings"; @@ -459,10 +434,8 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C } @Override - public String getElementName(final Object element) { - if (element instanceof OptionDescription) return ((OptionDescription)element).getHit(); - if (!(element instanceof ActionWrapper)) return null; - return ((ActionWrapper)element).getAction().getTemplatePresentation().getText(); + public String getElementName(final Object mv) { + return ((MatchedValue) mv).getValueText(); } @Override @@ -485,7 +458,10 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C return MatchMode.DESCRIPTION; } final String groupName = myActionsMap.get(anAction); - return groupName != null && text != null && matcher.matches(groupName + " " + text, compiledPattern) ? MatchMode.GROUP : MatchMode.NONE; + if (groupName == null) { + return text != null && matcher.matches(text, compiledPattern) ? MatchMode.NON_MENU : MatchMode.NONE; + } + return text != null && matcher.matches(groupName + " " + text, compiledPattern) ? MatchMode.GROUP : MatchMode.NONE; } @Nullable @@ -498,7 +474,7 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C } @NotNull - private Pattern getPattern(@NotNull String pattern) { + Pattern getPattern(@NotNull String pattern) { String converted = convertPattern(pattern.trim()); Pattern compiledPattern = myCompiledPattern; if (compiledPattern != null && !Comparing.strEqual(converted, compiledPattern.getPattern())) { @@ -528,7 +504,7 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C NONE, INTENTION, NAME, DESCRIPTION, GROUP, NON_MENU } - private static String convertPattern(String pattern) { + static String convertPattern(String pattern) { final int eol = pattern.indexOf('\n'); if (eol != -1) { pattern = pattern.substring(0, eol); @@ -632,7 +608,7 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C return new Perl5Matcher(); } }; - private PatternMatcher getMatcher() { + PatternMatcher getMatcher() { return myMatcher.get(); } @@ -660,20 +636,13 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C @Override public int compareTo(@NotNull ActionWrapper o) { - if (ApplicationManager.getApplication().isDispatchThread()) { - boolean p1Enable = visible(); - boolean p2enable = o.visible(); - if (p1Enable && !p2enable) return -1; - if (!p1Enable && p2enable) return 1; - } - int compared = myMode.compareTo(o.getMode()); return compared != 0 ? compared : StringUtil.compare(myAction.getTemplatePresentation().getText(), o.getAction().getTemplatePresentation().getText(), true); } - private boolean visible() { + private boolean isAvailable() { return getPresentation().isEnabledAndVisible(); } diff --git a/platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java b/platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java index 19651a7f68a8..e52fa66ccefc 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java @@ -15,17 +15,14 @@ */ package com.intellij.injected.editor; -import com.intellij.openapi.editor.Caret; -import com.intellij.openapi.editor.CaretModel; -import com.intellij.openapi.editor.LogicalPosition; -import com.intellij.openapi.editor.VisualPosition; +import com.intellij.openapi.editor.*; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.TextRange; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -class InjectedCaret implements Caret { +public class InjectedCaret implements Caret { private final EditorWindow myEditorWindow; final Caret myDelegate; @@ -36,10 +33,20 @@ class InjectedCaret implements Caret { @NotNull @Override + public Editor getEditor() { + return myEditorWindow; + } + + @NotNull + @Override public CaretModel getCaretModel() { return myEditorWindow.getCaretModel(); } + public Caret getDelegate() { + return myDelegate; + } + @Override public boolean isValid() { return myDelegate.isValid(); diff --git a/platform/lang-impl/src/com/intellij/internal/DumpDirectoryInfoAction.java b/platform/lang-impl/src/com/intellij/internal/DumpDirectoryInfoAction.java index b5d0ae16a0ca..2f278bb97919 100644 --- a/platform/lang-impl/src/com/intellij/internal/DumpDirectoryInfoAction.java +++ b/platform/lang-impl/src/com/intellij/internal/DumpDirectoryInfoAction.java @@ -18,14 +18,12 @@ package com.intellij.internal; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ContentIterator; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.roots.impl.DirectoryIndex; -import com.intellij.openapi.roots.impl.DirectoryInfo; import com.intellij.openapi.vfs.VirtualFile; /** @@ -53,10 +51,7 @@ public class DumpDirectoryInfoAction extends AnAction { public boolean processFile(VirtualFile fileOrDir) { LOG.info(fileOrDir.getPath()); - final DirectoryInfo directoryInfo = index.getInfoForDirectory(fileOrDir); - if (directoryInfo != null) { - LOG.info(directoryInfo.toString()); - } + LOG.info(index.getInfoForFile(fileOrDir).toString()); return true; } }; diff --git a/platform/lang-impl/src/com/intellij/internal/DumpVfsInfoForExcludedFilesAction.java b/platform/lang-impl/src/com/intellij/internal/DumpVfsInfoForExcludedFilesAction.java new file mode 100644 index 000000000000..c4fb34c5ce54 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/internal/DumpVfsInfoForExcludedFilesAction.java @@ -0,0 +1,114 @@ +/* + * Copyright 2000-2014 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.internal; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.impl.DirectoryIndexExcludePolicy; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +import com.intellij.openapi.vfs.newvfs.persistent.FSRecords; +import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; + +import java.util.*; + +/** + * @author nik + */ +@SuppressWarnings("UseOfSystemOutOrSystemErr") +public class DumpVfsInfoForExcludedFilesAction extends DumbAwareAction { + public DumpVfsInfoForExcludedFilesAction() { + super("Dump VFS content for files under excluded roots"); + } + + @Override + public void actionPerformed(AnActionEvent e) { + Project project = e.getProject(); + if (project == null) return; + + Set<String> excludeRoots = new HashSet<String>(); + for (Module module : ModuleManager.getInstance(project).getModules()) { + Collections.addAll(excludeRoots, ModuleRootManager.getInstance(module).getExcludeRootUrls()); + } + for (DirectoryIndexExcludePolicy policy : DirectoryIndexExcludePolicy.EP_NAME.getExtensions(project)) { + for (VirtualFile file : policy.getExcludeRootsForProject()) { + excludeRoots.add(file.getUrl()); + } + } + + if (excludeRoots.isEmpty()) { + System.out.println("No excluded roots found in project."); + } + + for (String root : excludeRoots) { + VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(root); + if (file == null) { + System.out.println(root + " not in VFS"); + continue; + } + dumpChildrenInDbRecursively(file, 0); + } + } + + private static void dumpChildrenInDbRecursively(VirtualFile dir, int depth) { + if (!(dir instanceof NewVirtualFile)) { + System.out.println(dir.getPresentableUrl() + ": not in db (" + dir.getClass().getName() + ")"); + return; + } + + List<VirtualFile> dirs = new ArrayList<VirtualFile>(); + int inDb = 0, contentInDb = 0, nullChildren = 0; + PersistentFS persistentFS = PersistentFS.getInstance(); + if (persistentFS.wereChildrenAccessed(dir)) { + for (String name : persistentFS.listPersisted(dir)) { + inDb++; + NewVirtualFile child = ((NewVirtualFile)dir).refreshAndFindChild(name); + if (child == null) { + nullChildren++; + continue; + } + if (child.isDirectory()) { + dirs.add(child); + } + else if (FSRecords.getContentId(child.getId()) != 0) { + contentInDb++; + } + } + } + System.out.print(dir.getPresentableUrl() + ": " + inDb + " children in db"); + if (contentInDb > 0) { + System.out.print(", content of " + contentInDb + " files in db"); + } + if (nullChildren > 0) { + System.out.print(", " + nullChildren + " invalid files in db"); + } + System.out.println(); + + if (depth > 10) { + System.out.println("too deep, skipping children"); + } + else { + for (VirtualFile childDir : dirs) { + dumpChildrenInDbRecursively(childDir, depth+1); + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/lang/LanguagePerFileMappings.java b/platform/lang-impl/src/com/intellij/lang/LanguagePerFileMappings.java deleted file mode 100644 index c1a186b614c7..000000000000 --- a/platform/lang-impl/src/com/intellij/lang/LanguagePerFileMappings.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2000-2009 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; - -import com.intellij.injected.editor.VirtualFileWindow; -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.impl.FilePropertyPusher; -import com.intellij.openapi.roots.impl.PushedFilePropertiesUpdater; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.testFramework.LightVirtualFile; -import com.intellij.util.containers.ContainerUtil; -import gnu.trove.THashMap; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import java.util.*; - -/** - * @author peter - */ -public abstract class LanguagePerFileMappings<T> implements PersistentStateComponent<Element>, PerFileMappings<T> { - - private static final Logger LOG = Logger.getInstance("com.intellij.lang.LanguagePerFileMappings"); - - private final Map<VirtualFile, T> myMappings = new HashMap<VirtualFile, T>(); - private final Project myProject; - - public LanguagePerFileMappings(final Project project) { - myProject = project; - } - - @Nullable - protected FilePropertyPusher<T> getFilePropertyPusher() { - return null; - } - - @Override - public Map<VirtualFile, T> getMappings() { - synchronized (myMappings) { - cleanup(); - return Collections.unmodifiableMap(myMappings); - } - } - - private void cleanup() { - for (final VirtualFile file : new ArrayList<VirtualFile>(myMappings.keySet())) { - if (file != null //PROJECT, top-level - && !file.isValid()) { - myMappings.remove(file); - } - } - } - - @Override - @Nullable - public T getMapping(@Nullable VirtualFile file) { - FilePropertyPusher<T> pusher = getFilePropertyPusher(); - T t = getMappingInner(file, myMappings, pusher == null? null : pusher.getFileDataKey()); - return t == null? getDefaultMapping(file) : t; - } - - @Nullable - protected static <T> T getMappingInner(@Nullable VirtualFile file, @Nullable Map<VirtualFile, T> mappings, @Nullable Key<T> pusherKey) { - if (file instanceof VirtualFileWindow) { - final VirtualFileWindow window = (VirtualFileWindow)file; - file = window.getDelegate(); - } - VirtualFile originalFile = file instanceof LightVirtualFile ? ((LightVirtualFile)file).getOriginalFile() : null; - if (Comparing.equal(originalFile, file)) originalFile = null; - - if (file != null) { - final T pushedValue = pusherKey == null? null : file.getUserData(pusherKey); - if (pushedValue != null) return pushedValue; - } - if (originalFile != null) { - final T pushedValue = pusherKey == null? null : originalFile.getUserData(pusherKey); - if (pushedValue != null) return pushedValue; - } - if (mappings == null) return null; - synchronized (mappings) { - for (VirtualFile cur = file; ; cur = cur.getParent()) { - T t = mappings.get(cur); - if (t != null) return t; - if (originalFile != null) { - t = mappings.get(originalFile); - if (t != null) return t; - originalFile = originalFile.getParent(); - } - if (cur == null) break; - } - } - return null; - } - - @Override - public T chosenToStored(VirtualFile file, T value) { - return value; - } - - @Override - public boolean isSelectable(T value) { - return true; - } - - @Override - @Nullable - public T getDefaultMapping(@Nullable final VirtualFile file) { - return null; - } - - @Nullable - public T getImmediateMapping(@Nullable final VirtualFile file) { - synchronized (myMappings) { - return myMappings.get(file); - } - } - - @Override - public void setMappings(final Map<VirtualFile, T> mappings) { - final Collection<VirtualFile> oldFiles; - synchronized (myMappings) { - oldFiles = new ArrayList<VirtualFile>(myMappings.keySet()); - myMappings.clear(); - myMappings.putAll(mappings); - cleanup(); - } - handleMappingChange(mappings.keySet(), oldFiles, !getProject().isDefault()); - } - - public void setMapping(@Nullable final VirtualFile file, @Nullable T dialect) { - synchronized (myMappings) { - if (dialect == null) { - myMappings.remove(file); - } - else { - myMappings.put(file, dialect); - } - } - final List<VirtualFile> files = ContainerUtil.createMaybeSingletonList(file); - handleMappingChange(files, files, false); - } - - private void handleMappingChange(final Collection<VirtualFile> files, Collection<VirtualFile> oldFiles, final boolean includeOpenFiles) { - final FilePropertyPusher<T> pusher = getFilePropertyPusher(); - if (pusher != null) { - for (VirtualFile oldFile : oldFiles) { - if (oldFile == null) continue; // project - oldFile.putUserData(pusher.getFileDataKey(), null); - } - PushedFilePropertiesUpdater updater = PushedFilePropertiesUpdater.getInstance(myProject); - if (updater == null) { - if (!myProject.isDefault()) { - LOG.error("updater = null. project=" + myProject.getName()+", this="+getClass().getSimpleName()); - } - } - else { - updater.pushAll(pusher); - } - } - if (shouldReparseFiles()) { - PsiDocumentManager.getInstance(myProject).reparseFiles(files, includeOpenFiles); - } - } - - @Override - public Collection<T> getAvailableValues(VirtualFile file) { - return getAvailableValues(); - } - - protected abstract List<T> getAvailableValues(); - - @Nullable - protected abstract String serialize(T t); - - @Override - public Element getState() { - synchronized (myMappings) { - cleanup(); - final Element element = new Element("x"); - final List<VirtualFile> files = new ArrayList<VirtualFile>(myMappings.keySet()); - Collections.sort(files, new Comparator<VirtualFile>() { - @Override - public int compare(final VirtualFile o1, final VirtualFile o2) { - if (o1 == null || o2 == null) return o1 == null ? o2 == null ? 0 : 1 : -1; - return o1.getPath().compareTo(o2.getPath()); - } - }); - for (VirtualFile file : files) { - final T dialect = myMappings.get(file); - String value = serialize(dialect); - if (value != null) { - final Element child = new Element("file"); - element.addContent(child); - child.setAttribute("url", file == null ? "PROJECT" : file.getUrl()); - child.setAttribute(getValueAttribute(), value); - } - } - return element; - } - } - - @Nullable - protected T handleUnknownMapping(VirtualFile file, String value) { - return null; - } - - @NotNull - protected String getValueAttribute() { - return "dialect"; - } - - @Override - public void loadState(final Element state) { - synchronized (myMappings) { - final THashMap<String, T> dialectMap = new THashMap<String, T>(); - for (T dialect : getAvailableValues()) { - String key = serialize(dialect); - if (key != null) { - dialectMap.put(key, dialect); - } - } - final List<Element> files = state.getChildren("file"); - for (Element fileElement : files) { - final String url = fileElement.getAttributeValue("url"); - final String dialectID = fileElement.getAttributeValue(getValueAttribute()); - final VirtualFile file = url.equals("PROJECT") ? null : VirtualFileManager.getInstance().findFileByUrl(url); - T dialect = dialectMap.get(dialectID); - if (dialect == null) { - dialect = handleUnknownMapping(file, dialectID); - if (dialect == null) continue; - } - if (file != null || url.equals("PROJECT")) { - myMappings.put(file, dialect); - } - } - } - } - - @TestOnly - public void cleanupForNextTest() { - synchronized (myMappings) { - myMappings.clear(); - } - } - - protected Project getProject() { - return myProject; - } - - protected boolean shouldReparseFiles() { - return true; - } - - public boolean hasMappings() { - synchronized (myMappings) { - return !myMappings.isEmpty(); - } - } - -} diff --git a/platform/lang-impl/src/com/intellij/lang/parser/GeneratedParserUtilBase.java b/platform/lang-impl/src/com/intellij/lang/parser/GeneratedParserUtilBase.java index ecda31db8355..ad322c232fa0 100644 --- a/platform/lang-impl/src/com/intellij/lang/parser/GeneratedParserUtilBase.java +++ b/platform/lang-impl/src/com/intellij/lang/parser/GeneratedParserUtilBase.java @@ -2,6 +2,7 @@ // Do not modify or refactor without complete investigation and/or review. package com.intellij.lang.parser; +import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; import com.intellij.lang.*; import com.intellij.lang.impl.PsiBuilderAdapter; import com.intellij.lang.impl.PsiBuilderImpl; @@ -337,18 +338,18 @@ public class GeneratedParserUtilBase { } else if (diff > 0 && diff <= length) { CharSequence fragment = builder_.getOriginalText().subSequence(offset, completionState.offset); - add = StringUtil.startsWithIgnoreCase(text, fragment.toString()); + add = completionState.prefixMatches(fragment.toString(), text); } else if (diff < 0) { for (int i=-1; ; i--) { IElementType type = builder_.rawLookup(i); int tokenStart = builder_.rawTokenTypeStart(i); - if (((PsiBuilderImpl)((Builder)builder_).getDelegate()).whitespaceOrComment(type)) { + if (isWhitespaceOrComment(builder_, type)) { diff = completionState.offset - tokenStart; } else if (type != null && tokenStart < completionState.offset) { CharSequence fragment = builder_.getOriginalText().subSequence(tokenStart, completionState.offset); - if (StringUtil.startsWithIgnoreCase(text, fragment.toString())) { + if (completionState.prefixMatches(fragment.toString(), text)) { diff = completionState.offset - tokenStart; } break; @@ -364,6 +365,10 @@ public class GeneratedParserUtilBase { } } + public static boolean isWhitespaceOrComment(@NotNull PsiBuilder builder_, @Nullable IElementType type) { + return ((PsiBuilderImpl)((Builder)builder_).getDelegate()).whitespaceOrComment(type); + } + // here's the new section API for compact parsers & less IntelliJ platform API exposure public static final int _NONE_ = 0x0; public static final int _COLLAPSE_ = 0x1; @@ -514,7 +519,9 @@ public class GeneratedParserUtilBase { } // propagate errorReportedAt up the stack to avoid duplicate reporting Frame prevFrame = willFail && eatMore == null ? null : state.frameStack.peekLast(); - if (prevFrame != null && prevFrame.errorReportedAt < frame.errorReportedAt) prevFrame.errorReportedAt = frame.errorReportedAt; + if (prevFrame != null && prevFrame.errorReportedAt < frame.errorReportedAt) { + prevFrame.errorReportedAt = frame.errorReportedAt; + } state.FRAMES.recycle(frame); } @@ -657,9 +664,17 @@ public class GeneratedParserUtilBase { return o.toString(); } - public void addItem(PsiBuilder builder, String text) { + public void addItem(@NotNull PsiBuilder builder, @NotNull String text) { items.add(text); } + + public boolean prefixMatches(@NotNull String prefix, @NotNull String variant) { + boolean matches = new CamelHumpMatcher(prefix, false).prefixMatches(variant.replace(' ', '_')); + if (matches && StringUtil.isWhiteSpace(prefix.charAt(prefix.length() - 1))) { + return StringUtil.startsWithIgnoreCase(variant, prefix); + } + return matches; + } } public static class Builder extends PsiBuilderAdapter { diff --git a/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewProvider.java b/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewProvider.java index 7aa83adfd275..1de4d4a96357 100644 --- a/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewProvider.java +++ b/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewProvider.java @@ -66,7 +66,7 @@ public abstract class DiffPreviewProvider { " long value;\n" + "\n" + " void foo() {\n" + - " // Left changes\n" + + " // Right changes\n" + " }\n" + "\n" + " void removedFromLeft() {}\n" + diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectAllOccurrencesAction.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectAllOccurrencesAction.java index d135b528f5e2..3e284ff6b2cc 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectAllOccurrencesAction.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectAllOccurrencesAction.java @@ -15,9 +15,9 @@ */ package com.intellij.openapi.editor.actions; -import com.intellij.find.FindManager; -import com.intellij.find.FindModel; -import com.intellij.find.FindResult; +import com.intellij.find.*; +import com.intellij.find.editorHeaderActions.EditorHeaderAction; +import com.intellij.find.editorHeaderActions.SelectAllAction; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; @@ -27,6 +27,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import org.jetbrains.annotations.Nullable; +import java.util.Iterator; + public class SelectAllOccurrencesAction extends EditorAction { protected SelectAllOccurrencesAction() { super(new Handler()); @@ -39,7 +41,9 @@ public class SelectAllOccurrencesAction extends EditorAction { } @Override - public void doExecute(Editor editor, @Nullable Caret c, DataContext dataContext) { + public void doExecute(final Editor editor, @Nullable Caret c, DataContext dataContext) { + if (executeEquivalentFindPanelAction(editor, dataContext)) return; + Caret caret = c == null ? editor.getCaretModel().getPrimaryCaret() : c; boolean wholeWordsSearch = false; @@ -58,25 +62,36 @@ public class SelectAllOccurrencesAction extends EditorAction { } int caretShiftFromSelectionStart = caret.getOffset() - caret.getSelectionStart(); - FindManager findManager = FindManager.getInstance(project); + final FindManager findManager = FindManager.getInstance(project); + + final FindModel model = getFindModel(selectedText, wholeWordsSearch); - FindModel model = new FindModel(); - model.setStringToFind(selectedText); - model.setCaseSensitive(true); - model.setWholeWordsOnly(wholeWordsSearch); + FindUtil.selectSearchResultsInEditor(editor, new Iterator<FindResult>() { + FindResult findResult = findManager.findString(editor.getDocument().getCharsSequence(), 0, model); - int searchStartOffset = 0; - FindResult findResult = findManager.findString(editor.getDocument().getCharsSequence(), searchStartOffset, model); - while (findResult.isStringFound()) { - int newCaretOffset = caretShiftFromSelectionStart + findResult.getStartOffset(); - EditorActionUtil.makePositionVisible(editor, newCaretOffset); - Caret newCaret = editor.getCaretModel().addCaret(editor.offsetToVisualPosition(newCaretOffset)); - if (newCaret != null) { - setSelection(editor, newCaret, findResult); + @Override + public boolean hasNext() { + return findResult.isStringFound(); } - findResult = findManager.findString(editor.getDocument().getCharsSequence(), findResult.getEndOffset(), model); - } + + @Override + public FindResult next() { + FindResult result = findResult; + findResult = findManager.findString(editor.getDocument().getCharsSequence(), findResult.getEndOffset(), model); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }, caretShiftFromSelectionStart); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } + + @Override + protected EditorHeaderAction getEquivalentFindPanelAction(EditorSearchComponent searchComponent) { + return new SelectAllAction(searchComponent); + } } } diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectNextOccurrenceAction.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectNextOccurrenceAction.java index cc4783b4d4db..7d3c408ee152 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectNextOccurrenceAction.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectNextOccurrenceAction.java @@ -15,9 +15,9 @@ */ package com.intellij.openapi.editor.actions; -import com.intellij.find.FindManager; -import com.intellij.find.FindModel; -import com.intellij.find.FindResult; +import com.intellij.find.*; +import com.intellij.find.editorHeaderActions.AddOccurrenceAction; +import com.intellij.find.editorHeaderActions.EditorHeaderAction; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; @@ -40,6 +40,8 @@ public class SelectNextOccurrenceAction extends EditorAction { @Override public void doExecute(Editor editor, @Nullable Caret c, DataContext dataContext) { + if (executeEquivalentFindPanelAction(editor, dataContext)) return; + Caret caret = c == null ? editor.getCaretModel().getPrimaryCaret() : c; TextRange wordSelectionRange = getSelectionRange(editor, caret); boolean notFoundPreviously = getAndResetNotFoundStatus(editor); @@ -52,26 +54,21 @@ public class SelectNextOccurrenceAction extends EditorAction { } FindManager findManager = FindManager.getInstance(project); - FindModel model = new FindModel(); - model.setStringToFind(selectedText); - model.setCaseSensitive(true); - model.setWholeWordsOnly(wholeWordSearch); + FindModel model = getFindModel(selectedText, wholeWordSearch); + + findManager.setFindWasPerformed(); + findManager.setFindNextModel(model); int searchStartOffset = notFoundPreviously ? 0 : caret.getSelectionEnd(); FindResult findResult = findManager.findString(editor.getDocument().getCharsSequence(), searchStartOffset, model); if (findResult.isStringFound()) { - int newCaretOffset = caret.getOffset() - caret.getSelectionStart() + findResult.getStartOffset(); - EditorActionUtil.makePositionVisible(editor, newCaretOffset); - Caret newCaret = editor.getCaretModel().addCaret(editor.offsetToVisualPosition(newCaretOffset)); - if (newCaret == null) { + boolean caretAdded = FindUtil.selectSearchResultInEditor(editor, findResult, caret.getOffset() - caret.getSelectionStart()); + if (!caretAdded) { // this means that the found occurence is already selected if (notFoundPreviously) { setNotFoundStatus(editor); // to make sure we won't show hint anymore if there are no more occurrences } } - else { - setSelection(editor, newCaret, findResult); - } } else { setNotFoundStatus(editor); @@ -87,5 +84,10 @@ public class SelectNextOccurrenceAction extends EditorAction { } editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } + + @Override + protected EditorHeaderAction getEquivalentFindPanelAction(EditorSearchComponent searchComponent) { + return new AddOccurrenceAction(searchComponent); + } } } diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectOccurrencesActionHandler.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectOccurrencesActionHandler.java index c43eae703fd5..7b189281dfd0 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectOccurrencesActionHandler.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectOccurrencesActionHandler.java @@ -19,8 +19,11 @@ import com.intellij.codeInsight.editorActions.SelectWordUtil; import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.hint.HintManagerImpl; import com.intellij.codeInsight.hint.HintUtil; +import com.intellij.find.EditorSearchComponent; import com.intellij.find.FindBundle; -import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.find.FindModel; +import com.intellij.find.editorHeaderActions.EditorHeaderAction; +import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorLastActionTracker; @@ -28,13 +31,24 @@ import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.ui.LightweightHint; +import com.intellij.util.containers.HashSet; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; +import java.util.Set; + abstract public class SelectOccurrencesActionHandler extends EditorActionHandler { private static final Key<Boolean> NOT_FOUND = Key.create("select.next.occurence.not.found"); private static final Key<Boolean> WHOLE_WORDS = Key.create("select.next.occurence.whole.words"); + private static final Set<String> SELECT_ACTIONS = new HashSet<String>(Arrays.asList( + IdeActions.ACTION_SELECT_NEXT_OCCURENCE, + IdeActions.ACTION_UNSELECT_PREVIOUS_OCCURENCE, + IdeActions.ACTION_FIND_NEXT, + IdeActions.ACTION_FIND_PREVIOUS + )); + protected static void setSelection(Editor editor, Caret caret, TextRange selectionRange) { EditorActionUtil.makePositionVisible(editor, selectionRange.getStartOffset()); EditorActionUtil.makePositionVisible(editor, selectionRange.getEndOffset()); @@ -83,6 +97,35 @@ abstract public class SelectOccurrencesActionHandler extends EditorActionHandler protected static boolean isRepeatedActionInvocation() { String lastActionId = EditorLastActionTracker.getInstance().getLastActionId(); - return IdeActions.ACTION_SELECT_NEXT_OCCURENCE.equals(lastActionId) || IdeActions.ACTION_UNSELECT_PREVIOUS_OCCURENCE.equals(lastActionId); + return SELECT_ACTIONS.contains(lastActionId); + } + + protected static FindModel getFindModel(String text, boolean wholeWords) { + FindModel model = new FindModel(); + model.setStringToFind(text); + model.setCaseSensitive(true); + model.setWholeWordsOnly(wholeWords); + return model; + } + + protected boolean executeEquivalentFindPanelAction(Editor editor, DataContext context) { + if (editor.getHeaderComponent() instanceof EditorSearchComponent) { + EditorSearchComponent searchComponent = (EditorSearchComponent)editor.getHeaderComponent(); + EditorHeaderAction action = getEquivalentFindPanelAction(searchComponent); + if (action != null) { + Presentation presentation = new Presentation(); + AnActionEvent event = new AnActionEvent(null, context, ActionPlaces.MAIN_MENU, presentation, ActionManager.getInstance(), 0); + action.update(event); + if (presentation.isEnabled()) { + action.actionPerformed(event); + return true; + } + } + } + return false; + } + + protected EditorHeaderAction getEquivalentFindPanelAction(EditorSearchComponent searchComponent) { + return null; } } diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/UnselectPreviousOccurrenceAction.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/UnselectPreviousOccurrenceAction.java index a5657ff89b27..fffff56b12dc 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/UnselectPreviousOccurrenceAction.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/UnselectPreviousOccurrenceAction.java @@ -15,6 +15,9 @@ */ package com.intellij.openapi.editor.actions; +import com.intellij.find.EditorSearchComponent; +import com.intellij.find.editorHeaderActions.EditorHeaderAction; +import com.intellij.find.editorHeaderActions.RemoveOccurrenceAction; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; @@ -35,6 +38,8 @@ public class UnselectPreviousOccurrenceAction extends EditorAction { @Override public void doExecute(Editor editor, @Nullable Caret caret, DataContext dataContext) { + if (executeEquivalentFindPanelAction(editor, dataContext)) return; + if (editor.getCaretModel().getCaretCount() > 1) { editor.getCaretModel().removeCaret(editor.getCaretModel().getPrimaryCaret()); } @@ -44,5 +49,10 @@ public class UnselectPreviousOccurrenceAction extends EditorAction { getAndResetNotFoundStatus(editor); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } + + @Override + protected EditorHeaderAction getEquivalentFindPanelAction(EditorSearchComponent searchComponent) { + return new RemoveOccurrenceAction(searchComponent); + } } } diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java index 2f5485d6deae..b1385686bc3a 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java @@ -149,12 +149,19 @@ public class DirectoryIndexImpl extends DirectoryIndex { @Override public DirectoryInfo getInfoForDirectory(@NotNull VirtualFile dir) { + DirectoryInfo info = getInfoForFile(dir); + return info.isInProject() ? info : null; + } + + @NotNull + @Override + public DirectoryInfo getInfoForFile(@NotNull VirtualFile file) { checkAvailability(); dispatchPendingEvents(); - if (!(dir instanceof NewVirtualFile)) return null; + if (!(file instanceof NewVirtualFile)) return NonProjectDirectoryInfo.NOT_SUPPORTED_VIRTUAL_FILE_IMPLEMENTATION; - return getRootIndex().getInfoForDirectory(dir); + return getRootIndex().getInfoForFile(file); } @Override @@ -167,22 +174,6 @@ public class DirectoryIndexImpl extends DirectoryIndex { } @Override - public boolean isProjectExcludeRoot(@NotNull VirtualFile dir) { - checkAvailability(); - if (!(dir instanceof NewVirtualFile)) return false; - - return getRootIndex().isProjectExcludeRoot(dir); - } - - @Override - public boolean isModuleExcludeRoot(@NotNull VirtualFile dir) { - checkAvailability(); - if (!(dir instanceof NewVirtualFile)) return false; - - return getRootIndex().isModuleExcludeRoot(dir); - } - - @Override public String getPackageName(@NotNull VirtualFile dir) { checkAvailability(); if (!(dir instanceof NewVirtualFile)) return null; diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdater.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdaterImpl.java index cb4b94016611..a96432e2b64c 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdater.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdaterImpl.java @@ -53,7 +53,7 @@ import java.io.IOException; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -public class PushedFilePropertiesUpdater { +public class PushedFilePropertiesUpdaterImpl extends PushedFilePropertiesUpdater { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.PushedFilePropertiesUpdater"); private final Project myProject; @@ -62,12 +62,7 @@ public class PushedFilePropertiesUpdater { private final Queue<Runnable> myTasks = new ConcurrentLinkedQueue<Runnable>(); private final MessageBusConnection myConnection; - @NotNull - public static PushedFilePropertiesUpdater getInstance(Project project) { - return project.getComponent(PushedFilePropertiesUpdater.class); - } - - public PushedFilePropertiesUpdater(final Project project) { + public PushedFilePropertiesUpdaterImpl(final Project project) { myProject = project; myPushers = Extensions.getExtensions(FilePropertyPusher.EP_NAME); myFilePushers = ContainerUtil.findAllAsArray(myPushers, new Condition<FilePropertyPusher>() { @@ -122,22 +117,24 @@ public class PushedFilePropertiesUpdater { }); } + @Override public void initializeProperties() { for (final FilePropertyPusher pusher : myPushers) { pusher.initExtra(myProject, myProject.getMessageBus(), new FilePropertyPusher.Engine() { @Override public void pushAll() { - PushedFilePropertiesUpdater.this.pushAll(pusher); + PushedFilePropertiesUpdaterImpl.this.pushAll(pusher); } @Override public void pushRecursively(VirtualFile file, Project project) { - PushedFilePropertiesUpdater.this.schedulePushRecursively(file, pusher); + PushedFilePropertiesUpdaterImpl.this.schedulePushRecursively(file, pusher); } }); } } + @Override public void pushAllPropertiesNow() { performPushTasks(); doPushAll(myPushers); @@ -222,6 +219,7 @@ public class PushedFilePropertiesUpdater { return projectValue != null ? projectValue : pusher.getDefaultValue(); } + @Override public void pushAll(final FilePropertyPusher... pushers) { queueTask(new Runnable() { @Override @@ -289,7 +287,7 @@ public class PushedFilePropertiesUpdater { pusher = pushers[i]; if (!isDir && (pusher.pushDirectoriesOnly() || !pusher.acceptsFile(fileOrDir))) continue; else if (isDir && !pusher.acceptsDirectory(fileOrDir, myProject)) continue; - findAndUpdateValue(myProject, fileOrDir, pusher, moduleValues != null ? moduleValues[i]:null); + findAndUpdateValue(fileOrDir, pusher, moduleValues != null ? moduleValues[i]:null); } } catch (AbstractMethodError ame) { // acceptsDirectory is missed @@ -298,17 +296,18 @@ public class PushedFilePropertiesUpdater { } } - public static <T> void findAndUpdateValue(final Project project, final VirtualFile fileOrDir, final FilePropertyPusher<T> pusher, final T moduleValue) { - final T value = findPusherValuesUpwards(project, fileOrDir, pusher, moduleValue); - updateValue(fileOrDir, value, pusher); + @Override + public <T> void findAndUpdateValue(final VirtualFile fileOrDir, final FilePropertyPusher<T> pusher, final T moduleValue) { + final T value = findPusherValuesUpwards(myProject, fileOrDir, pusher, moduleValue); + updateValue(myProject, fileOrDir, value, pusher); } - private static <T> void updateValue(final VirtualFile fileOrDir, final T value, final FilePropertyPusher<T> pusher) { + private static <T> void updateValue(final Project project, final VirtualFile fileOrDir, final T value, final FilePropertyPusher<T> pusher) { final T oldValue = fileOrDir.getUserData(pusher.getFileDataKey()); if (value != oldValue) { fileOrDir.putUserData(pusher.getFileDataKey(), value); try { - pusher.persistAttribute(fileOrDir, value); + pusher.persistAttribute(project, fileOrDir, value); } catch (IOException e) { LOG.error(e); @@ -316,7 +315,8 @@ public class PushedFilePropertiesUpdater { } } - public static void filePropertiesChanged(@NotNull final VirtualFile file) { + @Override + public void filePropertiesChanged(@NotNull final VirtualFile file) { ApplicationManager.getApplication().assertReadAccessAllowed(); FileBasedIndex.getInstance().requestReindex(file); for (final Project project : ProjectManager.getInstance().getOpenProjects()) { @@ -344,6 +344,7 @@ public class PushedFilePropertiesUpdater { } } + @Override public void processPendingEvents() { myConnection.deliverImmediately(); } diff --git a/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java b/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java index 5937af147648..0978f8f09ec7 100644 --- a/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java +++ b/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java @@ -109,11 +109,11 @@ public class RegistryUi implements Disposable { final RegistryValue value = myModel.getRegistryValue(selected); String desc = value.getDescription(); if (value.isRestartRequired()) { - String required = "Requires IDE restart."; + String required = " Requires IDE restart."; if (desc.endsWith(".")) { desc += required; } else { - desc += ". " + required; + desc += "." + required; } } myDescriptionLabel.setText(desc); diff --git a/platform/lang-impl/src/com/intellij/packageDependencies/ui/ProjectPatternProvider.java b/platform/lang-impl/src/com/intellij/packageDependencies/ui/ProjectPatternProvider.java index 5fd50cfeaf6c..df0a5caa1f97 100644 --- a/platform/lang-impl/src/com/intellij/packageDependencies/ui/ProjectPatternProvider.java +++ b/platform/lang-impl/src/com/intellij/packageDependencies/ui/ProjectPatternProvider.java @@ -27,11 +27,14 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.ToggleAction; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.packageDependencies.DependencyUISettings; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.search.scope.packageSet.FilePatternPackageSet; import com.intellij.psi.search.scope.packageSet.PackageSet; @@ -103,7 +106,10 @@ public class ProjectPatternProvider extends PatternDialectProvider { pattern += recursively ? "*/" : "*"; } } - return new FilePatternPackageSet(getModulePattern(node), pattern); + final VirtualFile vDir = ((DirectoryNode)node).getDirectory(); + final PsiElement psiElement = node.getPsiElement(); + final Module module = psiElement != null ? ModuleUtilCore.findModuleForFile(vDir, psiElement.getProject()) : null; + return new FilePatternPackageSet(module != null ? module.getName() : null, pattern); } else if (node instanceof FileNode) { if (recursively) return null; diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/AddScopeUtil.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/AddScopeUtil.java new file mode 100644 index 000000000000..d92445254f28 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/AddScopeUtil.java @@ -0,0 +1,117 @@ +/* + * 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. + */ + +/* + * User: anna + * Date: 14-May-2009 + */ +package com.intellij.profile.codeInspection.ui; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInspection.ex.Descriptor; +import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.codeInspection.ex.InspectionToolWrapper; +import com.intellij.codeInspection.ex.ScopeToolState; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionConfigTreeNode; +import com.intellij.psi.search.scope.packageSet.CustomScopesProviderEx; +import com.intellij.psi.search.scope.packageSet.NamedScope; +import com.intellij.psi.search.scope.packageSet.NamedScopesHolder; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.ui.treeStructure.treetable.TreeTable; +import com.intellij.util.ArrayUtil; + +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.util.*; + +public class AddScopeUtil { + public static ScopeToolState performAddScope(final TreeTable treeTable, + final Project project, + final InspectionProfileImpl inspectionProfile, + final Collection<InspectionConfigTreeNode> selectedNodes) { + final List<InspectionConfigTreeNode> nodes = new ArrayList<InspectionConfigTreeNode>(); + final List<Descriptor> descriptors = new ArrayList<Descriptor>(); + for (final InspectionConfigTreeNode node : selectedNodes) { + collect(descriptors, nodes, node); + } + + final List<String> availableScopes = getAvailableScopes(descriptors, project, inspectionProfile); + final int idx = Messages.showChooseDialog(treeTable, "Scope:", "Choose Scope", ArrayUtil.toStringArray(availableScopes), availableScopes.get(0), Messages.getQuestionIcon()); + if (idx == -1) return null; + final NamedScope chosenScope = NamedScopesHolder.getScope(project, availableScopes.get(idx)); + + ScopeToolState scopeToolState = null; + final Tree tree = treeTable.getTree(); + + for (final InspectionConfigTreeNode node : nodes) { + final Descriptor descriptor = node.getDefaultDescriptor(); + final InspectionToolWrapper toolWrapper = descriptor.getToolWrapper().createCopy(); //copy + final HighlightDisplayLevel level = inspectionProfile.getErrorLevel(descriptor.getKey(), chosenScope, project); + final boolean enabled = inspectionProfile.isToolEnabled(descriptor.getKey()); + scopeToolState = inspectionProfile.addScope(toolWrapper, chosenScope, level, enabled, project); + node.dropCache(); + ((DefaultTreeModel)tree.getModel()).reload(node); + tree.expandPath(new TreePath(node.getPath())); + } + tree.revalidate(); + return scopeToolState; + } + + private static void collect(final List<Descriptor> descriptors, + final List<InspectionConfigTreeNode> nodes, + final InspectionConfigTreeNode node) { + final ToolDescriptors currentDescriptors = node.getDescriptors(); + if (currentDescriptors != null) { + nodes.add(node); + descriptors.add(currentDescriptors.getDefaultDescriptor()); + descriptors.addAll(currentDescriptors.getNonDefaultDescriptors()); + } else if (node.getUserObject() instanceof String) { + for(int i = 0; i < node.getChildCount(); i++) { + final InspectionConfigTreeNode childNode = (InspectionConfigTreeNode)node.getChildAt(i); + collect(descriptors, nodes, childNode); + } + } + } + + private static List<String> getAvailableScopes(final List<Descriptor> descriptors, final Project project, final InspectionProfileImpl inspectionProfile) { + final ArrayList<NamedScope> scopes = new ArrayList<NamedScope>(); + for (final NamedScopesHolder holder : NamedScopesHolder.getAllNamedScopeHolders(project)) { + Collections.addAll(scopes, holder.getScopes()); + } + scopes.remove(CustomScopesProviderEx.getAllScope()); + + CustomScopesProviderEx.filterNoSettingsScopes(project, scopes); + + final Set<NamedScope> used = new HashSet<NamedScope>(); + for (final Descriptor descriptor : descriptors) { + final List<ScopeToolState> nonDefaultTools = inspectionProfile.getNonDefaultTools(descriptor.getKey().toString(), project); + if (nonDefaultTools != null) { + for (final ScopeToolState state : nonDefaultTools) { + used.add(state.getScope(project)); + } + } + } + scopes.removeAll(used); + + final List<String> availableScopes = new ArrayList<String>(); + for (final NamedScope scope : scopes) { + availableScopes.add(scope.getName()); + } + return availableScopes; + } +}
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionToolsConfigurable.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionToolsConfigurable.java index ad2b44494044..794945268d19 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionToolsConfigurable.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionToolsConfigurable.java @@ -151,7 +151,7 @@ public abstract class InspectionToolsConfigurable extends BaseConfigurable imple final Set<String> levels = new HashSet<String>(); for (Object o : rootElement.getChildren("inspection_tool")) { final Element inspectElement = (Element)o; - levels.add(inspectElement.getAttributeValue("level")); + levels.add(inspectElement.getAttributeValue("l")); for (Object s : inspectElement.getChildren("scope")) { levels.add(((Element)s).getAttributeValue("level")); } diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionsAggregationUtil.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionsAggregationUtil.java new file mode 100644 index 000000000000..3707c034ec04 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionsAggregationUtil.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui; + +import com.intellij.codeInsight.daemon.HighlightDisplayKey; +import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionConfigTreeNode; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.Queue; +import gnu.trove.THashSet; + +import javax.swing.tree.TreePath; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * @author Dmitry Batkovich + */ +public class InspectionsAggregationUtil { + public static List<HighlightDisplayKey> getInspectionsKeys(final InspectionConfigTreeNode node) { + return ContainerUtil.map(getInspectionsNodes(node), new Function<InspectionConfigTreeNode, HighlightDisplayKey>() { + @Override + public HighlightDisplayKey fun(final InspectionConfigTreeNode node) { + return node.getKey(); + } + }); + } + + public static List<InspectionConfigTreeNode> getInspectionsNodes(final InspectionConfigTreeNode node) { + final Queue<InspectionConfigTreeNode> q = new Queue<InspectionConfigTreeNode>(1); + q.addLast(node); + return getInspectionsNodes(q); + } + + public static List<InspectionConfigTreeNode> getInspectionsNodes(final TreePath[] paths) { + final Queue<InspectionConfigTreeNode> q = new Queue<InspectionConfigTreeNode>(paths.length); + for (final TreePath path : paths) { + if (path != null) { + q.addLast((InspectionConfigTreeNode)path.getLastPathComponent()); + } + } + return getInspectionsNodes(q); + } + + private static List<InspectionConfigTreeNode> getInspectionsNodes(final Queue<InspectionConfigTreeNode> queue) { + final Set<InspectionConfigTreeNode> nodes = new THashSet<InspectionConfigTreeNode>(); + while (!queue.isEmpty()) { + final InspectionConfigTreeNode node = queue.pullFirst(); + if (node.getDescriptors() == null) { + for (int i = 0; i < node.getChildCount(); i++) { + final InspectionConfigTreeNode childNode = (InspectionConfigTreeNode) node.getChildAt(i); + queue.addLast(childNode); + } + } else { + nodes.add(node); + } + } + return new ArrayList<InspectionConfigTreeNode>(nodes); + } +} diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/LevelChooser.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/LevelChooser.java index 2229550d192f..5bc792545c96 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/LevelChooser.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/LevelChooser.java @@ -56,7 +56,8 @@ public class LevelChooser extends ComboboxWithBrowseButton { addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - final SeverityEditorDialog dlg = new SeverityEditorDialog(LevelChooser.this, (HighlightSeverity)getComboBox().getSelectedItem(), severityRegistrar); + final SeverityEditorDialog dlg = + new SeverityEditorDialog(LevelChooser.this, (HighlightSeverity)getComboBox().getSelectedItem(), severityRegistrar); dlg.show(); if (dlg.isOK()) { final Object item = getComboBox().getSelectedItem(); @@ -64,7 +65,8 @@ public class LevelChooser extends ComboboxWithBrowseButton { final HighlightInfoType type = dlg.getSelectedType(); if (type != null) { getComboBox().setSelectedItem(type.getSeverity(null)); - } else { + } + else { getComboBox().setSelectedItem(item); } } diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/LevelChooserAction.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/LevelChooserAction.java index ec2fbce96cc5..1067a60ed358 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/LevelChooserAction.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/LevelChooserAction.java @@ -19,6 +19,7 @@ import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; import com.intellij.codeInsight.daemon.impl.SeverityUtil; +import com.intellij.codeInspection.ex.InspectionProfileImpl; import com.intellij.codeInspection.ex.SeverityEditorDialog; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.actionSystem.AnAction; @@ -26,6 +27,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.ex.ComboBoxAction; +import com.intellij.profile.codeInspection.SeverityProvider; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -40,24 +42,15 @@ public abstract class LevelChooserAction extends ComboBoxAction { private final SeverityRegistrar mySeverityRegistrar; private HighlightSeverity myChosen = null; - public LevelChooserAction(final SeverityRegistrar severityRegistrar) { - mySeverityRegistrar = severityRegistrar; + public LevelChooserAction(final InspectionProfileImpl profile) { + mySeverityRegistrar = ((SeverityProvider)profile.getProfileManager()).getOwnSeverityRegistrar(); } @NotNull @Override - protected DefaultActionGroup createPopupActionGroup(final JComponent button) { + public DefaultActionGroup createPopupActionGroup(final JComponent anchor) { final DefaultActionGroup group = new DefaultActionGroup(); - - final SortedSet<HighlightSeverity> severities = new TreeSet<HighlightSeverity>(mySeverityRegistrar); - for (final SeverityRegistrar.SeverityBasedTextAttributes type : SeverityUtil.getRegisteredHighlightingInfoTypes(mySeverityRegistrar)) { - severities.add(type.getSeverity()); - } - severities.add(HighlightSeverity.ERROR); - severities.add(HighlightSeverity.WARNING); - severities.add(HighlightSeverity.WEAK_WARNING); - severities.add(HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING); - for (final HighlightSeverity severity : severities) { + for (final HighlightSeverity severity : getSeverities(mySeverityRegistrar)) { final HighlightSeverityAction action = new HighlightSeverityAction(severity); if (myChosen == null) { setChosen(action.getSeverity()); @@ -68,7 +61,7 @@ public abstract class LevelChooserAction extends ComboBoxAction { group.add(new AnAction("Edit severities...") { @Override public void actionPerformed(final AnActionEvent e) { - final SeverityEditorDialog dlg = new SeverityEditorDialog(button, myChosen, mySeverityRegistrar); + final SeverityEditorDialog dlg = new SeverityEditorDialog(anchor, myChosen, mySeverityRegistrar); dlg.show(); if (dlg.isOK()) { final HighlightInfoType type = dlg.getSelectedType(); @@ -83,6 +76,18 @@ public abstract class LevelChooserAction extends ComboBoxAction { return group; } + public static SortedSet<HighlightSeverity> getSeverities(final SeverityRegistrar severityRegistrar) { + final SortedSet<HighlightSeverity> severities = new TreeSet<HighlightSeverity>(severityRegistrar); + for (final SeverityRegistrar.SeverityBasedTextAttributes type : SeverityUtil.getRegisteredHighlightingInfoTypes(severityRegistrar)) { + severities.add(type.getSeverity()); + } + severities.add(HighlightSeverity.ERROR); + severities.add(HighlightSeverity.WARNING); + severities.add(HighlightSeverity.WEAK_WARNING); + severities.add(HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING); + return severities; + } + protected abstract void onChosen(final HighlightSeverity severity); public void setChosen(final HighlightSeverity severity) { diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/MultiScopeSeverityIcon.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/MultiScopeSeverityIcon.java new file mode 100644 index 000000000000..2bf909666f82 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/MultiScopeSeverityIcon.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +/** + * @author Dmitry Batkovich + */ +public class MultiScopeSeverityIcon implements Icon { + private final int mySize; + private final List<Color> myColors; + + public MultiScopeSeverityIcon(final int size, final List<Color> colors) { + mySize = size; + myColors = colors; + } + + @Override + public void paintIcon(final Component c, final Graphics g, final int i, final int j) { + final int iconWidth = getIconWidth(); + final int iconHeightCoordinate = j + getIconHeight(); + + final int partWidth = iconWidth / myColors.size(); + + for (int idx = 0; idx < myColors.size(); idx++) { + final Color color = myColors.get(idx); + g.setColor(color); + final int x = i + partWidth * idx; + g.fillRect(x, j, x + partWidth, iconHeightCoordinate); + } + } + + @Override + public int getIconWidth() { + return mySize; + } + + @Override + public int getIconHeight() { + return mySize; + } +} diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/ScopesChooser.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/ScopesChooser.java new file mode 100644 index 000000000000..4932e7ff5ca9 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/ScopesChooser.java @@ -0,0 +1,98 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui; + +import com.intellij.codeInspection.ex.Descriptor; +import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.actionSystem.ex.ComboBoxAction; +import com.intellij.openapi.project.Project; +import com.intellij.psi.search.scope.packageSet.CustomScopesProviderEx; +import com.intellij.psi.search.scope.packageSet.NamedScope; +import com.intellij.psi.search.scope.packageSet.NamedScopesHolder; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Collections; + +/** + * @author Dmitry Batkovich + */ +public abstract class ScopesChooser extends ComboBoxAction { + + private final List<Descriptor> myDefaultDescriptors; + private final InspectionProfileImpl myInspectionProfile; + private final Project myProject; + + public ScopesChooser(final List<Descriptor> defaultDescriptors, final InspectionProfileImpl inspectionProfile, final Project project) { + myDefaultDescriptors = defaultDescriptors; + myInspectionProfile = inspectionProfile; + myProject = project; + setPopupTitle("Select a scope to change its settings"); + getTemplatePresentation().setText("In All Scopes"); + } + + @NotNull + @Override + protected DefaultActionGroup createPopupActionGroup(final JComponent button) { + final DefaultActionGroup group = new DefaultActionGroup(); + + final List<NamedScope> predefinedScopes = new ArrayList<NamedScope>(); + final List<NamedScope> customScopes = new ArrayList<NamedScope>(); + for (final NamedScopesHolder holder : NamedScopesHolder.getAllNamedScopeHolders(myProject)) { + Collections.addAll(customScopes, holder.getEditableScopes()); + predefinedScopes.addAll(holder.getPredefinedScopes()); + } + predefinedScopes.remove(CustomScopesProviderEx.getAllScope()); + fillActionGroup(group, predefinedScopes, myDefaultDescriptors, myInspectionProfile); + group.addSeparator(); + fillActionGroup(group, customScopes, myDefaultDescriptors, myInspectionProfile); + + //TODO edit scopes order + //group.addSeparator(); + //group.add(new AnAction("Edit Scopes Order...") { + // @Override + // public void actionPerformed(final AnActionEvent e) { + // + // } + //}); + + return group; + } + + protected abstract void onScopeAdded(); + + private void fillActionGroup(final DefaultActionGroup group, + final List<NamedScope> scopes, + final List<Descriptor> defaultDescriptors, + final InspectionProfileImpl inspectionProfile) { + for (final NamedScope scope : scopes) { + group.add(new AnAction(scope.getName()) { + @Override + public void actionPerformed(final AnActionEvent e) { + for (final Descriptor defaultDescriptor : defaultDescriptors) { + inspectionProfile.addScope(defaultDescriptor.getToolWrapper().createCopy(), scope, defaultDescriptor.getLevel(), true, getEventProject(e)); + } + onScopeAdded(); + } + }); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java index 62993f5cf9c8..ce5f308c1305 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java @@ -52,19 +52,25 @@ import com.intellij.profile.codeInspection.InspectionProfileManager; import com.intellij.profile.codeInspection.InspectionProfileManagerImpl; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.profile.codeInspection.SeverityProvider; -import com.intellij.profile.codeInspection.ui.actions.AddScopeAction; -import com.intellij.profile.codeInspection.ui.actions.DeleteScopeAction; -import com.intellij.profile.codeInspection.ui.actions.MoveScopeAction; +import com.intellij.profile.codeInspection.ui.filter.InspectionFilterAction; +import com.intellij.profile.codeInspection.ui.filter.InspectionsFilter; +import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionConfigTreeNode; +import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeComparator; +import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeRenderer; +import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeTable; +import com.intellij.profile.codeInspection.ui.table.ScopesAndSeveritiesTable; import com.intellij.psi.search.scope.packageSet.NamedScope; import com.intellij.ui.*; +import com.intellij.ui.components.JBLabel; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.Alarm; -import com.intellij.util.IconUtil; +import com.intellij.util.Function; import com.intellij.util.config.StorageAccessors; -import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.*; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import com.intellij.xml.util.XmlStringUtil; +import gnu.trove.THashSet; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -76,7 +82,6 @@ import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.DefaultTreeSelectionModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.*; @@ -85,6 +90,7 @@ import java.awt.event.KeyEvent; import java.io.IOException; import java.io.StringReader; import java.util.*; +import java.util.HashSet; import java.util.List; /** @@ -95,17 +101,23 @@ public class SingleInspectionProfilePanel extends JPanel { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.InspectionToolsPanel"); @NonNls private static final String INSPECTION_FILTER_HISTORY = "INSPECTION_FILTER_HISTORY"; private static final String UNDER_CONSTRUCTION = InspectionsBundle.message("inspection.tool.description.under.construction.text"); - private final Map<Descriptor, List<Descriptor>> myDescriptors = new HashMap<Descriptor, List<Descriptor>>(); + private final List<ToolDescriptors> myInitialToolDescriptors = new ArrayList<ToolDescriptors>(); private InspectionProfileImpl mySelectedProfile; private JEditorPane myBrowser; private JPanel myOptionsPanel; private JPanel myInspectionProfilePanel = null; private FilterComponent myProfileFilter; + private final InspectionsFilter myInspectionsFilter = new InspectionsFilter() { + @Override + protected void filterChanged() { + filterTree(myProfileFilter.getFilter()); + } + }; private final InspectionConfigTreeNode myRoot = - new InspectionConfigTreeNode(InspectionsBundle.message("inspection.root.node.title"), null, false, false); + new InspectionConfigTreeNode(InspectionsBundle.message("inspection.root.node.title")); private final Alarm myAlarm = new Alarm(); private boolean myModified = false; - private Tree myTree; + private InspectionsConfigTreeTable myTreeTable; private TreeExpander myTreeExpander; @NotNull private String myInitialProfile; @@ -171,21 +183,21 @@ public class SingleInspectionProfilePanel extends JPanel { } public void updateSelection() { - if (myTree != null) { - final TreePath selectionPath = myTree.getSelectionPath(); + if (myTreeTable != null) { + final TreePath selectionPath = myTreeTable.getTree().getSelectionPath(); if (selectionPath != null) { - TreeUtil.selectNode(myTree, (TreeNode)selectionPath.getLastPathComponent()); - TreeUtil.showRowCentered(myTree, myTree.getRowForPath(selectionPath), false); + TreeUtil.selectNode(myTreeTable.getTree(), (TreeNode)selectionPath.getLastPathComponent()); + TreeUtil.showRowCentered(myTreeTable.getTree(), myTreeTable.getTree().getRowForPath(selectionPath), false); } } } private void wereToolSettingsModified() { - for (Map.Entry<Descriptor, List<Descriptor>> entry : myDescriptors.entrySet()) { - Descriptor desc = entry.getKey(); + for (final ToolDescriptors toolDescriptor : myInitialToolDescriptors) { + Descriptor desc = toolDescriptor.getDefaultDescriptor(); if (wereToolSettingsModified(desc)) return; - List<Descriptor> descriptors = entry.getValue(); + List<Descriptor> descriptors = toolDescriptor.getNonDefaultDescriptors(); for (Descriptor descriptor : descriptors) { if (wereToolSettingsModified(descriptor)) return; } @@ -206,7 +218,7 @@ public class SingleInspectionProfilePanel extends JPanel { myAlarm.addRequest(new Runnable() { @Override public void run() { - myTree.repaint(); + myTreeTable.repaint(); } }, 300); myModified = true; @@ -216,10 +228,10 @@ public class SingleInspectionProfilePanel extends JPanel { } private void updateProperSettingsForSelection() { - final TreePath selectionPath = myTree.getSelectionPath(); + final TreePath selectionPath = myTreeTable.getTree().getSelectionPath(); if (selectionPath != null) { InspectionConfigTreeNode node = (InspectionConfigTreeNode)selectionPath.getLastPathComponent(); - final Descriptor descriptor = node.getDescriptor(); + final Descriptor descriptor = node.getDefaultDescriptor(); if (descriptor != null) { final boolean properSetting = mySelectedProfile.isProperSetting(descriptor.getKey().toString()); if (node.isProperSetting() != properSetting) { @@ -227,7 +239,7 @@ public class SingleInspectionProfilePanel extends JPanel { myAlarm.addRequest(new Runnable() { @Override public void run() { - myTree.repaint(); + myTreeTable.repaint(); } }, 300); node.dropCache(); @@ -237,21 +249,14 @@ public class SingleInspectionProfilePanel extends JPanel { } } - private void initDescriptors() { + private void initToolStates() { final InspectionProfileImpl profile = mySelectedProfile; if (profile == null) return; - myDescriptors.clear(); - List<ScopeToolState> tools = profile.getDefaultStates(myProjectProfileManager.getProject()); - for (ScopeToolState state : tools) { - final ArrayList<Descriptor> descriptors = new ArrayList<Descriptor>(); + myInitialToolDescriptors.clear(); + final Project project = myProjectProfileManager.getProject(); + for (final ScopeToolState state : profile.getDefaultStates(myProjectProfileManager.getProject())) { if (!accept(state.getTool())) continue; - Project project = myProjectProfileManager.getProject(); - myDescriptors.put(new Descriptor(state, profile, project), descriptors); - InspectionToolWrapper toolWrapper = state.getTool(); - final List<ScopeToolState> nonDefaultTools = profile.getNonDefaultTools(toolWrapper.getShortName(), project); - for (ScopeToolState nonDefaultToolState : nonDefaultTools) { - descriptors.add(new Descriptor(nonDefaultToolState, profile, project)); - } + myInitialToolDescriptors.add(ToolDescriptors.fromScopeToolState(state, profile, project)); } } @@ -267,7 +272,7 @@ public class SingleInspectionProfilePanel extends JPanel { } fillTreeData(myProfileFilter.getFilter(), true); repaintTableData(); - updateOptionsAndDescriptionPanel(myTree.getSelectionPath()); + updateOptionsAndDescriptionPanel(myTreeTable.getTree().getSelectionPaths()); } @Nullable @@ -311,22 +316,26 @@ public class SingleInspectionProfilePanel extends JPanel { myProfileFilter.setFilter(filter); } - public void filterTree(String filter) { - if (myTree != null) { - getExpandedNodes(mySelectedProfile).saveVisibleState(myTree); + private void filterTree(@Nullable String filter) { + if (myTreeTable != null) { + getExpandedNodes(mySelectedProfile).saveVisibleState(myTreeTable.getTree()); fillTreeData(filter, true); reloadModel(); restoreTreeState(); - if (myTree.getSelectionPath() == null) { - TreeUtil.selectFirstNode(myTree); + if (myTreeTable.getTree().getSelectionPath() == null) { + TreeUtil.selectFirstNode(myTreeTable.getTree()); } } } + private void filterTree() { + filterTree(myProfileFilter != null ? myProfileFilter.getFilter() : null); + } + private void reloadModel() { try { myIsInRestore = true; - ((DefaultTreeModel)myTree.getModel()).reload(); + ((DefaultTreeModel)myTreeTable.getTree().getModel()).reload(); } finally { myIsInRestore = false; @@ -338,7 +347,7 @@ public class SingleInspectionProfilePanel extends JPanel { try { myIsInRestore = true; - getExpandedNodes(mySelectedProfile).restoreVisibleState(myTree); + getExpandedNodes(mySelectedProfile).restoreVisibleState(myTreeTable.getTree()); } finally { myIsInRestore = false; @@ -349,13 +358,17 @@ public class SingleInspectionProfilePanel extends JPanel { final CommonActionsManager actionManager = CommonActionsManager.getInstance(); DefaultActionGroup actions = new DefaultActionGroup(); - actions.add(actionManager.createExpandAllAction(myTreeExpander, myTree)); - actions.add(actionManager.createCollapseAllAction(myTreeExpander, myTree)); + + actions.add(new InspectionFilterAction(mySelectedProfile, myInspectionsFilter)); + actions.addSeparator(); + + actions.add(actionManager.createExpandAllAction(myTreeExpander, myTreeTable)); + actions.add(actionManager.createCollapseAllAction(myTreeExpander, myTreeTable)); actions.add(new AnAction(CommonBundle.message("button.reset.to.default"), CommonBundle.message("button.reset.to.default"), AllIcons.General.Reset) { { - registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_MASK)), myTree); + registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_MASK)), myTreeTable); } @Override public void update(AnActionEvent e) { @@ -395,41 +408,14 @@ public class SingleInspectionProfilePanel extends JPanel { } }); - actions.addSeparator(); - actions.add(new MyAddScopeAction()); - actions.add(new MyDeleteScopeAction()); - actions.add(new MoveScopeAction(myTree, "Move Scope Up", IconUtil.getMoveUpIcon(), -1) { - @Override - protected boolean isEnabledFor(int idx, InspectionConfigTreeNode parent) { - return idx > 0; - } - - @Override - protected InspectionProfileImpl getSelectedProfile() { - return mySelectedProfile; - } - }); - actions.add(new MoveScopeAction(myTree, "Move Scope Down", IconUtil.getMoveDownIcon(), 1) { - @Override - protected boolean isEnabledFor(int idx, InspectionConfigTreeNode parent) { - return idx < parent.getChildCount() - 2; - } - - @Override - protected InspectionProfileImpl getSelectedProfile() { - return mySelectedProfile; - } - }); - actions.addSeparator(); - final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, actions, true); actionToolbar.setTargetComponent(this); return actionToolbar; } private void repaintTableData() { - if (myTree != null) { - getExpandedNodes(mySelectedProfile).saveVisibleState(myTree); + if (myTreeTable != null) { + getExpandedNodes(mySelectedProfile).saveVisibleState(myTreeTable.getTree()); reloadModel(); restoreTreeState(); } @@ -438,8 +424,8 @@ public class SingleInspectionProfilePanel extends JPanel { public void selectInspectionTool(String name) { final InspectionConfigTreeNode node = findNodeByKey(name, myRoot); if (node != null) { - TreeUtil.showRowCentered(myTree, myTree.getRowForPath(new TreePath(node.getPath())) - 1, true);//myTree.isRootVisible ? 0 : 1; - TreeUtil.selectNode(myTree, node); + TreeUtil.showRowCentered(myTreeTable.getTree(), myTreeTable.getTree().getRowForPath(new TreePath(node.getPath())) - 1, true);//myTree.isRootVisible ? 0 : 1; + TreeUtil.selectNode(myTreeTable.getTree(), node); } } @@ -447,7 +433,7 @@ public class SingleInspectionProfilePanel extends JPanel { private static InspectionConfigTreeNode findNodeByKey(String name, InspectionConfigTreeNode root) { for (int i = 0; i < root.getChildCount(); i++) { final InspectionConfigTreeNode child = (InspectionConfigTreeNode)root.getChildAt(i); - final Descriptor descriptor = child.getDescriptor(); + final Descriptor descriptor = child.getDefaultDescriptor(); if (descriptor != null) { if (descriptor.getKey().toString().equals(name)) { return child; @@ -462,42 +448,37 @@ public class SingleInspectionProfilePanel extends JPanel { } private JScrollPane initTreeScrollPane() { - fillTreeData(null, true); - final InspectionsConfigTreeRenderer renderer = new InspectionsConfigTreeRenderer(myProjectProfileManager.getProject()){ + final InspectionsConfigTreeRenderer renderer = new InspectionsConfigTreeRenderer(){ @Override protected String getFilter() { return myProfileFilter != null ? myProfileFilter.getFilter() : null; } }; - myTree = new CheckboxTree(renderer, myRoot) { + myTreeTable = new InspectionsConfigTreeTable(new InspectionsConfigTreeTable.InspectionsConfigTreeTableSettings(myRoot, myProjectProfileManager.getProject()) { @Override - public Dimension getPreferredScrollableViewportSize() { - Dimension size = super.getPreferredScrollableViewportSize(); - size = new Dimension(size.width + 10, size.height); - return size; + protected void onChanged(final InspectionConfigTreeNode node) { + updateOptionsAndDescriptionPanel(); + updateUpHierarchy(node, (InspectionConfigTreeNode)node.getParent()); } @Override - protected void onNodeStateChanged(final CheckedTreeNode node) { - toggleToolNode((InspectionConfigTreeNode)node); + public InspectionProfileImpl getInspectionProfile() { + return mySelectedProfile; } - }; - - - myTree.setCellRenderer(renderer); - myTree.setRootVisible(false); - myTree.setShowsRootHandles(true); - UIUtil.setLineStyleAngled(myTree); - TreeUtil.installActions(myTree); + }); + myTreeTable.setTreeCellRenderer(renderer); + myTreeTable.setRootVisible(false); + UIUtil.setLineStyleAngled(myTreeTable.getTree()); + TreeUtil.installActions(myTreeTable.getTree()); - myTree.addTreeSelectionListener(new TreeSelectionListener() { + myTreeTable.getTree().addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { - if (myTree.getSelectionPaths() != null && myTree.getSelectionPaths().length == 1) { - updateOptionsAndDescriptionPanel(myTree.getSelectionPaths()[0]); + if (myTreeTable.getTree().getSelectionPaths() != null) { + updateOptionsAndDescriptionPanel(myTreeTable.getTree().getSelectionPaths()); } else { initOptionsAndDescriptionPanel(); @@ -508,9 +489,9 @@ public class SingleInspectionProfilePanel extends JPanel { if (selected != null) { InspectionProfileImpl baseProfile = (InspectionProfileImpl)selected.getParentProfile(); if (baseProfile != null) { - getExpandedNodes(baseProfile).setSelectionPaths(myTree.getSelectionPaths()); + getExpandedNodes(baseProfile).setSelectionPaths(myTreeTable.getTree().getSelectionPaths()); } - getExpandedNodes(selected).setSelectionPaths(myTree.getSelectionPaths()); + getExpandedNodes(selected).setSelectionPaths(myTreeTable.getTree().getSelectionPaths()); } } @@ -518,36 +499,35 @@ public class SingleInspectionProfilePanel extends JPanel { }); - myTree.addMouseListener(new PopupHandler() { + myTreeTable.addMouseListener(new PopupHandler() { @Override public void invokePopup(Component comp, int x, int y) { - final int[] selectionRows = myTree.getSelectionRows(); - if (selectionRows != null && myTree.getPathForLocation(x, y) != null && Arrays.binarySearch(selectionRows, myTree.getRowForLocation(x, y)) > -1) - { + final int[] selectionRows = myTreeTable.getTree().getSelectionRows(); + if (selectionRows != null && + myTreeTable.getTree().getPathForLocation(x, y) != null && + Arrays.binarySearch(selectionRows, myTreeTable.getTree().getRowForLocation(x, y)) > -1) { compoundPopup().show(comp, x, y); } } }); - new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() { + new TreeSpeedSearch(myTreeTable.getTree(), new Convertor<TreePath, String>() { @Override public String convert(TreePath o) { final InspectionConfigTreeNode node = (InspectionConfigTreeNode)o.getLastPathComponent(); - final Descriptor descriptor = node.getDescriptor(); + final Descriptor descriptor = node.getDefaultDescriptor(); return descriptor != null ? InspectionsConfigTreeComparator.getDisplayTextToSort(descriptor.getText()) : InspectionsConfigTreeComparator .getDisplayTextToSort(node.getGroupName()); } }); - myTree.setSelectionModel(new DefaultTreeSelectionModel()); - - final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree); + final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTreeTable); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - TreeUtil.collapseAll(myTree, 1); + TreeUtil.collapseAll(myTreeTable.getTree(), 1); - myTree.addTreeExpansionListener(new TreeExpansionListener() { + myTreeTable.getTree().addTreeExpansionListener(new TreeExpansionListener() { @Override @@ -556,9 +536,9 @@ public class SingleInspectionProfilePanel extends JPanel { final InspectionConfigTreeNode node = (InspectionConfigTreeNode)event.getPath().getLastPathComponent(); final InspectionProfileImpl parentProfile = (InspectionProfileImpl)selected.getParentProfile(); if (parentProfile != null) { - getExpandedNodes(parentProfile).saveVisibleState(myTree); + getExpandedNodes(parentProfile).saveVisibleState(myTreeTable.getTree()); } - getExpandedNodes(selected).saveVisibleState(myTree); + getExpandedNodes(selected).saveVisibleState(myTreeTable.getTree()); } @Override @@ -575,7 +555,17 @@ public class SingleInspectionProfilePanel extends JPanel { } }); - myTreeExpander = new DefaultTreeExpander(myTree); + myTreeExpander = new DefaultTreeExpander(myTreeTable.getTree()) { + @Override + public boolean canExpand() { + return myTreeTable.isShowing(); + } + + @Override + public boolean canCollapse() { + return myTreeTable.isShowing(); + } + }; myProfileFilter = new MyFilterComponent(); return scrollPane; @@ -603,54 +593,14 @@ public class SingleInspectionProfilePanel extends JPanel { }); } group.add(Separator.getInstance()); - group.add(new MyAddScopeAction()); - group.add(new MyDeleteScopeAction()); ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group); return menu.getComponent(); } - static String renderSeverity(HighlightSeverity severity) { + public static String renderSeverity(HighlightSeverity severity) { return StringUtil.capitalizeWords(severity.getName().toLowerCase(), true); } - private void toggleToolNode(final InspectionConfigTreeNode toolNode) { - final Descriptor descriptor = toolNode.getDescriptor(); - Project project = myProjectProfileManager.getProject(); - if (descriptor!= null) { - final HighlightDisplayKey key = descriptor.getKey(); - final String toolShortName = key.toString(); - if (toolNode.isChecked()) { - if (toolNode.getScope(project) != null){ - if (toolNode.isByDefault()) { - mySelectedProfile.enableToolByDefault(toolShortName, project); - } - else { - mySelectedProfile.enableTool(toolShortName, toolNode.getScope(project), project); - } - } else { - mySelectedProfile.enableTool(toolShortName, project); - } - } - else { - if (toolNode.getScope(project) != null) { - if (toolNode.isByDefault()) { - mySelectedProfile.disableToolByDefault(toolShortName, project); - } else { - mySelectedProfile.disableTool(toolShortName, toolNode.getScope(project), project); - } - } else if (toolNode.getChildCount() == 0){ //default node and no scopes configured - mySelectedProfile.disableTool(toolShortName, project); - } - } - toolNode.dropCache(); - updateUpHierarchy(toolNode, (InspectionConfigTreeNode)toolNode.getParent()); - } - final TreePath path = new TreePath(toolNode.getPath()); - if (Comparing.equal(myTree.getSelectionPath(), path)) { - updateOptionsAndDescriptionPanel(path); - } - } - private static void updateUpHierarchy(final InspectionConfigTreeNode node, final InspectionConfigTreeNode parent) { if (parent != null) { parent.dropCache(); @@ -701,10 +651,9 @@ public class SingleInspectionProfilePanel extends JPanel { return forceInclude; } - private void fillTreeData(String filter, boolean forceInclude) { + private void fillTreeData(@Nullable String filter, boolean forceInclude) { if (mySelectedProfile == null) return; myRoot.removeAllChildren(); - myRoot.setChecked(false); myRoot.dropCache(); List<Set<String>> keySetList = new ArrayList<Set<String>>(); final Set<String> quoted = new HashSet<String>(); @@ -712,23 +661,17 @@ public class SingleInspectionProfilePanel extends JPanel { keySetList.addAll(SearchUtil.findKeys(filter, quoted)); } Project project = myProjectProfileManager.getProject(); - for (Descriptor descriptor : myDescriptors.keySet()) { + final boolean emptyFilter = myInspectionsFilter.isEmptyFilter(); + for (ToolDescriptors toolDescriptors : myInitialToolDescriptors) { + final Descriptor descriptor = toolDescriptors.getDefaultDescriptor(); if (filter != null && !filter.isEmpty() && !isDescriptorAccepted(descriptor, filter, forceInclude, keySetList, quoted)) { continue; } - final List<ScopeToolState> nonDefaultTools = mySelectedProfile.getNonDefaultTools(descriptor.getKey().toString(), project); - final HighlightDisplayKey key = descriptor.getKey(); - final boolean enabled = mySelectedProfile.isToolEnabled(key); - boolean hasNonDefaultScope = !nonDefaultTools.isEmpty(); - final InspectionConfigTreeNode node = new InspectionConfigTreeNode(descriptor, null, !hasNonDefaultScope, enabled, !hasNonDefaultScope); - getGroupNode(myRoot, descriptor.getGroup()).add(node); - if (hasNonDefaultScope) { - for (Descriptor desc : myDescriptors.get(descriptor)) { - node.add(new InspectionConfigTreeNode(desc, desc.getState(), false, false)); - } - node.add(new InspectionConfigTreeNode(descriptor, descriptor.getState(), true, false)); + if (!emptyFilter && !myInspectionsFilter.matches(mySelectedProfile.getTools(toolDescriptors.getDefaultDescriptor().getKey().toString(), project))) { + continue; } - myRoot.setEnabled(myRoot.isEnabled() || enabled); + final InspectionConfigTreeNode node = new InspectionConfigTreeNode(toolDescriptors); + getGroupNode(myRoot, toolDescriptors.getDefaultDescriptor().getGroup()).add(node); myRoot.dropCache(); } if (filter != null && forceInclude && myRoot.getChildCount() == 0) { @@ -740,19 +683,24 @@ public class SingleInspectionProfilePanel extends JPanel { TreeUtil.sort(myRoot, new InspectionsConfigTreeComparator()); } - private void updateOptionsAndDescriptionPanel(TreePath path) { + private void updateOptionsAndDescriptionPanel(final TreePath... paths) { + if (paths == null || paths.length == 0) { + return; + } + final TreePath path = paths[0]; if (path == null) return; - final InspectionConfigTreeNode node = (InspectionConfigTreeNode)path.getLastPathComponent(); - final Descriptor descriptor = node.getDescriptor(); - if (descriptor != null) { - final String description = descriptor.loadDescription(); - - if (description != null) { + final List<InspectionConfigTreeNode> nodes = InspectionsAggregationUtil.getInspectionsNodes(paths); + if (!nodes.isEmpty()) { + final InspectionConfigTreeNode singleNode = nodes.size() == 1 ? ContainerUtil.getFirstItem(nodes) : null; + if (singleNode != null && singleNode.getDefaultDescriptor().loadDescription() != null) { // need this in order to correctly load plugin-supplied descriptions + final Descriptor defaultDescriptor = singleNode.getDefaultDescriptor(); + final String description = defaultDescriptor.loadDescription(); try { final HintHint hintHint = new HintHint(myBrowser, new Point(0, 0)); hintHint.setFont(myBrowser.getFont()); - myBrowser.read(new StringReader(SearchUtil.markup(HintUtil.prepareHintText(description, hintHint), myProfileFilter.getFilter())), null); + myBrowser + .read(new StringReader(SearchUtil.markup(HintUtil.prepareHintText(description, hintHint), myProfileFilter.getFilter())), null); } catch (IOException e2) { try { @@ -764,7 +712,10 @@ public class SingleInspectionProfilePanel extends JPanel { } } catch (Throwable t) { - LOG.error("Failed to load description for: " + descriptor.getToolWrapper().getTool().getClass() + "; description: " + description, t); + LOG.error("Failed to load description for: " + + defaultDescriptor.getToolWrapper().getTool().getClass() + + "; description: " + + description, t); } } @@ -778,41 +729,123 @@ public class SingleInspectionProfilePanel extends JPanel { } myOptionsPanel.removeAll(); + final Project project = myProjectProfileManager.getProject(); + final JPanel severityPanel = new JPanel(new GridBagLayout()); + final double severityPanelWeightY; + final JPanel configPanelAnchor = new JPanel(new GridLayout()); + configPanelAnchor.setBorder(IdeBorderFactory.createTitledBorder("Options", false, new Insets(0, 0, 0, 0))); + + final Set<String> scopesNames = new THashSet<String>(); + for (final InspectionConfigTreeNode node : nodes) { + final List<ScopeToolState> nonDefaultTools = mySelectedProfile.getNonDefaultTools(node.getDefaultDescriptor().getKey().toString(), project); + for (final ScopeToolState tool : nonDefaultTools) { + scopesNames.add(tool.getScopeName()); + } + } + + if (scopesNames.isEmpty()) { - final NamedScope scope = node.getScope(myProjectProfileManager.getProject()); - if (scope != null || node.isInspectionNode()) { - final HighlightDisplayKey key = descriptor.getKey(); - final LevelChooserAction chooser = - new LevelChooserAction(((SeverityProvider)mySelectedProfile.getProfileManager()).getOwnSeverityRegistrar()) { + final LevelChooserAction severityLevelChooser = + new LevelChooserAction(mySelectedProfile) { @Override protected void onChosen(final HighlightSeverity severity) { final HighlightDisplayLevel level = HighlightDisplayLevel.find(severity); - final Project project = myProjectProfileManager.getProject(); - final boolean toUpdate = mySelectedProfile.getErrorLevel(key, scope, project) != level; - mySelectedProfile.setErrorLevel(key, level, - node.isInspectionNode() || node.isByDefault() ? -1 : node.getParent().getIndex(node), - project); - if (toUpdate) node.dropCache(); + for (final InspectionConfigTreeNode node : nodes) { + final HighlightDisplayKey key = node.getDefaultDescriptor().getKey(); + final NamedScope scope = node.getDefaultDescriptor().getScope(); + final boolean toUpdate = mySelectedProfile.getErrorLevel(key, scope, project) != level; + mySelectedProfile.setErrorLevel(key, level, -1, project); + if (toUpdate) node.dropCache(); + } + } }; - chooser.setChosen(mySelectedProfile.getErrorLevel(key, scope, myProjectProfileManager.getProject()).getSeverity()); + final HighlightSeverity severity = + ScopesAndSeveritiesTable.getSeverity(ContainerUtil.map(nodes, new Function<InspectionConfigTreeNode, ScopeToolState>() { + @Override + public ScopeToolState fun(InspectionConfigTreeNode node) { + return node.getDefaultDescriptor().getState(); + } + })); + severityLevelChooser.setChosen(severity); - final JPanel withSeverity = new JPanel(new GridBagLayout()); - withSeverity.add(new JLabel(InspectionsBundle.message("inspection.severity")), - new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, - GridBagConstraints.NONE, new Insets(0, 0, 10, 10), 0, 0)); - withSeverity.add(chooser.createCustomComponent(chooser.getTemplatePresentation()), new GridBagConstraints(1, 0, 1, 1, 1.0, 0, GridBagConstraints.WEST, - GridBagConstraints.NONE, new Insets(0, 0, 10, 0), 0, 0)); + final ScopesChooser scopesChooser = new ScopesChooser(ContainerUtil.map(nodes, new Function<InspectionConfigTreeNode, Descriptor>() { + @Override + public Descriptor fun(final InspectionConfigTreeNode node) { + return node.getDefaultDescriptor(); + } + }), mySelectedProfile, project) { + @Override + protected void onScopeAdded() { + updateOptionsAndDescriptionPanel(); + } + }; + + severityPanel.add(new JLabel(InspectionsBundle.message("inspection.severity")), + new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.VERTICAL, + new Insets(10, 0, 10, 10), 0, 0)); + severityPanel.add(severityLevelChooser.createCustomComponent(severityLevelChooser.getTemplatePresentation()), + new GridBagConstraints(1, 0, 1, 1, 1.0, 0, GridBagConstraints.WEST, GridBagConstraints.VERTICAL, + new Insets(10, 0, 10, 0), 0, 0)); + severityPanel.add(scopesChooser.createCustomComponent(scopesChooser.getTemplatePresentation()), + new GridBagConstraints(2, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.VERTICAL, + new Insets(10, 0, 10, 0), 0, 0)); + severityPanelWeightY = 0.0; + if (singleNode != null) { + setConfigPanel(configPanelAnchor, mySelectedProfile.getToolDefaultState(singleNode.getDefaultDescriptor().getKey().toString(), + project)); + } + } + else { + if (singleNode != null) { + for (final Descriptor descriptor : singleNode.getDescriptors().getNonDefaultDescriptors()) { + descriptor.loadConfig(); + } + } + final JTable scopesAndScopesAndSeveritiesTable = + new ScopesAndSeveritiesTable(new ScopesAndSeveritiesTable.TableSettings(nodes, mySelectedProfile, myTreeTable, project) { + @Override + protected void onScopeChosen(@NotNull final ScopeToolState state) { + setConfigPanel(configPanelAnchor, state); + configPanelAnchor.revalidate(); + configPanelAnchor.repaint(); + } - final JComponent comp = descriptor.getState().getAdditionalConfigPanel(); - withSeverity.add(comp, - new GridBagConstraints(0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, - GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + @Override + protected void onChange() { + myTreeTable.getTree().updateUI(); + } + + @Override + protected void onScopeAdded() { + } - myOptionsPanel.add(withSeverity, BorderLayout.CENTER); + @Override + protected void onScopeRemoved(final int scopesCount) { + if (scopesCount == 1) { + updateOptionsAndDescriptionPanel(); + } + } + }); + + + final ToolbarDecorator wrappedTable = ToolbarDecorator.createDecorator(scopesAndScopesAndSeveritiesTable); + final JPanel panel = wrappedTable.createPanel(); + severityPanel.add(new JBLabel("Scopes & Severities"), + new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, + new Insets(5, 0, 2, 10), 0, 0)); + severityPanel.add(new JBLabel("Add scope to change its settings", UIUtil.ComponentStyle.SMALL), + new GridBagConstraints(1, 0, 1, 1, 1.0, 0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, + new Insets(5, 0, 2, 0), 0, 0)); + severityPanel.add(panel, new GridBagConstraints(0, 1, 2, 1, 0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); + severityPanelWeightY = 0.3; } + myOptionsPanel.add(severityPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, severityPanelWeightY, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + myOptionsPanel.add(configPanelAnchor, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); myOptionsPanel.revalidate(); - GuiUtils.enableChildren(myOptionsPanel, node.isChecked()); + GuiUtils.enableChildren(myOptionsPanel, isThoughOneNodeEnabled(nodes)); } else { initOptionsAndDescriptionPanel(); @@ -820,6 +853,26 @@ public class SingleInspectionProfilePanel extends JPanel { myOptionsPanel.repaint(); } + private boolean isThoughOneNodeEnabled(final List<InspectionConfigTreeNode> nodes) { + final Project project = myProjectProfileManager.getProject(); + for (final InspectionConfigTreeNode node : nodes) { + final String toolId = node.getDefaultDescriptor().getKey().toString(); + if (mySelectedProfile.getTools(toolId, project).isEnabled()) { + return true; + } + } + return false; + } + + private void updateOptionsAndDescriptionPanel() { + final TreePath[] paths = myTreeTable.getTree().getSelectionPaths(); + if (paths != null) { + updateOptionsAndDescriptionPanel(paths); + } else { + initOptionsAndDescriptionPanel(); + } + } + private void initOptionsAndDescriptionPanel() { myOptionsPanel.removeAll(); try { @@ -832,6 +885,11 @@ public class SingleInspectionProfilePanel extends JPanel { myOptionsPanel.repaint(); } + private static void setConfigPanel(final JPanel configPanelAnchor, final ScopeToolState state) { + configPanelAnchor.removeAll(); + configPanelAnchor.add(state.getAdditionalConfigPanel()); + } + private static InspectionConfigTreeNode getGroupNode(InspectionConfigTreeNode root, String[] groupPath) { InspectionConfigTreeNode currentRoot = root; for (final String group : groupPath) { @@ -848,7 +906,7 @@ public class SingleInspectionProfilePanel extends JPanel { return child; } } - InspectionConfigTreeNode child = new InspectionConfigTreeNode(group, null, false, false); + InspectionConfigTreeNode child = new InspectionConfigTreeNode(group); root.add(child); return child; } @@ -868,8 +926,8 @@ public class SingleInspectionProfilePanel extends JPanel { if (mySelectedProfile != null) { myInitialProfile = mySelectedProfile.getName(); } - initDescriptors(); - filterTree(myProfileFilter != null ? myProfileFilter.getFilter() : null); + initToolStates(); + filterTree(); } @Override @@ -900,7 +958,7 @@ public class SingleInspectionProfilePanel extends JPanel { myBrowser.setBorder(IdeBorderFactory.createEmptyBorder(5, 5, 5, 5)); myBrowser.addHyperlinkListener(new BrowserHyperlinkListener()); - initDescriptors(); + initToolStates(); fillTreeData(myProfileFilter != null ? myProfileFilter.getFilter() : null, true); JPanel descriptionPanel = new JPanel(new BorderLayout()); @@ -912,9 +970,7 @@ public class SingleInspectionProfilePanel extends JPanel { myRightSplitter.setFirstComponent(descriptionPanel); myRightSplitter.setProportion(myProperties.getFloat(HORIZONTAL_DIVIDER_PROPORTION, 0.5f)); - myOptionsPanel = new JPanel(new BorderLayout()); - myOptionsPanel.setBorder(IdeBorderFactory.createTitledBorder("Options", false, - new Insets(0, 0, 0, 0))); + myOptionsPanel = new JPanel(new GridBagLayout()); initOptionsAndDescriptionPanel(); myRightSplitter.setSecondComponent(myOptionsPanel); myRightSplitter.setHonorComponentsMinimumSize(true); @@ -925,8 +981,8 @@ public class SingleInspectionProfilePanel extends JPanel { final JPanel northPanel = new JPanel(new GridBagLayout()); northPanel.setBorder(IdeBorderFactory.createEmptyBorder(2, 0, 2, 0)); - northPanel.add(createTreeToolbarPanel().getComponent(), new GridBagConstraints(0, 0, 1, 1, 0.5, 1, GridBagConstraints.BASELINE_LEADING, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); - northPanel.add(myProfileFilter, new GridBagConstraints(1, 0, 1, 1, 1, 1, GridBagConstraints.BASELINE_TRAILING, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + northPanel.add(myProfileFilter, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.BASELINE_TRAILING, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + northPanel.add(createTreeToolbarPanel().getComponent(), new GridBagConstraints(1, 0, 1, 1, 0.5, 1, GridBagConstraints.BASELINE_LEADING, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); treePanel.add(northPanel, BorderLayout.NORTH); myMainSplitter = new Splitter(false); @@ -1020,8 +1076,8 @@ public class SingleInspectionProfilePanel extends JPanel { } private boolean descriptorsAreChanged() { - for (Map.Entry<Descriptor, List<Descriptor>> entry : myDescriptors.entrySet()) { - Descriptor desc = entry.getKey(); + for (ToolDescriptors toolDescriptors : myInitialToolDescriptors) { + Descriptor desc = toolDescriptors.getDefaultDescriptor(); Project project = myProjectProfileManager.getProject(); if (mySelectedProfile.isToolEnabled(desc.getKey(), null, project) != desc.isEnabled()){ return true; @@ -1029,7 +1085,7 @@ public class SingleInspectionProfilePanel extends JPanel { if (mySelectedProfile.getErrorLevel(desc.getKey(), desc.getScope(), project) != desc.getLevel()) { return true; } - final List<Descriptor> descriptors = entry.getValue(); + final List<Descriptor> descriptors = toolDescriptors.getNonDefaultDescriptors(); for (Descriptor descriptor : descriptors) { if (mySelectedProfile.isToolEnabled(descriptor.getKey(), descriptor.getScope(), project) != descriptor.isEnabled()) { return true; @@ -1055,8 +1111,8 @@ public class SingleInspectionProfilePanel extends JPanel { return false; } - public Tree getTree() { - return myTree; + public Tree getTreeTable() { + return myTreeTable.getTree(); } public boolean isProfileShared() { @@ -1076,13 +1132,13 @@ public class SingleInspectionProfilePanel extends JPanel { } private void setNewHighlightingLevel(@NotNull HighlightDisplayLevel level) { - final int[] rows = myTree.getSelectionRows(); + final int[] rows = myTreeTable.getTree().getSelectionRows(); final boolean showOptionsAndDescriptorPanels = rows != null && rows.length == 1; for (int i = 0; rows != null && i < rows.length; i++) { - final InspectionConfigTreeNode node = (InspectionConfigTreeNode)myTree.getPathForRow(rows[i]).getLastPathComponent(); + final InspectionConfigTreeNode node = (InspectionConfigTreeNode)myTreeTable.getTree().getPathForRow(rows[i]).getLastPathComponent(); final InspectionConfigTreeNode parent = (InspectionConfigTreeNode)node.getParent(); final Object userObject = node.getUserObject(); - if (userObject instanceof Descriptor && (node.getScopeName() != null || node.isLeaf())) { + if (userObject instanceof ToolDescriptors && (node.getScopeName() != null || node.isLeaf())) { updateErrorLevel(node, showOptionsAndDescriptorPanels, level); updateUpHierarchy(node, parent); } @@ -1091,8 +1147,8 @@ public class SingleInspectionProfilePanel extends JPanel { updateUpHierarchy(node, parent); } } - if (rows != null && rows.length == 1) { - updateOptionsAndDescriptionPanel(myTree.getPathForRow(rows[0])); + if (rows != null) { + updateOptionsAndDescriptionPanel(myTreeTable.getTree().getSelectionPaths()); } else { initOptionsAndDescriptionPanel(); @@ -1107,7 +1163,7 @@ public class SingleInspectionProfilePanel extends JPanel { for (int j = 0; j < node.getChildCount(); j++) { final InspectionConfigTreeNode child = (InspectionConfigTreeNode)node.getChildAt(j); final Object userObject = child.getUserObject(); - if (userObject instanceof Descriptor && (child.getScopeName() != null || child.isLeaf())) { + if (userObject instanceof ToolDescriptors && (child.getScopeName() != null || child.isLeaf())) { updateErrorLevel(child, showOptionsAndDescriptorPanels, level); } else { @@ -1119,15 +1175,18 @@ public class SingleInspectionProfilePanel extends JPanel { private void updateErrorLevel(final InspectionConfigTreeNode child, final boolean showOptionsAndDescriptorPanels, @NotNull HighlightDisplayLevel level) { - final HighlightDisplayKey key = child.getDescriptor().getKey(); - mySelectedProfile.setErrorLevel(key, level, child.isInspectionNode() || child.isByDefault() ? -1 : child.getParent().getIndex(child), - myProjectProfileManager.getProject()); + final HighlightDisplayKey key = child.getDefaultDescriptor().getKey(); + mySelectedProfile.setErrorLevel(key, level, -1, myProjectProfileManager.getProject()); child.dropCache(); if (showOptionsAndDescriptorPanels) { updateOptionsAndDescriptionPanel(new TreePath(child.getPath())); } } + public JComponent getTree() { + return myTreeTable.getTree(); + } + private class MyFilterComponent extends FilterComponent { private MyFilterComponent() { super(INSPECTION_FILTER_HISTORY, 10); @@ -1143,47 +1202,14 @@ public class SingleInspectionProfilePanel extends JPanel { protected void onlineFilter() { if (mySelectedProfile == null) return; final String filter = getFilter(); - getExpandedNodes(mySelectedProfile).saveVisibleState(myTree); + getExpandedNodes(mySelectedProfile).saveVisibleState(myTreeTable.getTree()); fillTreeData(filter, true); reloadModel(); if (filter == null || filter.isEmpty()) { restoreTreeState(); } else { - TreeUtil.expandAll(myTree); - } - } - } - - private class MyAddScopeAction extends AddScopeAction { - public MyAddScopeAction() { - super(SingleInspectionProfilePanel.this.myTree); - } - - @Override - protected InspectionProfileImpl getSelectedProfile() { - return mySelectedProfile; - } - - @Override - public void actionPerformed(AnActionEvent e) { - super.actionPerformed(e); - final TreePath[] paths = myTree.getSelectionPaths(); - if (paths != null && paths.length == 1) { - updateOptionsAndDescriptionPanel(myTree.getSelectionPath()); - } else { - initOptionsAndDescriptionPanel(); + TreeUtil.expandAll(myTreeTable.getTree()); } } } - - private class MyDeleteScopeAction extends DeleteScopeAction { - public MyDeleteScopeAction() { - super(SingleInspectionProfilePanel.this.myTree); - } - - @Override - protected InspectionProfileImpl getSelectedProfile() { - return mySelectedProfile; - } - } } diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/ToolDescriptors.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/ToolDescriptors.java new file mode 100644 index 000000000000..47826735ec33 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/ToolDescriptors.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui; + +import com.intellij.codeInspection.ex.Descriptor; +import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.codeInspection.ex.InspectionToolWrapper; +import com.intellij.codeInspection.ex.ScopeToolState; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Dmitry Batkovich + */ +public class ToolDescriptors { + + @NotNull + private final Descriptor myDefaultDescriptor; + @NotNull + private final List<Descriptor> myNonDefaultDescriptors; + + private ToolDescriptors(final @NotNull Descriptor defaultDescriptor, + final @NotNull List<Descriptor> nonDefaultDescriptors) { + myDefaultDescriptor = defaultDescriptor; + myNonDefaultDescriptors = nonDefaultDescriptors; + } + + public static ToolDescriptors fromScopeToolState(final ScopeToolState state, + final InspectionProfileImpl profile, + final Project project) { + final InspectionToolWrapper toolWrapper = state.getTool(); + final List<ScopeToolState> nonDefaultTools = profile.getNonDefaultTools(toolWrapper.getShortName(), project); + final ArrayList<Descriptor> descriptors = new ArrayList<Descriptor>(nonDefaultTools.size()); + for (final ScopeToolState nonDefaultToolState : nonDefaultTools) { + descriptors.add(new Descriptor(nonDefaultToolState, profile, project)); + } + return new ToolDescriptors(new Descriptor(state, profile, project), descriptors); + } + + @NotNull + public Descriptor getDefaultDescriptor() { + return myDefaultDescriptor; + } + + @NotNull + public List<Descriptor> getNonDefaultDescriptors() { + return myNonDefaultDescriptors; + } + + @NotNull + public ScopeToolState getDefaultScopeToolState() { + return myDefaultDescriptor.getState(); + } +} diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/actions/AddScopeAction.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/actions/AddScopeAction.java deleted file mode 100644 index 3a1031946b6b..000000000000 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/actions/AddScopeAction.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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. - */ - -/* - * User: anna - * Date: 14-May-2009 - */ -package com.intellij.profile.codeInspection.ui.actions; - -import com.intellij.codeHighlighting.HighlightDisplayLevel; -import com.intellij.codeInspection.ex.Descriptor; -import com.intellij.codeInspection.ex.InspectionProfileImpl; -import com.intellij.codeInspection.ex.InspectionToolWrapper; -import com.intellij.codeInspection.ex.ScopeToolState; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.ui.Messages; -import com.intellij.packageDependencies.DefaultScopesProvider; -import com.intellij.profile.codeInspection.ui.InspectionConfigTreeNode; -import com.intellij.psi.search.scope.packageSet.CustomScopesProviderEx; -import com.intellij.psi.search.scope.packageSet.NamedScope; -import com.intellij.psi.search.scope.packageSet.NamedScopesHolder; -import com.intellij.ui.treeStructure.Tree; -import com.intellij.util.ArrayUtil; -import com.intellij.util.IconUtil; - -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreePath; -import java.util.*; - -public abstract class AddScopeAction extends AnAction { - private final Tree myTree; - private static final Logger LOG = Logger.getInstance("#" + AddScopeAction.class.getName()); - - public AddScopeAction(Tree tree) { - super("Add Scope", "Add Scope", IconUtil.getAddIcon()); - myTree = tree; - registerCustomShortcutSet(CommonShortcuts.INSERT, myTree); - } - - @Override - public void update(AnActionEvent e) { - final Presentation presentation = e.getPresentation(); - presentation.setEnabled(false); - if (getSelectedProfile() == null) return; - final Project project = getProject(e); - final InspectionConfigTreeNode[] selectedNodes = myTree.getSelectedNodes(InspectionConfigTreeNode.class, null); - if (selectedNodes == null) return; - final List<Descriptor> descriptors = new ArrayList<Descriptor>(); - for (InspectionConfigTreeNode node : selectedNodes) { - collect(descriptors, new ArrayList<InspectionConfigTreeNode>(), node); - } - - presentation.setEnabled(!getAvailableScopes(project, descriptors).isEmpty()); - } - - private static Project getProject(AnActionEvent e) { - Project project = CommonDataKeys.PROJECT.getData(e.getDataContext()); - if (project == null) { - project = ProjectManager.getInstance().getDefaultProject(); - } - return project; - } - - @Override - public void actionPerformed(AnActionEvent e) { - final List<Descriptor> descriptors = new ArrayList<Descriptor>(); - final InspectionConfigTreeNode[] selectedNodes = myTree.getSelectedNodes(InspectionConfigTreeNode.class, null); - LOG.assertTrue(selectedNodes != null); - - final List<InspectionConfigTreeNode> nodes = new ArrayList<InspectionConfigTreeNode>(Arrays.asList(selectedNodes)); - for (InspectionConfigTreeNode node : selectedNodes) { - collect(descriptors, nodes, node); - } - - final Project project = getProject(e); - final List<String> availableScopes = getAvailableScopes(project, descriptors); - final int idx = Messages.showChooseDialog(myTree, "Scope:", "Choose Scope", ArrayUtil.toStringArray(availableScopes), availableScopes.get(0), Messages.getQuestionIcon()); - if (idx == -1) return; - final NamedScope chosenScope = NamedScopesHolder.getScope(project, availableScopes.get(idx)); - - for (InspectionConfigTreeNode node : nodes) { - final Descriptor descriptor = node.getDescriptor(); - if (node.getScopeName() != null || descriptor == null) continue; - final InspectionToolWrapper toolWrapper = descriptor.getToolWrapper(); //copy - InspectionProfileImpl selectedProfile = getSelectedProfile(); - HighlightDisplayLevel level = selectedProfile.getErrorLevel(descriptor.getKey(), chosenScope, project); - boolean enabled = selectedProfile.isToolEnabled(descriptor.getKey()); - final ScopeToolState scopeToolState = selectedProfile.addScope(toolWrapper, chosenScope, level, enabled, project); - final Descriptor addedDescriptor = new Descriptor(scopeToolState, selectedProfile, project); - if (node.getChildCount() == 0) { - node.add(new InspectionConfigTreeNode(descriptor, selectedProfile.getToolDefaultState(descriptor.getKey().toString(), project), true, true, false)); - } - node.insert(new InspectionConfigTreeNode(addedDescriptor, scopeToolState, false, false), 0); - node.setInspectionNode(false); - node.dropCache(); - ((DefaultTreeModel)myTree.getModel()).reload(node); - myTree.expandPath(new TreePath(node.getPath())); - } - myTree.revalidate(); - } - - private static void collect(List<Descriptor> descriptors, - List<InspectionConfigTreeNode> nodes, - InspectionConfigTreeNode node) { - final Descriptor descriptor = node.getDescriptor(); - if (descriptor != null) { - if (node.getScopeName() == null) { - descriptors.add(descriptor); - } - } else if (node.getUserObject() instanceof String) { - for(int i = 0; i < node.getChildCount(); i++) { - final InspectionConfigTreeNode childNode = (InspectionConfigTreeNode)node.getChildAt(i); - nodes.add(childNode); - collect(descriptors, nodes, childNode); - } - } - } - - private List<String> getAvailableScopes(Project project, List<Descriptor> descriptors) { - final ArrayList<NamedScope> scopes = new ArrayList<NamedScope>(); - for (NamedScopesHolder holder : NamedScopesHolder.getAllNamedScopeHolders(project)) { - Collections.addAll(scopes, holder.getScopes()); - } - scopes.remove(CustomScopesProviderEx.getAllScope()); - - CustomScopesProviderEx.filterNoSettingsScopes(project, scopes); - - final Set<NamedScope> used = new HashSet<NamedScope>(); - for (Descriptor descriptor : descriptors) { - final List<ScopeToolState> nonDefaultTools = getSelectedProfile().getNonDefaultTools(descriptor.getKey().toString(), project); - if (nonDefaultTools != null) { - for (ScopeToolState state : nonDefaultTools) { - used.add(state.getScope(project)); - } - } - } - scopes.removeAll(used); - - final List<String> availableScopes = new ArrayList<String>(); - for (NamedScope scope : scopes) { - availableScopes.add(scope.getName()); - } - return availableScopes; - } - - protected abstract InspectionProfileImpl getSelectedProfile(); -}
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/actions/DeleteScopeAction.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/actions/DeleteScopeAction.java deleted file mode 100644 index 1fc94c2d15b2..000000000000 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/actions/DeleteScopeAction.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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. - */ - -/* - * User: anna - * Date: 14-May-2009 - */ -package com.intellij.profile.codeInspection.ui.actions; - -import com.intellij.codeInsight.daemon.HighlightDisplayKey; -import com.intellij.codeInspection.ex.Descriptor; -import com.intellij.codeInspection.ex.InspectionProfileImpl; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonShortcuts; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.profile.codeInspection.ui.InspectionConfigTreeNode; -import com.intellij.ui.treeStructure.Tree; -import com.intellij.util.PlatformIcons; - -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreePath; - -public abstract class DeleteScopeAction extends AnAction { - private static final Logger LOG = Logger.getInstance("#" + DeleteScopeAction.class.getName()); - private final Tree myTree; - - public DeleteScopeAction(Tree tree) { - super("Delete Scope", "Delete Scope", PlatformIcons.DELETE_ICON); - myTree = tree; - registerCustomShortcutSet(CommonShortcuts.getDelete(), myTree); - } - - @Override - public void update(AnActionEvent e) { - final Presentation presentation = e.getPresentation(); - presentation.setEnabled(false); - if (getSelectedProfile() == null) return; - final InspectionConfigTreeNode[] nodes = myTree.getSelectedNodes(InspectionConfigTreeNode.class, null); - if (nodes.length > 0) { - for (InspectionConfigTreeNode node : nodes) { - if (node.getScopeName() == null || node.isByDefault()) return; - } - presentation.setEnabled(true); - } - } - - @Override - public void actionPerformed(AnActionEvent e) { - InspectionConfigTreeNode parent = null; - final InspectionConfigTreeNode[] nodes = myTree.getSelectedNodes(InspectionConfigTreeNode.class, null); - for (InspectionConfigTreeNode node : nodes) { - final Descriptor descriptor = node.getDescriptor(); - LOG.assertTrue(descriptor != null); - parent = (InspectionConfigTreeNode)node.getParent(); - final HighlightDisplayKey key = descriptor.getKey(); - if (parent.getChildCount() <= 2) { //remove default with last non-default - getSelectedProfile().removeAllScopes(key.toString(), e.getProject()); - parent.removeAllChildren(); - parent.setInspectionNode(true); - parent.setByDefault(true); - } - else { - getSelectedProfile().removeScope(key.toString(), parent.getIndex(node), e.getProject()); - node.removeFromParent(); - } - ((DefaultTreeModel)myTree.getModel()).reload(parent); - } - if (parent != null) { - myTree.setSelectionPath(new TreePath(parent.getPath())); - } - myTree.revalidate(); - } - - protected abstract InspectionProfileImpl getSelectedProfile(); -}
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/actions/MoveScopeAction.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/actions/MoveScopeAction.java deleted file mode 100644 index 40d4b438f995..000000000000 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/actions/MoveScopeAction.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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. - */ - -/* - * User: anna - * Date: 14-May-2009 - */ -package com.intellij.profile.codeInspection.ui.actions; - -import com.intellij.codeInspection.ex.Descriptor; -import com.intellij.codeInspection.ex.InspectionProfileImpl; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.profile.codeInspection.ui.InspectionConfigTreeNode; -import com.intellij.ui.treeStructure.Tree; - -import javax.swing.*; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; - -public abstract class MoveScopeAction extends AnAction { - private final Tree myTree; - private final int myDir; - - public MoveScopeAction(Tree tree, String text, Icon icon, int dir) { - super(text, text, icon); - myTree = tree; - myDir = dir; - } - - protected abstract boolean isEnabledFor(int idx, InspectionConfigTreeNode parent); - - - @Override - public void update(AnActionEvent e) { - final Presentation presentation = e.getPresentation(); - presentation.setEnabled(false); - if (getSelectedProfile() == null) return; - final InspectionConfigTreeNode[] nodes = myTree.getSelectedNodes(InspectionConfigTreeNode.class, null); - if (nodes.length > 0) { - final InspectionConfigTreeNode treeNode = nodes[0]; - if (treeNode.getScope(getEventProject(e)) != null && !treeNode.isByDefault()) { - final TreeNode parent = treeNode.getParent(); - final int index = parent.getIndex(treeNode); - presentation.setEnabled(isEnabledFor(index, (InspectionConfigTreeNode)parent)); - } - } - } - - @Override - public void actionPerformed(AnActionEvent e) { - final InspectionConfigTreeNode[] nodes = myTree.getSelectedNodes(InspectionConfigTreeNode.class, null); - final InspectionConfigTreeNode node = nodes[0]; - final Descriptor descriptor = node.getDescriptor(); - final TreeNode parent = node.getParent(); - final int index = parent.getIndex(node); - getSelectedProfile().moveScope(descriptor.getKey().toString(), index, myDir, e.getProject()); - node.removeFromParent(); - ((InspectionConfigTreeNode)parent).insert(node, index + myDir); - ((DefaultTreeModel)myTree.getModel()).reload(parent); - myTree.setSelectionPath(new TreePath(node.getPath())); - myTree.revalidate(); - } - - protected abstract InspectionProfileImpl getSelectedProfile(); -}
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/filter/InspectionFilterAction.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/filter/InspectionFilterAction.java new file mode 100644 index 000000000000..cbd1c2482032 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/filter/InspectionFilterAction.java @@ -0,0 +1,129 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui.filter; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; +import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.icons.AllIcons; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.actionSystem.ex.CheckboxAction; +import com.intellij.profile.codeInspection.SeverityProvider; +import com.intellij.profile.codeInspection.ui.LevelChooserAction; +import com.intellij.profile.codeInspection.ui.SingleInspectionProfilePanel; +import org.jetbrains.annotations.Nullable; + +import java.util.SortedSet; + +/** + * @author Dmitry Batkovich + */ +public class InspectionFilterAction extends DefaultActionGroup { + + private final SeverityRegistrar mySeverityRegistrar; + private final InspectionsFilter myInspectionsFilter; + + public InspectionFilterAction(final InspectionProfileImpl profile, final InspectionsFilter inspectionsFilter) { + super("Filter Inspections", true); + myInspectionsFilter = inspectionsFilter; + mySeverityRegistrar = ((SeverityProvider)profile.getProfileManager()).getOwnSeverityRegistrar(); + getTemplatePresentation().setIcon(AllIcons.General.Filter); + tune(); + } + + private void tune() { + addAction(new ShowEnabledOrDisabledInspectionsAction(null)); + addAction(new ShowEnabledOrDisabledInspectionsAction(true)); + addAction(new ShowEnabledOrDisabledInspectionsAction(false)); + addSeparator(); + + final SortedSet<HighlightSeverity> severities = LevelChooserAction.getSeverities(mySeverityRegistrar); + for (final HighlightSeverity severity : severities) { + add(new ShowWithSpecifiedSeverityInspectionsAction(severity)); + } + addSeparator(); + + add(new ShowAvailableOnlyOnAnalyzeInspectionsAction()); + } + + private class ShowAvailableOnlyOnAnalyzeInspectionsAction extends CheckboxAction { + + public ShowAvailableOnlyOnAnalyzeInspectionsAction() { + super("Show Only \"Available only for Analyze | Inspect Code\""); + } + + @Override + public boolean isSelected(final AnActionEvent e) { + return myInspectionsFilter.isAvailableOnlyForAnalyze(); + } + + @Override + public void setSelected(final AnActionEvent e, final boolean state) { + myInspectionsFilter.setAvailableOnlyForAnalyze(state); + } + } + + private class ShowWithSpecifiedSeverityInspectionsAction extends CheckboxAction { + + private final HighlightSeverity mySeverity; + + private ShowWithSpecifiedSeverityInspectionsAction(final HighlightSeverity severity) { + super(SingleInspectionProfilePanel.renderSeverity(severity), + null, + HighlightDisplayLevel.find(severity).getIcon()); + mySeverity = severity; + } + + + @Override + public boolean isSelected(final AnActionEvent e) { + return myInspectionsFilter.containsSeverity(mySeverity); + } + + @Override + public void setSelected(final AnActionEvent e, final boolean state) { + if (state) { + myInspectionsFilter.add(mySeverity); + } else { + myInspectionsFilter.remove(mySeverity); + } + } + } + + private class ShowEnabledOrDisabledInspectionsAction extends CheckboxAction { + + private final Boolean myShowEnabledActions; + + public ShowEnabledOrDisabledInspectionsAction(@Nullable final Boolean showEnabledActions) { + super(showEnabledActions == null ? "All Inspections" : (showEnabledActions ? "Enabled" : "Disabled")); + myShowEnabledActions = showEnabledActions; + } + + + @Override + public boolean isSelected(final AnActionEvent e) { + return myInspectionsFilter.getSuitableInspectionsStates() == myShowEnabledActions; + } + + @Override + public void setSelected(final AnActionEvent e, final boolean state) { + myInspectionsFilter.setSuitableInspectionsStates(myShowEnabledActions); + } + } +}
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/filter/InspectionsFilter.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/filter/InspectionsFilter.java new file mode 100644 index 000000000000..432ef560ba58 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/filter/InspectionsFilter.java @@ -0,0 +1,102 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui.filter; + +import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper; +import com.intellij.codeInspection.ex.InspectionToolWrapper; +import com.intellij.codeInspection.ex.ScopeToolState; +import com.intellij.codeInspection.ex.Tools; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +/** + * @author Dmitry Batkovich + */ +public abstract class InspectionsFilter { + + private final Set<HighlightSeverity> mySuitableSeverities = new HashSet<HighlightSeverity>(); + private Boolean mySuitableInspectionsStates; + private boolean myAvailableOnlyForAnalyze; + + public boolean isAvailableOnlyForAnalyze() { + return myAvailableOnlyForAnalyze; + } + + public Boolean getSuitableInspectionsStates() { + return mySuitableInspectionsStates; + } + + public boolean containsSeverity(final HighlightSeverity severity) { + return mySuitableSeverities.contains(severity); + } + + public void setAvailableOnlyForAnalyze(final boolean availableOnlyForAnalyze) { + myAvailableOnlyForAnalyze = availableOnlyForAnalyze; + filterChanged(); + } + + public void setSuitableInspectionsStates(@Nullable final Boolean suitableInspectionsStates) { + mySuitableInspectionsStates = suitableInspectionsStates; + filterChanged(); + } + + public void add(final HighlightSeverity severity) { + mySuitableSeverities.add(severity); + filterChanged(); + } + + public void remove(final HighlightSeverity severity) { + mySuitableSeverities.remove(severity); + filterChanged(); + } + + public boolean isEmptyFilter() { + return mySuitableInspectionsStates == null && !myAvailableOnlyForAnalyze && mySuitableSeverities.isEmpty(); + } + + public boolean matches(final Tools tools) { + if (mySuitableInspectionsStates != null && mySuitableInspectionsStates != tools.isEnabled()) { + return false; + } + + if (myAvailableOnlyForAnalyze != isAvailableOnlyForAnalyze(tools)) { + return false; + } + + if (mySuitableSeverities.isEmpty()) { + return true; + } + for (final ScopeToolState state : tools.getTools()) { + if (mySuitableInspectionsStates != null && mySuitableInspectionsStates != state.isEnabled()) { + continue; + } + if (mySuitableSeverities.contains(tools.getDefaultState().getLevel().getSeverity())) { + return true; + } + } + return false; + } + + protected abstract void filterChanged(); + + private static boolean isAvailableOnlyForAnalyze(final Tools tools) { + final InspectionToolWrapper tool = tools.getTool(); + return tool instanceof GlobalInspectionToolWrapper && ((GlobalInspectionToolWrapper)tool).worksInBatchModeOnly(); + } +}
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionConfigTreeNode.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionConfigTreeNode.java index f8fdc2c3dc5f..1f26308c61b1 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionConfigTreeNode.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionConfigTreeNode.java @@ -13,31 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.profile.codeInspection.ui; +package com.intellij.profile.codeInspection.ui.inspectionsTree; +import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInspection.ex.Descriptor; -import com.intellij.codeInspection.ex.ScopeToolState; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.ClearableLazyValue; -import com.intellij.psi.search.scope.packageSet.NamedScope; -import com.intellij.ui.CheckedTreeNode; +import com.intellij.profile.codeInspection.ui.ToolDescriptors; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.tree.DefaultMutableTreeNode; + /** * @author anna * @since 14-May-2009 */ -public class InspectionConfigTreeNode extends CheckedTreeNode { - private final ScopeToolState myState; - private boolean myByDefault; - private boolean myInspectionNode; +public class InspectionConfigTreeNode extends DefaultMutableTreeNode { private final ClearableLazyValue<Boolean> myProperSetting = new ClearableLazyValue<Boolean>() { @NotNull @Override protected Boolean compute() { - Descriptor descriptor = getDescriptor(); - if (descriptor != null) return descriptor.getInspectionProfile().isProperSetting(descriptor.getToolWrapper().getShortName()); + ToolDescriptors descriptors = getDescriptors(); + if (descriptors != null) { + final Descriptor defaultDescriptor = descriptors.getDefaultDescriptor(); + return defaultDescriptor.getInspectionProfile().isProperSetting(defaultDescriptor.getToolWrapper().getShortName()); + } for (int i = 0; i < getChildCount(); i++) { InspectionConfigTreeNode node = (InspectionConfigTreeNode)getChildAt(i); if (node.isProperSetting()) { @@ -48,35 +48,24 @@ public class InspectionConfigTreeNode extends CheckedTreeNode { } }; - public InspectionConfigTreeNode(@NotNull Object userObject, ScopeToolState state, boolean byDefault, boolean inspectionNode) { + public InspectionConfigTreeNode(@NotNull Object userObject) { super(userObject); - myState = state; - myByDefault = byDefault; - myInspectionNode = inspectionNode; - if (state != null) { - setChecked(state.isEnabled()); - } } - public InspectionConfigTreeNode(@NotNull Descriptor descriptor, ScopeToolState state, boolean byDefault, boolean isEnabled, - boolean inspectionNode) { - this(descriptor, state, byDefault, inspectionNode); - setChecked(isEnabled); + public HighlightDisplayKey getKey() { + return getDefaultDescriptor().getKey(); } @Nullable - public Descriptor getDescriptor() { - if (userObject instanceof String) return null; - return (Descriptor)userObject; + public Descriptor getDefaultDescriptor() { + final ToolDescriptors descriptors = getDescriptors(); + return descriptors == null ? null : descriptors.getDefaultDescriptor(); } @Nullable - public NamedScope getScope(Project project) { - return myState == null ? null : myState.getScope(project); - } - - public boolean isByDefault() { - return myByDefault; + public ToolDescriptors getDescriptors() { + if (userObject instanceof String) return null; + return (ToolDescriptors)userObject; } @Nullable @@ -84,21 +73,10 @@ public class InspectionConfigTreeNode extends CheckedTreeNode { return userObject instanceof String ? (String)userObject : null; } - public boolean isInspectionNode() { - return myInspectionNode; - } - - public void setInspectionNode(boolean inspectionNode) { - myInspectionNode = inspectionNode; - } - - public void setByDefault(boolean byDefault) { - myByDefault = byDefault; - } - @Nullable public String getScopeName() { - return myState != null ? myState.getScopeName() : null; + final ToolDescriptors descriptors = getDescriptors(); + return descriptors != null ? descriptors.getDefaultScopeToolState().getScopeName() : null; } public boolean isProperSetting() { diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionsConfigTreeComparator.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeComparator.java index fe60c51e49b9..b920b0b741ab 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionsConfigTreeComparator.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeComparator.java @@ -18,9 +18,9 @@ * User: anna * Date: 14-May-2009 */ -package com.intellij.profile.codeInspection.ui; +package com.intellij.profile.codeInspection.ui.inspectionsTree; -import com.intellij.codeInspection.ex.Descriptor; +import com.intellij.profile.codeInspection.ui.ToolDescriptors; import java.util.Comparator; @@ -44,11 +44,11 @@ public class InspectionsConfigTreeComparator implements Comparator<InspectionCon return getDisplayTextToSort(s1).compareToIgnoreCase(getDisplayTextToSort(s2)); } - final Descriptor descriptor1 = o1.getDescriptor(); - final Descriptor descriptor2 = o2.getDescriptor(); - if (descriptor1 != null && descriptor2 != null) { - s1 = descriptor1.getText(); - s2 = descriptor2.getText(); + final ToolDescriptors descriptors1 = o1.getDescriptors(); + final ToolDescriptors descriptors2 = o2.getDescriptors(); + if (descriptors1 != null && descriptors2 != null) { + s1 = descriptors1.getDefaultDescriptor().getText(); + s2 = descriptors2.getDefaultDescriptor().getText(); } if (s1 != null && s2 != null) { diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionsConfigTreeRenderer.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeRenderer.java index 8094a1bfd415..43b791cbe6a2 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/InspectionsConfigTreeRenderer.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeRenderer.java @@ -18,7 +18,7 @@ * User: anna * Date: 14-May-2009 */ -package com.intellij.profile.codeInspection.ui; +package com.intellij.profile.codeInspection.ui.inspectionsTree; import com.intellij.codeInspection.InspectionsBundle; import com.intellij.codeInspection.ex.Descriptor; @@ -26,29 +26,23 @@ import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper; import com.intellij.codeInspection.ex.InspectionToolWrapper; import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; import com.intellij.ide.ui.search.SearchUtil; -import com.intellij.openapi.project.Project; -import com.intellij.ui.CheckboxTree; -import com.intellij.ui.JBColor; +import com.intellij.profile.codeInspection.ui.ToolDescriptors; +import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.ui.PlatformColors; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; -abstract class InspectionsConfigTreeRenderer extends CheckboxTree.CheckboxTreeCellRenderer { - private final Project myProject; - - public InspectionsConfigTreeRenderer(Project project) { - myProject = project; - } - +public abstract class InspectionsConfigTreeRenderer extends ColoredTreeCellRenderer { protected abstract String getFilter(); @Override - public void customizeRenderer(final JTree tree, + public void customizeCellRenderer(@NotNull final JTree tree, final Object value, final boolean selected, final boolean expanded, @@ -73,41 +67,25 @@ abstract class InspectionsConfigTreeRenderer extends CheckboxTree.CheckboxTreeCe style = SimpleTextAttributes.STYLE_BOLD; } else { - final Descriptor descriptor = node.getDescriptor(); - final String scopeName = node.getScopeName(); - if (scopeName != null) { - if (node.isByDefault()) { - text = "Everywhere else"; - } - else { - text = "In scope \'" + scopeName + "\'"; - if (node.getScope(myProject) == null) { - foreground = JBColor.RED; - } - } - } else { - text = descriptor.getText(); - } - hint = getHint(descriptor); + final ToolDescriptors descriptors = node.getDescriptors(); + assert descriptors != null; + final Descriptor defaultDescriptor = descriptors.getDefaultDescriptor(); + text = defaultDescriptor.getText(); + hint = getHint(defaultDescriptor); } if (text != null) { - SearchUtil.appendFragments(getFilter(), text, style, foreground, background, - getTextRenderer()); + SearchUtil.appendFragments(getFilter(), text, style, foreground, background, this); } if (hint != null) { - getTextRenderer() - .append(" " + hint, selected ? new SimpleTextAttributes(Font.PLAIN, foreground) : SimpleTextAttributes.GRAYED_ATTRIBUTES); + append(" " + hint, selected ? new SimpleTextAttributes(Font.PLAIN, foreground) : SimpleTextAttributes.GRAYED_ATTRIBUTES); } setForeground(foreground); } @Nullable - private static String getHint(Descriptor descriptor) { + private static String getHint(final Descriptor descriptor) { final InspectionToolWrapper toolWrapper = descriptor.getToolWrapper(); - if (toolWrapper == null) { - return InspectionsBundle.message("inspection.tool.availability.in.tree.node"); - } if (toolWrapper instanceof LocalInspectionToolWrapper || toolWrapper instanceof GlobalInspectionToolWrapper && !((GlobalInspectionToolWrapper)toolWrapper).worksInBatchModeOnly()) { return null; diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeTable.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeTable.java new file mode 100644 index 000000000000..966a456e1d38 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeTable.java @@ -0,0 +1,257 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui.inspectionsTree; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInsight.daemon.HighlightDisplayKey; +import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.codeInspection.ex.ScopeToolState; +import com.intellij.ide.IdeTooltip; +import com.intellij.ide.IdeTooltipManager; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.profile.codeInspection.ui.InspectionsAggregationUtil; +import com.intellij.profile.codeInspection.ui.table.ScopesAndSeveritiesTable; +import com.intellij.profile.codeInspection.ui.table.ThreeStateCheckBoxRenderer; +import com.intellij.ui.treeStructure.treetable.TreeTable; +import com.intellij.ui.treeStructure.treetable.TreeTableModel; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.table.TableColumn; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.*; +import java.util.List; + +/** + * @author Dmitry Batkovich + */ +public class InspectionsConfigTreeTable extends TreeTable { + private final static Logger LOG = Logger.getInstance(InspectionsConfigTreeTable.class); + + private final static int TREE_COLUMN = 0; + private final static int SEVERITIES_COLUMN = 1; + private final static int IS_ENABLED_COLUMN = 2; + + public InspectionsConfigTreeTable(final InspectionsConfigTreeTableSettings settings) { + super(new InspectionsConfigTreeTableModel(settings)); + + final TableColumn severitiesColumn = getColumnModel().getColumn(SEVERITIES_COLUMN); + severitiesColumn.setMaxWidth(20); + + final TableColumn isEnabledColumn = getColumnModel().getColumn(IS_ENABLED_COLUMN); + isEnabledColumn.setMaxWidth(20); + isEnabledColumn.setCellRenderer(new ThreeStateCheckBoxRenderer()); + isEnabledColumn.setCellEditor(new ThreeStateCheckBoxRenderer()); + + addMouseMotionListener(new MouseAdapter() { + @Override + public void mouseMoved(final MouseEvent e) { + final Point point = e.getPoint(); + final int column = columnAtPoint(point); + if (column != SEVERITIES_COLUMN) { + return; + } + final int row = rowAtPoint(point); + final Object maybeIcon = getModel().getValueAt(row, column); + if (maybeIcon instanceof MultiScopeSeverityIcon) { + final LinkedHashMap<String, HighlightSeverity> scopeToAverageSeverityMap = + ((MultiScopeSeverityIcon)maybeIcon).getScopeToAverageSeverityMap(); + IdeTooltipManager.getInstance().show(new IdeTooltip(InspectionsConfigTreeTable.this, point, new ScopesAndSeveritiesHintTable(scopeToAverageSeverityMap)), false); + } + } + }); + } + + public abstract static class InspectionsConfigTreeTableSettings { + private final TreeNode myRoot; + private final Project myProject; + + public InspectionsConfigTreeTableSettings(final TreeNode root, final Project project) { + myRoot = root; + myProject = project; + } + + public TreeNode getRoot() { + return myRoot; + } + + public Project getProject() { + return myProject; + } + + protected abstract InspectionProfileImpl getInspectionProfile(); + + protected abstract void onChanged(InspectionConfigTreeNode node); + } + + private static class InspectionsConfigTreeTableModel extends DefaultTreeModel implements TreeTableModel { + + private final InspectionsConfigTreeTableSettings mySettings; + + public InspectionsConfigTreeTableModel(final InspectionsConfigTreeTableSettings settings) { + super(settings.getRoot()); + mySettings = settings; + } + + @Override + public int getColumnCount() { + return 3; + } + + @Nullable + @Override + public String getColumnName(final int column) { + return null; + } + + @Override + public Class getColumnClass(final int column) { + switch (column) { + case TREE_COLUMN: + return TreeTableModel.class; + case SEVERITIES_COLUMN: + return Icon.class; + case IS_ENABLED_COLUMN: + return Boolean.class; + } + throw new IllegalArgumentException(); + } + + @Nullable + @Override + public Object getValueAt(final Object node, final int column) { + if (column == TREE_COLUMN) { + return null; + } + final InspectionConfigTreeNode treeNode = (InspectionConfigTreeNode)node; + final List<HighlightDisplayKey> inspectionsKeys = InspectionsAggregationUtil.getInspectionsKeys(treeNode); + if (column == SEVERITIES_COLUMN) { + final MultiColoredHighlightSeverityIconSink sink = new MultiColoredHighlightSeverityIconSink(); + for (final HighlightDisplayKey selectedInspectionsNode : inspectionsKeys) { + final String toolId = selectedInspectionsNode.toString(); + if (mySettings.getInspectionProfile().getTools(toolId, mySettings.getProject()).isEnabled()) { + sink.put(mySettings.getInspectionProfile().getToolDefaultState(toolId, mySettings.getProject()), + mySettings.getInspectionProfile().getNonDefaultTools(toolId, mySettings.getProject())); + } + } + return sink.constructIcon(); + } else if (column == IS_ENABLED_COLUMN) { + return isEnabled(inspectionsKeys); + } + throw new IllegalArgumentException(); + } + + @Nullable + private Boolean isEnabled(final List<HighlightDisplayKey> selectedInspectionsNodes) { + Boolean isPreviousEnabled = null; + for (final HighlightDisplayKey key : selectedInspectionsNodes) { + final boolean enabled = mySettings.getInspectionProfile().getTools(key.toString(), mySettings.getProject()).isEnabled(); + if (isPreviousEnabled == null) { + isPreviousEnabled = enabled; + } else if (!isPreviousEnabled.equals(enabled)) { + return null; + } + } + return isPreviousEnabled; + } + + @Override + public boolean isCellEditable(final Object node, final int column) { + return column == IS_ENABLED_COLUMN; + } + + @Override + public void setValueAt(final Object aValue, final Object node, final int column) { + LOG.assertTrue(column == IS_ENABLED_COLUMN); + LOG.assertTrue(aValue != null); + final boolean doEnable = (Boolean) aValue; + for (final InspectionConfigTreeNode aNode : InspectionsAggregationUtil.getInspectionsNodes((InspectionConfigTreeNode) node)) { + final String toolId = aNode.getKey().toString(); + if (doEnable) { + mySettings.getInspectionProfile().enableTool(toolId, mySettings.getProject()); + } else { + mySettings.getInspectionProfile().disableTool(toolId, mySettings.getProject()); + } + aNode.dropCache(); + mySettings.onChanged(aNode); + } + } + + @Override + public void setTree(final JTree tree) { + } + } + + private static class MultiColoredHighlightSeverityIconSink { + + private final LinkedHashMap<String, HighlightSeverity> myScopeToAverageSeverityMap = new LinkedHashMap<String, HighlightSeverity>(); + + private boolean myIsFirst = true; + + public Icon constructIcon() { + if (myScopeToAverageSeverityMap.isEmpty()) { + return null; + } + //TODO order scopes + return !allScopesHasMixedSeverity() + ? new MultiScopeSeverityIcon(myScopeToAverageSeverityMap) + : ScopesAndSeveritiesTable.MIXED_FAKE_LEVEL.getIcon(); + } + + private boolean allScopesHasMixedSeverity() { + for (final Map.Entry<String, HighlightSeverity> e : myScopeToAverageSeverityMap.entrySet()) { + if (!ScopesAndSeveritiesTable.MIXED_FAKE_SEVERITY.equals(e.getValue())) { + return false; + } + } + return true; + } + + public void put(final ScopeToolState defaultState, final Collection<ScopeToolState> nonDefault) { + putOne(defaultState); + for (final ScopeToolState scopeToolState : nonDefault) { + putOne(scopeToolState); + } + if (myIsFirst) { + myIsFirst = false; + } + } + + public void putOne(final ScopeToolState state) { + final Icon icon = state.getLevel().getIcon(); + final String scopeName = state.getScopeName(); + if (icon instanceof HighlightDisplayLevel.SingleColorIconWithMask) { + if (myIsFirst) { + myScopeToAverageSeverityMap.put(scopeName, state.getLevel().getSeverity()); + } else { + final HighlightSeverity severity = myScopeToAverageSeverityMap.get(scopeName); + if (!ScopesAndSeveritiesTable.MIXED_FAKE_SEVERITY.equals(severity) && !Comparing.equal(severity, state.getLevel().getSeverity())) { + myScopeToAverageSeverityMap.put(scopeName, ScopesAndSeveritiesTable.MIXED_FAKE_SEVERITY); + } + } + } else if (!ScopesAndSeveritiesTable.MIXED_FAKE_SEVERITY.equals(myScopeToAverageSeverityMap.get(scopeName))) { + myScopeToAverageSeverityMap.put(scopeName, ScopesAndSeveritiesTable.MIXED_FAKE_SEVERITY); + } + } + } +} diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/MultiScopeSeverityIcon.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/MultiScopeSeverityIcon.java new file mode 100644 index 000000000000..6f093ca66349 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/MultiScopeSeverityIcon.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui.inspectionsTree; + + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.ui.JBColor; + +import javax.swing.*; +import java.awt.*; +import java.util.Collection; +import java.util.LinkedHashMap; + +/** + * @author Dmitry Batkovich + */ +public class MultiScopeSeverityIcon implements Icon { + private final static JBColor MIXED_SEVERITY_COLOR = JBColor.DARK_GRAY; + + private final static int SIZE = 12; + + private final LinkedHashMap<String, HighlightSeverity> myScopeToAverageSeverityMap; + + public MultiScopeSeverityIcon(final LinkedHashMap<String, HighlightSeverity> scopeToAverageSeverityMap) { + myScopeToAverageSeverityMap = scopeToAverageSeverityMap; + } + + public LinkedHashMap<String, HighlightSeverity> getScopeToAverageSeverityMap() { + return myScopeToAverageSeverityMap; + } + + @Override + public void paintIcon(final Component c, final Graphics g, final int i, final int j) { + final int iconWidth = getIconWidth(); + + final int partWidth = iconWidth / myScopeToAverageSeverityMap.size(); + + final Collection<HighlightSeverity> values = myScopeToAverageSeverityMap.values(); + int idx = 0; + for (final HighlightSeverity severity : values) { + final Icon icon = HighlightDisplayLevel.find(severity).getIcon(); + g.setColor(icon instanceof HighlightDisplayLevel.SingleColorIconWithMask ? + ((HighlightDisplayLevel.SingleColorIconWithMask)icon).getColor() : MIXED_SEVERITY_COLOR); + final int x = i + partWidth * idx; + g.fillRect(x, j, partWidth, getIconHeight()); + idx++; + } + g.drawImage(HighlightDisplayLevel.ImageHolder.ourErrorMaskImage, i, j, null); + } + + @Override + public int getIconWidth() { + return SIZE; + } + + @Override + public int getIconHeight() { + return SIZE; + } +} diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/ScopesAndSeveritiesHintTable.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/ScopesAndSeveritiesHintTable.java new file mode 100644 index 000000000000..05cb7ab193f5 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/ScopesAndSeveritiesHintTable.java @@ -0,0 +1,106 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui.inspectionsTree; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.profile.codeInspection.ui.SingleInspectionProfilePanel; +import com.intellij.ui.table.JBTable; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import java.awt.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * @author Dmitry Batkovich + */ +public class ScopesAndSeveritiesHintTable extends JBTable { + private final static int SCOPE_COLUMN = 0; + private final static int SEVERITY_COLUMN = 1; + + public ScopesAndSeveritiesHintTable(final LinkedHashMap<String, HighlightSeverity> scopeToAverageSeverityMap) { + super(new MyModel(scopeToAverageSeverityMap)); + + final DefaultTableCellRenderer cellRenderer = new DefaultTableCellRenderer(); + cellRenderer.setOpaque(false); + getColumnModel().getColumn(SCOPE_COLUMN).setCellRenderer(cellRenderer); + + getColumnModel().getColumn(SEVERITY_COLUMN).setCellRenderer(new DefaultTableCellRenderer() { + @Override + public Component getTableCellRendererComponent(final JTable table, + final Object value, + final boolean isSelected, + final boolean hasFocus, + final int row, + final int column) { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + final HighlightSeverity severity = (HighlightSeverity)value; + setIcon(HighlightDisplayLevel.find(severity).getIcon()); + setText(SingleInspectionProfilePanel.renderSeverity(severity)); + setOpaque(false); + return this; + } + }); + setShowGrid(false); + setRowSelectionAllowed(false); + setColumnSelectionAllowed(false); + setOpaque(false); + } + + private final static class MyModel extends AbstractTableModel { + + private final LinkedHashMap<String, HighlightSeverity> myScopeToAverageSeverityMap; + private final List<String> myScopes; + + public MyModel(final LinkedHashMap<String, HighlightSeverity> scopeToAverageSeverityMap) { + myScopeToAverageSeverityMap = scopeToAverageSeverityMap; + myScopes = new ArrayList<String>(myScopeToAverageSeverityMap.keySet()); + } + + @Override + public Class<?> getColumnClass(final int columnIndex) { + switch (columnIndex) { + case SCOPE_COLUMN: return String.class; + case SEVERITY_COLUMN: return HighlightSeverity.class; + default: throw new IllegalArgumentException(); + } + } + + @Override + public int getRowCount() { + return myScopes.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public Object getValueAt(final int rowIndex, final int columnIndex) { + switch (columnIndex) { + case SCOPE_COLUMN: return myScopes.get(rowIndex); + case SEVERITY_COLUMN: return myScopeToAverageSeverityMap.get(myScopes.get(rowIndex)); + default: throw new IllegalArgumentException(); + } + + } + } +} diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/ScopesAndSeveritiesTable.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/ScopesAndSeveritiesTable.java new file mode 100644 index 000000000000..ad7dc946fff8 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/ScopesAndSeveritiesTable.java @@ -0,0 +1,404 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui.table; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInsight.daemon.HighlightDisplayKey; +import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.codeInspection.ex.ScopeToolState; +import com.intellij.icons.AllIcons; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.profile.codeInspection.ui.AddScopeUtil; +import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionConfigTreeNode; +import com.intellij.psi.search.scope.packageSet.NamedScope; +import com.intellij.ui.table.JBTable; +import com.intellij.ui.treeStructure.treetable.TreeTable; +import com.intellij.util.ArrayUtil; +import com.intellij.util.SmartList; +import com.intellij.util.ui.EditableModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; + +/** + * @author Dmitry Batkovich + */ +public class ScopesAndSeveritiesTable extends JBTable { + private final static Logger LOG = Logger.getInstance(ScopesAndSeveritiesTable.class); + + public static final HighlightSeverity MIXED_FAKE_SEVERITY = new HighlightSeverity("Mixed", -1); + @SuppressWarnings("UnusedDeclaration") + public static final HighlightDisplayLevel MIXED_FAKE_LEVEL = new HighlightDisplayLevel(MIXED_FAKE_SEVERITY, AllIcons.Actions.Help); + + private final static int SCOPE_ENABLED_COLUMN = 0; + private final static int SCOPE_NAME_COLUMN = 1; + private final static int SEVERITY_COLUMN = 2; + + public ScopesAndSeveritiesTable(final TableSettings tableSettings) { + super(new MyTableModel(tableSettings)); + + final TableColumnModel columnModel = getColumnModel(); + + final TableColumn scopeEnabledColumn = columnModel.getColumn(SCOPE_ENABLED_COLUMN); + scopeEnabledColumn.setMaxWidth(30); + scopeEnabledColumn.setCellRenderer(new ThreeStateCheckBoxRenderer()); + scopeEnabledColumn.setCellEditor(new ThreeStateCheckBoxRenderer()); + + final TableColumn severityColumn = columnModel.getColumn(SEVERITY_COLUMN); + severityColumn.setCellRenderer(SeverityRenderer.create(tableSettings.getInspectionProfile())); + severityColumn.setCellEditor(SeverityRenderer.create(tableSettings.getInspectionProfile())); + + setColumnSelectionAllowed(false); + setRowSelectionAllowed(true); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(final ListSelectionEvent e) { + final int idx = getSelectionModel().getMinSelectionIndex(); + if (idx >= 0) { + final ExistedScopesStatesAndNonExistNames scopeToolState = ((MyTableModel)getModel()).getScopeToolState(idx); + final List<ScopeToolState> existedStates = scopeToolState.getExistedStates(); + if (existedStates.size() == 1) { + tableSettings.onScopeChosen(existedStates.get(0)); + } + } + } + }); + setRowSelectionInterval(0, 0); + + setStriped(true); + setShowGrid(false); + } + + public abstract static class TableSettings { + private final List<InspectionConfigTreeNode> myNodes; + private final List<String> myKeyNames; + private final List<HighlightDisplayKey> myKeys; + private final InspectionProfileImpl myInspectionProfile; + private final TreeTable myTreeTable; + private final Project myProject; + + protected TableSettings(final List<InspectionConfigTreeNode> nodes, + final InspectionProfileImpl inspectionProfile, + final TreeTable treeTable, + final Project project) { + myNodes = nodes; + myKeys = new ArrayList<HighlightDisplayKey>(myNodes.size()); + myKeyNames = new ArrayList<String>(myNodes.size()); + for(final InspectionConfigTreeNode node : nodes) { + final HighlightDisplayKey key = node.getDefaultDescriptor().getKey(); + myKeys.add(key); + myKeyNames.add(key.toString()); + } + + myInspectionProfile = inspectionProfile; + myTreeTable = treeTable; + myProject = project; + } + + public List<HighlightDisplayKey> getKeys() { + return myKeys; + } + + public List<String> getKeyNames() { + return myKeyNames; + } + + public List<InspectionConfigTreeNode> getNodes() { + return myNodes; + } + + public InspectionProfileImpl getInspectionProfile() { + return myInspectionProfile; + } + + public TreeTable getTreeTable() { + return myTreeTable; + } + + public Project getProject() { + return myProject; + } + + protected abstract void onScopeAdded(); + + protected abstract void onScopeRemoved(final int scopesCount); + + protected abstract void onScopeChosen(final @NotNull ScopeToolState scopeToolState); + + protected abstract void onChange(); + } + + @NotNull + public static HighlightSeverity getSeverity(final List<ScopeToolState> scopeToolStates) { + HighlightSeverity previousValue = null; + for (final ScopeToolState scopeToolState : scopeToolStates) { + final HighlightSeverity currentValue = scopeToolState.getLevel().getSeverity(); + if (previousValue == null) { + previousValue = currentValue; + } else if (!previousValue.equals(currentValue)){ + return MIXED_FAKE_SEVERITY; + } + } + return previousValue; + } + + private static class MyTableModel extends AbstractTableModel implements EditableModel { + private final InspectionProfileImpl myInspectionProfile; + private final List<String> myKeyNames; + private final List<InspectionConfigTreeNode> myNodes; + private final TreeTable myTreeTable; + private final Project myProject; + private final TableSettings myTableSettings; + private final List<HighlightDisplayKey> myKeys; + + private String[] myScopeNames; + + public MyTableModel(final TableSettings tableSettings) { + myTableSettings = tableSettings; + myProject = tableSettings.getProject(); + myInspectionProfile = tableSettings.getInspectionProfile(); + myKeys = tableSettings.getKeys(); + myKeyNames = tableSettings.getKeyNames(); + myNodes = tableSettings.getNodes(); + myTreeTable = tableSettings.getTreeTable(); + refreshAggregatedScopes(); + } + + @Override + public boolean isCellEditable(final int rowIndex, final int columnIndex) { + return columnIndex != SCOPE_NAME_COLUMN; + } + + @Override + public int getRowCount() { + return lastRowIndex() + 1; + } + + @Nullable + @Override + public String getColumnName(final int column) { + return null; + } + + @Override + public int getColumnCount() { + return 3; + } + + @Override + public Class<?> getColumnClass(final int columnIndex) { + if (SCOPE_ENABLED_COLUMN == columnIndex) { + return Boolean.class; + } + if (SCOPE_NAME_COLUMN == columnIndex) { + return String.class; + } + if (SEVERITY_COLUMN == columnIndex) { + return HighlightSeverity.class; + } + throw new IllegalArgumentException(); + } + + @Override + public Object getValueAt(final int rowIndex, final int columnIndex) { + if (rowIndex < 0) { + return null; + } + switch (columnIndex) { + case SCOPE_ENABLED_COLUMN: + return isEnabled(rowIndex); + case SCOPE_NAME_COLUMN: + return getScope(rowIndex).getName(); + case SEVERITY_COLUMN: + return getSeverity(rowIndex); + default: + throw new IllegalArgumentException("Invalid column index " + columnIndex); + } + } + + private NamedScope getScope(final int rowIndex) { + return getScopeToolState(rowIndex).getExistedStates().get(0).getScope(myProject); + } + + @NotNull + private HighlightSeverity getSeverity(final int rowIndex) { + final ExistedScopesStatesAndNonExistNames existedScopesStatesAndNonExistNames = getScopeToolState(rowIndex); + if (!existedScopesStatesAndNonExistNames.getNonExistNames().isEmpty()) { + return MIXED_FAKE_SEVERITY; + } + return ScopesAndSeveritiesTable.getSeverity(existedScopesStatesAndNonExistNames.getExistedStates()); + } + + @Nullable + private Boolean isEnabled(final int rowIndex) { + Boolean previousValue = null; + final ExistedScopesStatesAndNonExistNames existedScopesStatesAndNonExistNames = getScopeToolState(rowIndex); + for (final ScopeToolState scopeToolState : existedScopesStatesAndNonExistNames.getExistedStates()) { + final boolean currentValue = scopeToolState.isEnabled(); + if (previousValue == null) { + previousValue = currentValue; + } else if (!previousValue.equals(currentValue)){ + return null; + } + } + if (!existedScopesStatesAndNonExistNames.getNonExistNames().isEmpty() && !Boolean.FALSE.equals(previousValue)) { + return null; + } + return previousValue; + } + + private ExistedScopesStatesAndNonExistNames getScopeToolState(final int rowIndex) { + final List<String> nonExistNames = new SmartList<String>(); + final List<ScopeToolState> existedStates = new SmartList<ScopeToolState>(); + for (final String keyName : myKeyNames) { + final ScopeToolState scopeToolState = getScopeToolState(keyName, rowIndex); + if (scopeToolState != null) { + existedStates.add(scopeToolState); + } else { + nonExistNames.add(keyName); + } + } + return new ExistedScopesStatesAndNonExistNames(existedStates, nonExistNames); + } + + @Nullable + private ScopeToolState getScopeToolState(final String keyName, final int rowIndex) { + if (rowIndex == lastRowIndex()) { + return myInspectionProfile.getToolDefaultState(keyName, myProject); + } + else { + final String scopeName = myScopeNames[rowIndex]; + final List<ScopeToolState> nonDefaultTools = myInspectionProfile.getNonDefaultTools(keyName, myProject); + for (final ScopeToolState nonDefaultTool : nonDefaultTools) { + if (Comparing.equal(scopeName, nonDefaultTool.getScopeName())) { + return nonDefaultTool; + } + } + } + return null; + } + + private void refreshAggregatedScopes() { + final LinkedHashSet<String> scopesNames = new LinkedHashSet<String>(); + for (final String keyName : myKeyNames) { + final List<ScopeToolState> nonDefaultTools = myInspectionProfile.getNonDefaultTools(keyName, myProject); + for (final ScopeToolState tool : nonDefaultTools) { + scopesNames.add(tool.getScopeName()); + } + } + myScopeNames = ArrayUtil.toStringArray(scopesNames); + } + + private int lastRowIndex() { + return myScopeNames.length; + } + + @Override + public void setValueAt(final Object value, final int rowIndex, final int columnIndex) { + if (value == null) { + return; + } + if (columnIndex == SEVERITY_COLUMN) { + final HighlightDisplayLevel level = HighlightDisplayLevel.find(((HighlightSeverity)value).getName()); + if (level == null) { + LOG.error("no display level found for name " + ((HighlightSeverity)value).getName()); + return; + } + final int idx = rowIndex == lastRowIndex() ? -1 : rowIndex; + myInspectionProfile.setErrorLevel(myKeys, level, idx, myProject); + } + else if (columnIndex == SCOPE_ENABLED_COLUMN) { + final NamedScope scope = getScope(rowIndex); + if ((Boolean)value) { + if (rowIndex == lastRowIndex()) { + myInspectionProfile.enableToolsByDefault(myKeyNames, myProject); + } + else { + //TODO create scopes states if not exist (need scope sorting) + myInspectionProfile.enableTools(myKeyNames, scope, myProject); + } + } + else { + if (rowIndex == lastRowIndex()) { + myInspectionProfile.disableToolByDefault(myKeyNames, myProject); + } + else { + myInspectionProfile.disableTools(myKeyNames, scope, myProject); + } + } + } + myTableSettings.onChange(); + } + + @Override + public void removeRow(final int idx) { + if (idx != lastRowIndex()) { + myInspectionProfile.removeScopes(myKeyNames, getScope(idx), myProject); + refreshAggregatedScopes(); + myTableSettings.onScopeRemoved(getRowCount()); + } + } + + @Override + public void addRow() { + AddScopeUtil.performAddScope(myTreeTable, myProject, myInspectionProfile, myNodes); + myTableSettings.onScopeAdded(); + refreshAggregatedScopes(); + } + + @Override + public void exchangeRows(final int oldIndex, final int newIndex) { + } + + @Override + public boolean canExchangeRows(final int oldIndex, final int newIndex) { + return false; + } + } + + private static class ExistedScopesStatesAndNonExistNames { + + private final List<ScopeToolState> myExistedStates; + private final List<String> myNonExistNames; + + public ExistedScopesStatesAndNonExistNames(final List<ScopeToolState> existedStates, final List<String> nonExistNames) { + myExistedStates = existedStates; + myNonExistNames = nonExistNames; + } + + public List<ScopeToolState> getExistedStates() { + return myExistedStates; + } + + public List<String> getNonExistNames() { + return myNonExistNames; + } + } +}
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/SeverityRenderer.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/SeverityRenderer.java new file mode 100644 index 000000000000..2fe95e6d7224 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/SeverityRenderer.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui.table; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.ui.ComboBoxTableRenderer; +import com.intellij.profile.codeInspection.SeverityProvider; +import com.intellij.profile.codeInspection.ui.LevelChooserAction; +import com.intellij.profile.codeInspection.ui.SingleInspectionProfilePanel; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.event.MouseEvent; +import java.util.EventObject; +import java.util.SortedSet; + +/** + * @author Dmitry Batkovich + */ +public class SeverityRenderer extends ComboBoxTableRenderer<HighlightSeverity> { + public SeverityRenderer(final HighlightSeverity[] values) { + super(values); + } + + public static SeverityRenderer create(final InspectionProfileImpl inspectionProfile) { + final SortedSet<HighlightSeverity> severities = + LevelChooserAction.getSeverities(((SeverityProvider)inspectionProfile.getProfileManager()).getOwnSeverityRegistrar()); + return new SeverityRenderer(severities.toArray(new HighlightSeverity[severities.size()])); + } + + + @Override + protected String getTextFor(@NotNull final HighlightSeverity value) { + return SingleInspectionProfilePanel.renderSeverity(value); + } + + @Override + protected Icon getIconFor(@NotNull final HighlightSeverity value) { + return HighlightDisplayLevel.find(value).getIcon(); + } + + @Override + public boolean isCellEditable(final EventObject event) { + if (event instanceof MouseEvent) { + return ((MouseEvent)event).getClickCount() >= 1; + } + return true; + } +} diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/ThreeStateCheckBoxRenderer.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/ThreeStateCheckBoxRenderer.java new file mode 100644 index 000000000000..7d8cfdfc9db5 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/ThreeStateCheckBoxRenderer.java @@ -0,0 +1,127 @@ +/* + * Copyright 2000-2014 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.profile.codeInspection.ui.table; + +import com.intellij.ui.ClickListener; +import com.intellij.util.SmartList; +import com.intellij.util.ui.ThreeStateCheckBox; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.EventObject; +import java.util.List; + +/** + * @author Dmitry Batkovich + */ +public class ThreeStateCheckBoxRenderer extends ThreeStateCheckBox implements TableCellRenderer, TableCellEditor { + + private final List<CellEditorListener> myListeners = new SmartList<CellEditorListener>(); + + public ThreeStateCheckBoxRenderer() { + setThirdStateEnabled(false); + setHorizontalAlignment(CENTER); + setVerticalAlignment(CENTER); + } + + @Override + public Component getTableCellEditorComponent(final JTable table, final Object value, final boolean isSelected, final int row, final int column) { + return tune(value, isSelected, row, table); + } + + @Override + public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { + return tune(value, isSelected, row, table); + } + + private JCheckBox tune(final Object value, final boolean isSelected, final int row, final JTable table) { + final Color bg = UIUtil.isUnderNimbusLookAndFeel() && row % 2 == 1 ? UIUtil.TRANSPARENT_COLOR : table.getBackground(); + final Color fg = table.getForeground(); + final Color selBg = table.getSelectionBackground(); + final Color selFg = table.getSelectionForeground(); + + setForeground(isSelected ? selFg : fg); + setBackground(isSelected ? selBg : bg); + + if (value == null) { + setState(State.DONT_CARE); + } else { + setSelected((Boolean) value); + } + new ClickListener() { + @Override + public boolean onClick(@NotNull final MouseEvent event, final int clickCount) { + if (clickCount == 1) { + stopCellEditing(); + return true; + } + return false; + } + }.installOn(this); + return this; + } + + @Nullable + @Override + public Object getCellEditorValue() { + return getState() != State.DONT_CARE ? isSelected() : null; + } + + @Override + public boolean isCellEditable(final EventObject anEvent) { + return true; + } + + @Override + public boolean shouldSelectCell(final EventObject anEvent) { + return true; + } + + @Override + public boolean stopCellEditing() { + final ChangeEvent e = new ChangeEvent(this); + for (final CellEditorListener listener : myListeners) { + listener.editingStopped(e); + } + return true; + } + + @Override + public void cancelCellEditing() { + final ChangeEvent e = new ChangeEvent(this); + for (final CellEditorListener listener : myListeners) { + listener.editingCanceled(e); + } + } + + @Override + public void addCellEditorListener(final CellEditorListener l) { + myListeners.add(l); + } + + @Override + public void removeCellEditorListener(final CellEditorListener l) { + myListeners.remove(l); + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/file/DirectoryIconProvider.java b/platform/lang-impl/src/com/intellij/psi/impl/file/DirectoryIconProvider.java index d12bc939293f..6aaa810340bd 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/file/DirectoryIconProvider.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/file/DirectoryIconProvider.java @@ -20,11 +20,15 @@ */ package com.intellij.psi.impl.file; +import com.intellij.icons.AllIcons; import com.intellij.ide.IconProvider; import com.intellij.ide.projectView.impl.ProjectRootsUtil; import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.roots.SourceFolder; import com.intellij.openapi.roots.ui.configuration.SourceRootPresentation; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; @@ -39,11 +43,18 @@ public class DirectoryIconProvider extends IconProvider implements DumbAware { if (element instanceof PsiDirectory) { final PsiDirectory psiDirectory = (PsiDirectory)element; final VirtualFile vFile = psiDirectory.getVirtualFile(); - SourceFolder sourceFolder = ProjectRootsUtil.getModuleSourceRoot(vFile, psiDirectory.getProject()); + Project project = psiDirectory.getProject(); + SourceFolder sourceFolder = ProjectRootsUtil.getModuleSourceRoot(vFile, project); if (sourceFolder != null) { return SourceRootPresentation.getSourceRootIcon(sourceFolder); } else { + if (!Registry.is("ide.hide.excluded.files")) { + boolean ignored = ProjectRootManager.getInstance(project).getFileIndex().isExcluded(vFile); + if (ignored) { + return AllIcons.Modules.ExcludeRoot; + } + } return PlatformIcons.DIRECTORY_CLOSED_ICON; } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.java b/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.java index bb2c938e3d08..17dd2bca1f17 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.java @@ -443,7 +443,7 @@ public class PsiVFSListener extends VirtualFileAdapter { public void run() { PsiTreeChangeEventImpl treeEvent = new PsiTreeChangeEventImpl(myManager); - boolean isExcluded = vFile.isDirectory() && myProjectRootManager.getFileIndex().isIgnored(vFile); + boolean isExcluded = vFile.isDirectory() && myProjectRootManager.getFileIndex().isExcluded(vFile); if (oldParentDir != null && !isExcluded) { if (newParentDir != null) { treeEvent.setOldParent(oldParentDir); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/PostprocessReformattingAspect.java b/platform/lang-impl/src/com/intellij/psi/impl/source/PostprocessReformattingAspect.java index 63413321ce41..c5b183c3594b 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/PostprocessReformattingAspect.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/PostprocessReformattingAspect.java @@ -636,7 +636,7 @@ public class PostprocessReformattingAspect implements PomModelAspect { final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myPsiManager.getProject()); final Document document = viewProvider.getDocument(); assert document != null; - final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(styleSettings); + final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(styleSettings, viewProvider.getBaseLanguage()); documentManager.commitDocument(document); return codeFormatter; diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java index e76aecfa1e0b..fa1dd9e86b4a 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java @@ -78,10 +78,12 @@ public class CodeFormatterFacade { private final CodeStyleSettings mySettings; private final FormatterTagHandler myTagHandler; + private final int myRightMargin; - public CodeFormatterFacade(CodeStyleSettings settings) { + public CodeFormatterFacade(CodeStyleSettings settings, @Nullable Language language) { mySettings = settings; myTagHandler = new FormatterTagHandler(settings); + myRightMargin = mySettings.getRightMargin(language); } public ASTNode processElement(ASTNode element) { @@ -637,8 +639,8 @@ public class CodeFormatterFacade { } private int wrapPositionForTextWithoutTabs(int startLineOffset, int endLineOffset, int targetRangeEndOffset) { - if (Math.min(endLineOffset, targetRangeEndOffset) - startLineOffset > mySettings.RIGHT_MARGIN) { - return startLineOffset + mySettings.RIGHT_MARGIN - FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS; + if (Math.min(endLineOffset, targetRangeEndOffset) - startLineOffset > myRightMargin) { + return startLineOffset + myRightMargin - FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS; } return -1; } @@ -659,13 +661,13 @@ public class CodeFormatterFacade { case '\t': symbolWidth = tabSize - (width % tabSize); break; default: symbolWidth = 1; } - if (width + symbolWidth + FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS >= mySettings.RIGHT_MARGIN + if (width + symbolWidth + FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS >= myRightMargin && (Math.min(endLineOffset, targetRangeEndOffset) - i) >= FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS) { // Remember preferred position. result = i - 1; } - if (width + symbolWidth >= mySettings.RIGHT_MARGIN) { + if (width + symbolWidth >= myRightMargin) { wrapLine = true; break; } @@ -700,12 +702,12 @@ public class CodeFormatterFacade { break; default: newX = x + EditorUtil.charWidth(c, Font.PLAIN, editor); symbolWidth = 1; } - if (width + symbolWidth + FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS >= mySettings.RIGHT_MARGIN + if (width + symbolWidth + FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS >= myRightMargin && (Math.min(endLineOffset, targetRangeEndOffset) - i) >= FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS) { result = i - 1; } - if (width + symbolWidth >= mySettings.RIGHT_MARGIN) { + if (width + symbolWidth >= myRightMargin) { wrapLine = true; break; } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleFacadeImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleFacadeImpl.java index a9031f3133a3..81eda35222dd 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleFacadeImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleFacadeImpl.java @@ -20,6 +20,7 @@ package com.intellij.psi.impl.source.codeStyle; import com.intellij.codeStyle.CodeStyleFacade; +import com.intellij.lang.Language; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.options.Configurable; @@ -68,8 +69,8 @@ public class CodeStyleFacadeImpl extends CodeStyleFacade { } @Override - public int getRightMargin() { - return CodeStyleSettingsManager.getSettings(myProject).RIGHT_MARGIN; + public int getRightMargin(Language language) { + return CodeStyleSettingsManager.getSettings(myProject).getRightMargin(language); } @Override diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java index 043ac483d2ed..767152fc1e2b 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java @@ -101,7 +101,7 @@ public class CodeStyleManagerImpl extends CodeStyleManager { } ASTNode treeElement = SourceTreeToPsiMap.psiElementToTree(element); - final PsiElement formatted = SourceTreeToPsiMap.treeElementToPsi(new CodeFormatterFacade(getSettings()).processElement(treeElement)); + final PsiElement formatted = SourceTreeToPsiMap.treeElementToPsi(new CodeFormatterFacade(getSettings(), element.getLanguage()).processElement(treeElement)); if (!canChangeWhiteSpacesOnly) { return postProcessElement(formatted); } @@ -180,7 +180,7 @@ public class CodeStyleManagerImpl extends CodeStyleManager { ASTNode treeElement = SourceTreeToPsiMap.psiElementToTree(file); transformAllChildren(treeElement); - final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings()); + final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings(), file.getLanguage()); LOG.assertTrue(file.isValid()); if (editor == null) { @@ -274,7 +274,7 @@ public class CodeStyleManagerImpl extends CodeStyleManager { } ASTNode treeElement = SourceTreeToPsiMap.psiElementToTree(element); - final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings()); + final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings(), element.getLanguage()); final PsiElement formatted = SourceTreeToPsiMap.treeElementToPsi(codeFormatter.processRange(treeElement, startOffset, endOffset)); return canChangeWhiteSpacesOnly ? formatted : postProcessElement(formatted); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java index f2863469d614..27089fa70e91 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java @@ -20,6 +20,7 @@ import com.intellij.injected.editor.*; import com.intellij.lang.Language; import com.intellij.lang.LanguageUtil; import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.SelectionModel; @@ -158,6 +159,22 @@ public class InjectedLanguageUtil { return getEditorForInjectedLanguageNoCommit(editor, file, offset); } + public static Caret getCaretForInjectedLanguageNoCommit(@Nullable Caret caret, @Nullable PsiFile file) { + if (caret == null || file == null || caret instanceof InjectedCaret) return caret; + + PsiFile injectedFile = findInjectedPsiNoCommit(file, caret.getOffset()); + Editor injectedEditor = getInjectedEditorForInjectedFile(caret.getEditor(), injectedFile); + if (!(injectedEditor instanceof EditorWindow)) { + return caret; + } + for (Caret injectedCaret : injectedEditor.getCaretModel().getAllCarets()) { + if (((InjectedCaret)injectedCaret).getDelegate() == caret) { + return injectedCaret; + } + } + return null; + } + public static Editor getEditorForInjectedLanguageNoCommit(@Nullable Editor editor, @Nullable PsiFile file, final int offset) { if (editor == null || file == null || editor instanceof EditorWindow) return editor; PsiFile injectedFile = findInjectedPsiNoCommit(file, offset); diff --git a/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java b/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java index fc17bf25a33f..d791f14aa841 100644 --- a/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java @@ -154,15 +154,24 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe break; } catch (IOException e) { - LOG.info(e); needRebuild = true; - FileUtil.delete(indexRootDir); - IndexingStamp.rewriteVersion(versionFile, version); // todo snapshots indices + onExceptionInstantiatingIndex(version, versionFile, indexRootDir, e); + } catch (RuntimeException e) { + //noinspection ThrowableResultOfMethodCallIgnored + Throwable cause = FileBasedIndexImpl.getCauseToRebuildIndex(e); + if (cause == null) throw e; + onExceptionInstantiatingIndex(version, versionFile, indexRootDir, e); } } return needRebuild; } + private static void onExceptionInstantiatingIndex(int version, File versionFile, File indexRootDir, Exception e) throws IOException { + LOG.info(e); + FileUtil.delete(indexRootDir); + IndexingStamp.rewriteVersion(versionFile, version); // todo snapshots indices + } + private static class StubIdExternalizer implements DataExternalizer<StubIdList> { @Override public void save(@NotNull final DataOutput out, @NotNull final StubIdList value) throws IOException { @@ -207,7 +216,7 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe public <Key, Psi extends PsiElement> Collection<Psi> get(@NotNull final StubIndexKey<Key, Psi> indexKey, @NotNull final Key key, @NotNull final Project project, - final GlobalSearchScope scope) { + @Nullable final GlobalSearchScope scope) { return get(indexKey, key, project, scope, null); } @@ -215,7 +224,7 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe public <Key, Psi extends PsiElement> Collection<Psi> get(@NotNull StubIndexKey<Key, Psi> indexKey, @NotNull Key key, @NotNull Project project, - GlobalSearchScope scope, + @Nullable GlobalSearchScope scope, IdFilter filter) { final List<Psi> result = new SmartList<Psi>(); process(indexKey, key, project, scope, filter, new CommonProcessors.CollectProcessor<Psi>(result)); @@ -226,7 +235,7 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe public <Key, Psi extends PsiElement> boolean processElements(@NotNull StubIndexKey<Key, Psi> indexKey, @NotNull Key key, @NotNull Project project, - GlobalSearchScope scope, + @Nullable GlobalSearchScope scope, Class<Psi> requiredClass, @NotNull Processor<? super Psi> processor) { return processElements(indexKey, key, project, scope, null, requiredClass, processor); @@ -238,7 +247,7 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe @NotNull final Project project, @Nullable final GlobalSearchScope scope, @Nullable IdFilter idFilter, - final Class<Psi> requiredClass, + @NotNull final Class<Psi> requiredClass, @NotNull final Processor<? super Psi> processor) { final FileBasedIndexImpl fileBasedIndex = (FileBasedIndexImpl)FileBasedIndex.getInstance(); fileBasedIndex.ensureUpToDate(StubUpdatingIndex.INDEX_ID, project, scope); @@ -315,7 +324,7 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe return processAllKeys(indexKey, processor, GlobalSearchScope.allScope(project), null); } - public <K> boolean processAllKeys(@NotNull StubIndexKey<K, ?> indexKey, Processor<K> processor, GlobalSearchScope scope, @Nullable IdFilter idFilter) { + public <K> boolean processAllKeys(@NotNull StubIndexKey<K, ?> indexKey, @NotNull Processor<K> processor, @NotNull GlobalSearchScope scope, @Nullable IdFilter idFilter) { FileBasedIndex.getInstance().ensureUpToDate(StubUpdatingIndex.INDEX_ID, scope.getProject(), scope); diff --git a/platform/lang-impl/src/com/intellij/refactoring/actions/RefactoringQuickListPopupAction.java b/platform/lang-impl/src/com/intellij/refactoring/actions/RefactoringQuickListPopupAction.java index aea982678b9d..159279570cf2 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/actions/RefactoringQuickListPopupAction.java +++ b/platform/lang-impl/src/com/intellij/refactoring/actions/RefactoringQuickListPopupAction.java @@ -26,6 +26,10 @@ import org.jetbrains.annotations.Nullable; public class RefactoringQuickListPopupAction extends QuickSwitchSchemeAction { + public RefactoringQuickListPopupAction() { + setInjectedContext(true); + } + @Override protected void fillActions(@Nullable final Project project, @NotNull final DefaultActionGroup group, @@ -64,6 +68,7 @@ public class RefactoringQuickListPopupAction extends QuickSwitchSchemeAction { child instanceof CopyElementAction) { final Presentation presentation = new Presentation(); final AnActionEvent event = new AnActionEvent(null, dataContext, ActionPlaces.UNKNOWN, presentation, actionManager, 0); + event.setInjectedContext(child.isInInjectedContext()); child.update(event); if (presentation.isEnabled() && presentation.isVisible()) { destinationGroup.add(child); diff --git a/platform/lang-impl/src/com/intellij/refactoring/introduce/inplace/AbstractInplaceIntroducer.java b/platform/lang-impl/src/com/intellij/refactoring/introduce/inplace/AbstractInplaceIntroducer.java index e761e9a3f2f3..81a2885c2022 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/introduce/inplace/AbstractInplaceIntroducer.java +++ b/platform/lang-impl/src/com/intellij/refactoring/introduce/inplace/AbstractInplaceIntroducer.java @@ -45,6 +45,8 @@ import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.RefactoringActionHandler; +import com.intellij.refactoring.listeners.RefactoringEventData; +import com.intellij.refactoring.listeners.RefactoringEventListener; import com.intellij.refactoring.rename.inplace.InplaceRefactoring; import com.intellij.ui.DottedBorder; import com.intellij.util.ui.PositionTracker; @@ -537,6 +539,13 @@ public abstract class AbstractInplaceIntroducer<V extends PsiNameIdentifierOwner CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { @Override public void run() { + final String refactoringId = getRefactoringId(); + if (refactoringId != null) { + final RefactoringEventData beforeData = new RefactoringEventData(); + beforeData.addElements(new PsiElement[] {getLocalVariable(), getExpr()}); + myProject.getMessageBus() + .syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringStarted(refactoringId, beforeData); + } performIntroduce(); } }, getCommandName(), getCommandName()); @@ -567,9 +576,20 @@ public abstract class AbstractInplaceIntroducer<V extends PsiNameIdentifierOwner } if (success) { performPostIntroduceTasks(); + final String refactoringId = getRefactoringId(); + if (refactoringId != null) { + final RefactoringEventData afterData = new RefactoringEventData(); + afterData.addElement(getVariable()); + myProject.getMessageBus() + .syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringDone(refactoringId, afterData); + } } } + protected String getRefactoringId() { + return null; + } + @Override protected boolean startsOnTheSameElement(RefactoringActionHandler handler, PsiElement element) { return super.startsOnTheSameElement(handler, element) || getLocalVariable() == element; diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java index db30a105865a..aa4f696df179 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java @@ -773,32 +773,31 @@ public abstract class InplaceRefactoring { if (ApplicationManager.getApplication().isHeadlessEnvironment()) return; final BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createDialogBalloonBuilder(component, null).setSmallVariant(true); myBalloon = balloonBuilder.createBalloon(); - final Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor); Disposer.register(myProject, myBalloon); Disposer.register(myBalloon, new Disposable() { @Override public void dispose() { releaseIfNotRestart(); - topLevelEditor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION, null); + myEditor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION, null); } }); - topLevelEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); + myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); final JBPopupFactory popupFactory = JBPopupFactory.getInstance(); - myBalloon.show(new PositionTracker<Balloon>(topLevelEditor.getContentComponent()) { + myBalloon.show(new PositionTracker<Balloon>(myEditor.getContentComponent()) { @Override public RelativePoint recalculateLocation(Balloon object) { - if (myTarget != null && !popupFactory.isBestPopupLocationVisible(topLevelEditor)) { + if (myTarget != null && !popupFactory.isBestPopupLocationVisible(myEditor)) { return myTarget; } if (myCaretRangeMarker != null && myCaretRangeMarker.isValid()) { - topLevelEditor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION, - topLevelEditor.offsetToVisualPosition(myCaretRangeMarker.getStartOffset())); + myEditor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION, + myEditor.offsetToVisualPosition(myCaretRangeMarker.getStartOffset())); } - final RelativePoint target = popupFactory.guessBestPopupLocation(topLevelEditor); + final RelativePoint target = popupFactory.guessBestPopupLocation(myEditor); final Point screenPoint = target.getScreenPoint(); int y = screenPoint.y; - if (target.getPoint().getY() > topLevelEditor.getLineHeight() + myBalloon.getPreferredSize().getHeight()) { - y -= topLevelEditor.getLineHeight(); + if (target.getPoint().getY() > myEditor.getLineHeight() + myBalloon.getPreferredSize().getHeight()) { + y -= myEditor.getLineHeight(); } myTarget = new RelativePoint(new Point(screenPoint.x, y)); return myTarget; diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenamer.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenamer.java index bd3c52aac910..5e5907c83a07 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenamer.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/MemberInplaceRenamer.java @@ -177,6 +177,11 @@ public class MemberInplaceRenamer extends VariableInplaceRenamer { return false; } + @Override + protected String getRefactoringId() { + return null; + } + private void appendAdditionalElement(Collection<Pair<PsiElement, TextRange>> stringUsages, PsiNamedElement variable, PsiElement element) { diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenamer.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenamer.java index 376561535a1e..6fa0ed27d8c8 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenamer.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/VariableInplaceRenamer.java @@ -38,6 +38,8 @@ import com.intellij.psi.util.PsiUtilCore; import com.intellij.refactoring.RefactoringActionHandler; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.listeners.RefactoringElementListener; +import com.intellij.refactoring.listeners.RefactoringEventData; +import com.intellij.refactoring.listeners.RefactoringEventListener; import com.intellij.refactoring.rename.*; import com.intellij.refactoring.rename.naming.AutomaticRenamer; import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory; @@ -137,6 +139,10 @@ public class VariableInplaceRenamer extends InplaceRefactoring { protected boolean shouldCreateSnapshot() { return true; } + + protected String getRefactoringId() { + return "refactoring.rename"; + } @Override protected void beforeTemplateStart() { @@ -204,11 +210,18 @@ public class VariableInplaceRenamer extends InplaceRefactoring { protected void performRefactoringRename(final String newName, final StartMarkAction markAction) { + final String refactoringId = getRefactoringId(); try { + PsiNamedElement elementToRename = getVariable(); + if (refactoringId != null) { + final RefactoringEventData beforeData = new RefactoringEventData(); + beforeData.addElement(elementToRename); + myProject.getMessageBus() + .syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringStarted(refactoringId, beforeData); + } if (!isIdentifier(newName, myLanguage)) { return; } - PsiNamedElement elementToRename = getVariable(); if (elementToRename != null) { new WriteCommandAction(myProject, getCommandName()) { @Override @@ -278,6 +291,14 @@ public class VariableInplaceRenamer extends InplaceRefactoring { } } finally { + + if (refactoringId != null) { + final RefactoringEventData afterData = new RefactoringEventData(); + afterData.addElement(getVariable()); + myProject.getMessageBus() + .syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringDone(refactoringId, afterData); + } + try { ((EditorImpl)InjectedLanguageUtil.getTopLevelEditor(myEditor)).stopDumbLater(); } diff --git a/platform/lang-impl/src/com/intellij/unscramble/AnalyzeStacktraceOnErrorAction.java b/platform/lang-impl/src/com/intellij/unscramble/AnalyzeStacktraceOnErrorAction.java index e1de663c9418..d3385e88b4b1 100644 --- a/platform/lang-impl/src/com/intellij/unscramble/AnalyzeStacktraceOnErrorAction.java +++ b/platform/lang-impl/src/com/intellij/unscramble/AnalyzeStacktraceOnErrorAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -19,26 +19,28 @@ package com.intellij.unscramble; import com.intellij.diagnostic.IdeErrorsDialog; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.project.Project; /** * @author spleaner */ public class AnalyzeStacktraceOnErrorAction extends AnAction { + @Override + public void update(AnActionEvent e) { + Project project = e.getProject(); + String message = getMessage(e); + e.getPresentation().setEnabledAndVisible(project != null && message != null); + } @Override public void actionPerformed(AnActionEvent e) { - final DataContext dataContext = e.getDataContext(); - - final Project project = CommonDataKeys.PROJECT.getData(dataContext); - if (project == null) return; + Project project = e.getProject(); + String message = getMessage(e); + if (project == null || message == null) return; + AnalyzeStacktraceUtil.addConsole(project, null, "<Stacktrace>", message); + } - final String message = IdeErrorsDialog.CURRENT_TRACE_KEY.getData(dataContext); - if (message != null) { - AnalyzeStacktraceUtil.addConsole(project, null, "<Stacktrace>", message); - } + private static String getMessage(AnActionEvent e) { + return IdeErrorsDialog.CURRENT_TRACE_KEY.getData(e.getDataContext()); } }
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/util/indexing/ContentHashesSupport.java b/platform/lang-impl/src/com/intellij/util/indexing/ContentHashesSupport.java index d20d01dc4e03..34f80dae5cc9 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/ContentHashesSupport.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/ContentHashesSupport.java @@ -21,6 +21,7 @@ import com.intellij.openapi.vfs.newvfs.persistent.ContentHashesUtil; import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon; import com.intellij.util.io.IOUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -63,19 +64,19 @@ class ContentHashesSupport { } static void flushContentHashes() { - if (ourHashesWithFileType.isDirty()) ourHashesWithFileType.force(); + if (ourHashesWithFileType != null && ourHashesWithFileType.isDirty()) ourHashesWithFileType.force(); } - static int calcContentHashIdWithFileType(@NotNull byte[] bytes, @NotNull FileType fileType) throws IOException { - return enumerateHash(calcContentHashWithFileType(bytes, fileType)); + static int calcContentHashIdWithFileType(@NotNull byte[] bytes, @Nullable Charset charset, @NotNull FileType fileType) throws IOException { + return enumerateHash(calcContentHashWithFileType(bytes, charset, fileType)); } static int enumerateHash(@NotNull byte[] digest) throws IOException { return ourHashesWithFileType.enumerate(digest); } - static byte[] calcContentHashWithFileType(@NotNull byte[] bytes, @NotNull FileType fileType) throws IOException { + static byte[] calcContentHashWithFileType(@NotNull byte[] bytes, @Nullable Charset charset, @NotNull FileType fileType) throws IOException { MessageDigest messageDigest = ContentHashesUtil.HASHER_CACHE.getValue(); Charset defaultCharset = Charset.defaultCharset(); @@ -83,6 +84,8 @@ class ContentHashesSupport { messageDigest.update((byte)0); messageDigest.update(String.valueOf(bytes.length).getBytes(defaultCharset)); messageDigest.update((byte)0); + messageDigest.update((charset != null ? charset.displayName():"null_charset").getBytes(defaultCharset)); + messageDigest.update((byte)0); messageDigest.update(bytes, 0, bytes.length); return messageDigest.digest(); diff --git a/platform/lang-impl/src/com/intellij/util/indexing/DebugAssertions.java b/platform/lang-impl/src/com/intellij/util/indexing/DebugAssertions.java index 0b7650aace4f..6c866ad2283f 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/DebugAssertions.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/DebugAssertions.java @@ -19,6 +19,8 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.SystemProperties; +import java.util.Formatter; + public class DebugAssertions { private static final Logger LOG = Logger.getInstance(DebugAssertions.class); @@ -29,7 +31,7 @@ public class DebugAssertions { public static final boolean EXTRA_SANITY_CHECKS = SystemProperties.getBooleanProperty( "intellij.idea.indices.debug.extra.sanity", - DEBUG && ApplicationManager.getApplication().isInternal() + true ); public static void assertTrue(boolean value) { @@ -37,4 +39,14 @@ public class DebugAssertions { LOG.assertTrue(false); } } + + public static void assertTrue(boolean value, String message, Object ... args) { + if (!value) { + error(message, args); + } + } + + public static void error(String message, Object ... args) { + LOG.error(new Formatter().format(message, args)); + } } diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java index c81f7c485876..5242ee3b70cf 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java @@ -380,9 +380,9 @@ public class FileBasedIndexImpl extends FileBasedIndex { LOG.info("Version has changed for index " + name + ". The index will be rebuilt."); } if (extension.hasSnapshotMapping() && (isCurrentVersionCorrupted || versionChanged)) { - safeDelete(IndexInfrastructure.getPersistentIndexRootDir(name)); + FileUtil.delete(IndexInfrastructure.getPersistentIndexRootDir(name)); } - safeDelete(IndexInfrastructure.getIndexRootDir(name)); + FileUtil.delete(IndexInfrastructure.getIndexRootDir(name)); IndexingStamp.rewriteVersion(versionFile, version); } @@ -458,22 +458,16 @@ public class FileBasedIndexImpl extends FileBasedIndex { catch (Exception ignored) { } - safeDelete(IndexInfrastructure.getIndexRootDir(name)); + FileUtil.delete(IndexInfrastructure.getIndexRootDir(name)); if (extension.hasSnapshotMapping() && (!contentHashesEnumeratorOk || instantiatedStorage)) { - safeDelete(IndexInfrastructure.getPersistentIndexRootDir(name)); // todo there is possibility of corruption of storage and content hashes + FileUtil.delete(IndexInfrastructure.getPersistentIndexRootDir(name)); } IndexingStamp.rewriteVersion(versionFile, version); } } } - private static boolean safeDelete(File dir) { - File directory = FileUtil.findSequentNonexistentFile(dir.getParentFile(), dir.getName(), ""); - boolean success = dir.renameTo(directory); - return FileUtil.delete(success ? directory:dir); - } - private static void saveRegisteredIndices(@NotNull Collection<ID<?, ?>> ids) { final File file = getRegisteredIndicesFile(); try { @@ -1206,6 +1200,7 @@ public class FileBasedIndexImpl extends FileBasedIndex { @Nullable public static Throwable getCauseToRebuildIndex(@NotNull RuntimeException e) { + if (e instanceof IndexOutOfBoundsException) return e; // something wrong with direct byte buffer Throwable cause = e.getCause(); if (cause instanceof StorageException || cause instanceof IOException || cause instanceof IllegalArgumentException) return cause; @@ -1568,7 +1563,7 @@ public class FileBasedIndexImpl extends FileBasedIndex { indicesToDrop.remove(key.toString()); } for (String s : indicesToDrop) { - safeDelete(IndexInfrastructure.getIndexRootDir(ID.create(s))); + FileUtil.delete(IndexInfrastructure.getIndexRootDir(ID.create(s))); } } @@ -1664,21 +1659,26 @@ public class FileBasedIndexImpl extends FileBasedIndex { } byte[] currentBytes; - byte[] hash; try { currentBytes = content.getBytes(); - if (fileType.isBinary() || !IdIndex.ourSnapshotMappingsEnabled) { - hash = null; - } else { - hash = ContentHashesSupport - .calcContentHashWithFileType(currentBytes, SubstitutedFileType.substituteFileType(file, fileType, project)); - } } catch (IOException e) { currentBytes = ArrayUtil.EMPTY_BYTE_ARRAY; - hash = null; } - fc = new FileContentImpl(file, currentBytes, hash); + fc = new FileContentImpl(file, currentBytes); + + if (!fileType.isBinary() && IdIndex.ourSnapshotMappingsEnabled) { + try { + byte[] hash = ContentHashesSupport.calcContentHashWithFileType( + currentBytes, + fc.getCharset(), + SubstitutedFileType.substituteFileType(file, fileType, project) + ); + fc.setHash(hash); + } catch (IOException e) { + LOG.error(e); + } + } psiFile = content.getUserData(IndexingDataKeys.PSI_FILE); initFileContent(fc, project, psiFile); @@ -1909,21 +1909,26 @@ public class FileBasedIndexImpl extends FileBasedIndex { @Override public void beforePropertyChange(@NotNull final VirtualFilePropertyEvent event) { - if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { - // indexes may depend on file name - final VirtualFile file = event.getFile(); + String propertyName = event.getPropertyName(); + if (propertyName.equals(VirtualFile.PROP_NAME)) { + // indexes may depend on file name // name change may lead to filetype change so the file might become not indexable // in general case have to 'unindex' the file and index it again if needed after the name has been changed - invalidateIndices(file, false); + invalidateIndices(event.getFile(), false); + } else if (propertyName.equals(VirtualFile.PROP_ENCODING)) { + invalidateIndices(event.getFile(), true); } } @Override public void propertyChanged(@NotNull final VirtualFilePropertyEvent event) { - if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { + String propertyName = event.getPropertyName(); + if (propertyName.equals(VirtualFile.PROP_NAME)) { // indexes may depend on file name markDirty(event, false); + } else if (propertyName.equals(VirtualFile.PROP_ENCODING)) { + markDirty(event, true); } } diff --git a/platform/lang-impl/src/com/intellij/util/indexing/IndexingStamp.java b/platform/lang-impl/src/com/intellij/util/indexing/IndexingStamp.java index 7bea700016c8..6bdd55b1d343 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/IndexingStamp.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/IndexingStamp.java @@ -59,7 +59,7 @@ public class IndexingStamp { private static final long UNINDEXED_STAMP = -1L; // we don't store trivial "absent" state private static final long INDEX_DATA_OUTDATED_STAMP = -2L; - private static final int VERSION = 11; + private static final int VERSION = 12; private static final ConcurrentHashMap<ID<?, ?>, Long> ourIndexIdToCreationStamp = new ConcurrentHashMap<ID<?, ?>, Long>(); private static volatile long ourLastStamp; // ensure any file index stamp increases diff --git a/platform/lang-impl/src/com/intellij/util/indexing/MapReduceIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/MapReduceIndex.java index 3ad505baf207..ebb7911e82be 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/MapReduceIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/MapReduceIndex.java @@ -22,18 +22,16 @@ import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; import com.intellij.openapi.util.io.ByteSequence; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.util.Processor; -import com.intellij.util.SmartList; -import com.intellij.util.SystemProperties; +import com.intellij.util.*; import com.intellij.util.io.*; +import com.intellij.util.io.DataOutputStream; import gnu.trove.THashMap; import gnu.trove.TObjectObjectProcedure; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.DataInputStream; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.nio.charset.Charset; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -56,6 +54,7 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val private PersistentHashMap<Integer, Collection<Key>> myInputsIndex; private PersistentHashMap<Integer, ByteSequence> myContents; private PersistentHashMap<Integer, Integer> myInputsSnapshotMapping; + private PersistentHashMap<Integer, String> myIndexingTrace; private final ReentrantReadWriteLock myLock = new ReentrantReadWriteLock(); @@ -127,6 +126,10 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val cleanMapping(myInputsSnapshotMapping); myInputsSnapshotMapping = createInputSnapshotMapping(); } + if (myIndexingTrace != null) { + cleanMapping(myIndexingTrace); + myIndexingTrace = createIndexingTrace(); + } if (myContents != null) { cleanMapping(myContents); myContents = createContentsIndex(); @@ -161,6 +164,31 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val } } + private PersistentHashMap<Integer, String> createIndexingTrace() throws IOException { + assert myIndexId != null; + final File mapFile = new File(IndexInfrastructure.getIndexRootDir(myIndexId), "indextrace"); + try { + return new PersistentHashMap<Integer, String>(mapFile, EnumeratorIntegerDescriptor.INSTANCE, + new DataExternalizer<String>() { + @Override + public void save(@NotNull DataOutput out, String value) throws IOException { + out.write((byte[])CompressionUtil.compressCharSequence(value, Charset.defaultCharset())); + } + + @Override + public String read(@NotNull DataInput in) throws IOException { + byte[] b = new byte[((InputStream)in).available()]; + in.readFully(b); + return (String)CompressionUtil.uncompressCharSequence(b, Charset.defaultCharset()); + } + }, 4096); + } + catch (IOException ex) { + IOUtil.deleteAllFilesStartingWith(mapFile); + throw ex; + } + } + private static void cleanMapping(@NotNull PersistentHashMap<?, ?> index) { final File baseFile = index.getBaseFile(); try { @@ -178,6 +206,7 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val getReadLock().lock(); doForce(myInputsIndex); doForce(myInputsSnapshotMapping); + doForce(myIndexingTrace); doForce(myContents); myStorage.flush(); } @@ -216,6 +245,7 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val finally { doClose(myInputsIndex); doClose(myInputsSnapshotMapping); + doClose(myIndexingTrace); doClose(myContents); } } @@ -283,6 +313,9 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val myInputsSnapshotMapping = createInputSnapshotMapping(); } myInputsIndex = createInputsIndex(); + if (DebugAssertions.EXTRA_SANITY_CHECKS && myIndexId != null) { + myIndexingTrace = createIndexingTrace(); + } } @Nullable @@ -318,7 +351,8 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val if (myContents != null && weProcessPhysicalContent && content != null) { try { - hashId = getHashOfContent((FileContent)content); + FileContent fileContent = (FileContent)content; + hashId = getHashOfContent(fileContent); if (doReadSavedPersistentData) { if (!myContents.isBusyReading()) { // avoid blocking read, we can calculate index value ByteSequence bytes = myContents.get(hashId); @@ -326,7 +360,19 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val data = deserializeSavedPersistentData(bytes); havePersistentData = true; if (DebugAssertions.EXTRA_SANITY_CHECKS) { - DebugAssertions.assertTrue(myIndexer.map(content).equals(data)); + Map<Key, Value> contentData = myIndexer.map(content); + boolean sameValueForSavedIndexedResultAndCurrentOne = contentData.equals(data); + if (!sameValueForSavedIndexedResultAndCurrentOne) { + DebugAssertions.error( + "Unexpected difference in indexing of %s by index %s, file type %s, charset %s\ndiff %s\nprevious indexed info %s", + fileContent.getFile(), + myIndexId, + fileContent.getFileType(), + ((FileContentImpl)fileContent).getCharset(), + buildDiff(data, contentData), + myIndexingTrace.get(hashId) + ); + } } } } else { @@ -344,16 +390,27 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val if (data == null) data = content != null ? myIndexer.map(content) : Collections.<Key, Value>emptyMap(); if (hashId != null && !havePersistentData) { - savePersistentData(data, hashId, skippedReadingPersistentDataButMayHaveIt); + boolean saved = savePersistentData(data, hashId, skippedReadingPersistentDataButMayHaveIt); + if (DebugAssertions.EXTRA_SANITY_CHECKS) { + if (saved) { + + FileContent fileContent = (FileContent)content; + try { + myIndexingTrace.put(hashId, ((FileContentImpl)fileContent).getCharset() + "," + fileContent.getFileType()+"," + fileContent.getFile().getPath() + "," + + ExceptionUtil.getThrowableText(new Throwable())); + } catch (IOException ex) { + LOG.error(ex); + } + } + } } ProgressManager.checkCanceled(); final NotNullComputable<Collection<Key>> oldKeysGetter; final int savedInputId; - if (myHasSnapshotMapping && weProcessPhysicalContent) { + if (myHasSnapshotMapping) { try { - - oldKeysGetter = new NotNullComputable<Collection<Key>>() { + final NotNullComputable<Collection<Key>> keysForGivenInputId = new NotNullComputable<Collection<Key>>() { @NotNull @Override public Collection<Key> compute() { @@ -369,15 +426,38 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val } return currentKeys; - } catch (IOException e) { + } + catch (IOException e) { throw new RuntimeException(e); } } }; - if (content instanceof FileContent) { - savedInputId = getHashOfContent((FileContent)content); + if (weProcessPhysicalContent) { + if (content instanceof FileContent) { + savedInputId = getHashOfContent((FileContent)content); + } + else { + savedInputId = NULL_MAPPING; + } + oldKeysGetter = keysForGivenInputId; } else { + oldKeysGetter = new NotNullComputable<Collection<Key>>() { + @NotNull + @Override + public Collection<Key> compute() { + try { + Collection<Key> oldKeys = myInputsIndex.get(inputId); + if (oldKeys == null) { + return keysForGivenInputId.compute(); + } + return oldKeys; + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + }; savedInputId = NULL_MAPPING; } } catch (IOException ex) { @@ -429,6 +509,41 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val }; } + private StringBuilder buildDiff(Map<Key, Value> data, Map<Key, Value> contentData) { + StringBuilder moreInfo = new StringBuilder(); + if (contentData.size() != data.size()) { + moreInfo.append("Indexer has different number of elements, previously ").append(data.size()).append(" after ") + .append(contentData.size()).append("\n"); + } else { + moreInfo.append("total ").append(contentData.size()).append(" entries\n"); + } + + for(Map.Entry<Key, Value> keyValueEntry:contentData.entrySet()) { + if (!data.containsKey(keyValueEntry.getKey())) { + moreInfo.append("Previous data doesn't contain:").append(keyValueEntry.getKey()).append( " with value ").append(keyValueEntry.getValue()).append("\n"); + } + else { + Value value = data.get(keyValueEntry.getKey()); + if (!Comparing.equal(keyValueEntry.getValue(), value)) { + moreInfo.append("Previous data has different value for key:").append(keyValueEntry.getKey()).append( ", new value ").append(keyValueEntry.getValue()).append( ", oldValue:").append(value).append("\n"); + } + } + } + + for(Map.Entry<Key, Value> keyValueEntry:data.entrySet()) { + if (!contentData.containsKey(keyValueEntry.getKey())) { + moreInfo.append("New data doesn't contain:").append(keyValueEntry.getKey()).append( " with value ").append(keyValueEntry.getValue()).append("\n"); + } + else { + Value value = contentData.get(keyValueEntry.getKey()); + if (!Comparing.equal(keyValueEntry.getValue(), value)) { + moreInfo.append("New data has different value for key:").append(keyValueEntry.getKey()).append( " new value ").append(value).append( ", oldValue:").append(keyValueEntry.getValue()).append("\n"); + } + } + } + return moreInfo; + } + private Map<Key, Value> deserializeSavedPersistentData(ByteSequence bytes) throws IOException { DataInputStream stream = new DataInputStream(new UnsyncByteArrayInputStream(bytes.getBytes(), bytes.getOffset(), bytes.getLength())); int pairs = DataInputOutputUtil.readINT(stream); @@ -447,8 +562,9 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val if (previouslyCalculatedContentHashId == null) { byte[] hash = content instanceof FileContentImpl ? ((FileContentImpl)content).getHash():null; if (hash == null) { + Charset charset = content instanceof FileContentImpl ? ((FileContentImpl)content).getCharset() : null; previouslyCalculatedContentHashId = ContentHashesSupport - .calcContentHashIdWithFileType(content.getContent(), content.getFileType()); + .calcContentHashIdWithFileType(content.getContent(), charset, content.getFileType()); } else { previouslyCalculatedContentHashId = ContentHashesSupport.enumerateHash(hash); } @@ -459,9 +575,9 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val private static final ThreadLocalCachedByteArray ourSpareByteArray = new ThreadLocalCachedByteArray(); - private void savePersistentData(Map<Key, Value> data, int id, boolean delayedReading) { + private boolean savePersistentData(Map<Key, Value> data, int id, boolean delayedReading) { try { - if (delayedReading && myContents.containsMapping(id)) return; + if (delayedReading && myContents.containsMapping(id)) return false; BufferExposingByteArrayOutputStream out = new BufferExposingByteArrayOutputStream(ourSpareByteArray.getBuffer(4 * data.size())); DataOutputStream stream = new DataOutputStream(out); int size = data.size(); @@ -496,6 +612,7 @@ public class MapReduceIndex<Key, Value, Input> implements UpdatableIndex<Key,Val } catch (IOException ex) { throw new RuntimeException(ex); } + return true; } private static final com.intellij.openapi.util.Key<Integer> ourSavedContentHashIdKey = com.intellij.openapi.util.Key.create("saved.content.hash.id"); diff --git a/platform/lang-impl/src/com/intellij/util/indexing/containers/ChangeBufferingList.java b/platform/lang-impl/src/com/intellij/util/indexing/containers/ChangeBufferingList.java index 77c5b50581ea..103f7d5fb587 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/containers/ChangeBufferingList.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/containers/ChangeBufferingList.java @@ -255,8 +255,9 @@ public class ChangeBufferingList implements Cloneable { if (intContainer == null && removals == 0) { ValueContainer.IntIterator iterator = new ChangesIterator(changes, length); if (DEBUG) { - iterator = SortedFileIdSetIterator.getTransientIterator(iterator); - DebugAssertions.assertTrue(iterator.size() == length); + ValueContainer.IntIterator iteratorSurelyWithoutDupes = SortedFileIdSetIterator.getTransientIterator(iterator); + DebugAssertions.assertTrue(iteratorSurelyWithoutDupes.size() == length); + iterator = iterator.createCopyInInitialState(); } return iterator; } |