diff options
Diffstat (limited to 'platform/lang-impl/src/com/intellij/codeInsight')
27 files changed, 756 insertions, 215 deletions
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java index bf77a3022a0e..af6bbdc931ca 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java @@ -289,7 +289,16 @@ public class FormatChangedTextUtil { } try { - List<Range> changedRanges = new RangesBuilder(document, documentFromVcs).getRanges(); + List<Range> changedRanges; + + LineStatusTracker tracker = LineStatusTrackerManager.getInstance(project).getLineStatusTracker(document); + if (tracker != null) { + changedRanges = tracker.getRanges(); + } + else { + changedRanges = new RangesBuilder(document, documentFromVcs).getRanges(); + } + return getChangedTextRanges(document, changedRanges); } catch (FilesTooBigForDiffException e) { 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 4966a9466a26..8ec833e8258b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java @@ -79,9 +79,6 @@ public class ReformatCodeAction extends AnAction implements DumbAware { PsiDocumentManager.getInstance(project).commitAllDocuments(); final Editor editor = CommonDataKeys.EDITOR.getData(dataContext); final VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext); - if (files == null) { - return; - } PsiFile file = null; final PsiDirectory dir; 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 e0a3be5f423d..fcb080dcd277 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java @@ -71,6 +71,8 @@ import java.util.concurrent.atomic.AtomicReference; public class CodeCompletionHandlerBase { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CodeCompletionHandlerBase"); + private static final Key<Boolean> CARET_PROCESSED = Key.create("CodeCompletionHandlerBase.caretProcessed"); + @NotNull private final CompletionType myCompletionType; final boolean invokedExplicitly; final boolean synchronous; @@ -108,6 +110,13 @@ public class CodeCompletionHandlerBase { } public final void invokeCompletion(@NotNull final Project project, @NotNull final Editor editor, int time, boolean hasModifiers, boolean restarted) { + clearCaretMarkers(editor); + invokeCompletion(project, editor, time, hasModifiers, restarted, editor.getCaretModel().getPrimaryCaret()); + } + + public final void invokeCompletion(@NotNull final Project project, @NotNull final Editor editor, int time, boolean hasModifiers, boolean restarted, @NotNull final Caret caret) { + markCaretAsProcessed(caret); + if (invokedExplicitly) { CompletionLookupArranger.applyLastCompletionStatisticsUpdate(); } @@ -162,12 +171,12 @@ public class CodeCompletionHandlerBase { PsiDocumentManager.getInstance(project).commitAllDocuments(); CompletionAssertions.checkEditorValid(editor); - final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project); + final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(caret, project); assert psiFile != null : "no PSI file: " + FileDocumentManager.getInstance().getFile(editor.getDocument()); psiFile.putUserData(PsiFileEx.BATCH_REFERENCE_PROCESSING, Boolean.TRUE); CompletionAssertions.assertCommitSuccessful(editor, psiFile); - initializationContext[0] = runContributorsBeforeCompletion(editor, psiFile, invocationCount); + initializationContext[0] = runContributorsBeforeCompletion(editor, psiFile, invocationCount, caret); } }; ApplicationManager.getApplication().runWriteAction(runnable); @@ -186,9 +195,9 @@ public class CodeCompletionHandlerBase { insertDummyIdentifier(initializationContext[0], hasModifiers, invocationCount); } - private CompletionInitializationContext runContributorsBeforeCompletion(Editor editor, PsiFile psiFile, int invocationCount) { + private CompletionInitializationContext runContributorsBeforeCompletion(Editor editor, PsiFile psiFile, int invocationCount, Caret caret) { final Ref<CompletionContributor> current = Ref.create(null); - CompletionInitializationContext context = new CompletionInitializationContext(editor, psiFile, myCompletionType, invocationCount) { + CompletionInitializationContext context = new CompletionInitializationContext(editor, caret, psiFile, myCompletionType, invocationCount) { CompletionContributor dummyIdentifierChanger; @Override @@ -400,8 +409,15 @@ public class CodeCompletionHandlerBase { final LookupElement[] items, boolean hasModifiers) { if (items.length == 0) { LookupManager.getInstance(indicator.getProject()).hideActiveLookup(); - indicator.handleEmptyLookup(true); - checkNotSync(indicator, items); + + Caret nextCaret = getNextCaretToProcess(indicator.getEditor()); + if (nextCaret != null) { + invokeCompletion(indicator.getProject(), indicator.getEditor(), indicator.getParameters().getInvocationCount(), hasModifiers, false, nextCaret); + } + else { + indicator.handleEmptyLookup(true); + checkNotSync(indicator, items); + } return; } @@ -866,4 +882,23 @@ public class CodeCompletionHandlerBase { } }; } + + private static void clearCaretMarkers(@NotNull Editor editor) { + for (Caret caret : editor.getCaretModel().getAllCarets()) { + caret.putUserData(CARET_PROCESSED, null); + } + } + + private static void markCaretAsProcessed(@NotNull Caret caret) { + caret.putUserData(CARET_PROCESSED, Boolean.TRUE); + } + + private static Caret getNextCaretToProcess(@NotNull Editor editor) { + for (Caret caret : editor.getCaretModel().getAllCarets()) { + if (caret.getUserData(CARET_PROCESSED) == null) { + return caret; + } + } + return null; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java index 8560129194f1..0321191294c5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java @@ -73,7 +73,7 @@ public class InjectedGeneralHighlightingPass extends GeneralHighlightingPass imp @Override protected void collectInformationWithProgress(@NotNull final ProgressIndicator progress) { - if (!Registry.is("editor.injected.highlighting.enabled", true)) return; + if (!Registry.is("editor.injected.highlighting.enabled")) return; final Set<HighlightInfo> gotHighlights = new THashSet<HighlightInfo>(100); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/WolfHighlightingPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/WolfHighlightingPass.java index 9211bead784b..6ff242108eed 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/WolfHighlightingPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/WolfHighlightingPass.java @@ -38,7 +38,7 @@ class WolfHighlightingPass extends ProgressableTextEditorHighlightingPass implem @Override protected void collectInformationWithProgress(@NotNull final ProgressIndicator progress) { - if (!Registry.is("wolf.the.problem.solver", true)) return; + if (!Registry.is("wolf.the.problem.solver")) return; final WolfTheProblemSolver solver = WolfTheProblemSolver.getInstance(myProject); if (solver instanceof WolfTheProblemSolverImpl) { ((WolfTheProblemSolverImpl)solver).startCheckingIfVincentSolvedProblemsYet(progress, this); 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 c27ce400049e..05a1e01c6aa1 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java @@ -261,10 +261,20 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp } public void showJavaDocInfo(final Editor editor, @Nullable final PsiFile file, boolean requestFocus) { - showJavaDocInfo(editor, file, requestFocus, true); + showJavaDocInfo(editor, file, requestFocus, null); } - private void showJavaDocInfo(final Editor editor, @Nullable final PsiFile file, boolean requestFocus, final boolean autoupdate) { + public void showJavaDocInfo(final Editor editor, + @Nullable final PsiFile file, + boolean requestFocus, + final Runnable closeCallback) { + showJavaDocInfo(editor, file, requestFocus, true, closeCallback); + } + + private void showJavaDocInfo(final Editor editor, + @Nullable final PsiFile file, + boolean requestFocus, + final boolean autoupdate, @Nullable final Runnable closeCallback) { myEditor = editor; final Project project = getProject(file); PsiDocumentManager.getInstance(project).commitAllDocuments(); @@ -314,7 +324,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp return; } if (lookupIteObject instanceof PsiElement) { - doShowJavaDocInfo((PsiElement)lookupIteObject, false, this, originalElement, autoupdate); + doShowJavaDocInfo((PsiElement)lookupIteObject, false, this, originalElement, autoupdate, closeCallback); return; } @@ -337,12 +347,12 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp } } else { - doShowJavaDocInfo(element, false, this, originalElement, autoupdate); + doShowJavaDocInfo(element, false, this, originalElement, autoupdate, closeCallback); } } }; - doShowJavaDocInfo(element, requestFocus, updateProcessor, originalElement, autoupdate); + doShowJavaDocInfo(element, requestFocus, updateProcessor, originalElement, autoupdate, closeCallback); } public PsiElement findTargetElement(Editor editor, PsiFile file) { @@ -969,7 +979,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp @Override protected void doUpdateComponent(Editor editor, PsiFile psiFile) { - showJavaDocInfo(editor, psiFile, false, true); + showJavaDocInfo(editor, psiFile, false, true, null); } @Override 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 2a46c08fc2dd..b738bafb038a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java @@ -19,12 +19,12 @@ package com.intellij.codeInsight.editorActions; import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.editor.actions.CopyAction; import com.intellij.openapi.editor.actions.EditorActionUtil; import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.impl.EditorCopyPasteHelperImpl; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.project.Project; @@ -92,7 +92,7 @@ public class CopyHandler extends EditorActionHandler { } String text = editor.getCaretModel().supportsMultipleCarets() - ? CopyPasteSupport.getSelectedTextForClipboard(editor, transferableDatas) + ? EditorCopyPasteHelperImpl.getSelectedTextForClipboard(editor, transferableDatas) : selectionModel.getSelectedText(); String rawText = TextBlockTransferable.convertLineSeparators(text, "\n", transferableDatas); String escapedText = null; 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 345cb80bae69..17916e008501 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java @@ -17,15 +17,22 @@ package com.intellij.codeInsight.editorActions; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeStyle.CodeStyleFacade; +import com.intellij.formatting.*; +import com.intellij.lang.LanguageFormatting; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.CaretModel; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; @@ -36,40 +43,75 @@ import org.jetbrains.annotations.NotNull; public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { private static final Logger LOG = Logger.getInstance(IndentingBackspaceHandler.class); + private boolean isApplicable; private boolean caretWasAtLineStart; + private String precalculatedSpacing; @Override public void beforeCharDeleted(char c, PsiFile file, Editor editor) { - caretWasAtLineStart = editor.getCaretModel().getLogicalPosition().column == 0; - } - - @Override - public boolean charDeleted(char c, PsiFile file, Editor editor) { if (CodeInsightSettings.getInstance().SMART_BACKSPACE != CodeInsightSettings.AUTOINDENT || !StringUtil.isWhiteSpace(c)) { - return false; + isApplicable = false; + return; } LanguageCodeStyleSettingsProvider codeStyleSettingsProvider = LanguageCodeStyleSettingsProvider.forLanguage(file.getLanguage()); if (codeStyleSettingsProvider != null && codeStyleSettingsProvider.isIndentBasedLanguageSemantics()) { + isApplicable = false; + return; + } + Document document = editor.getDocument(); + CharSequence charSequence = document.getCharsSequence(); + CaretModel caretModel = editor.getCaretModel(); + int caretOffset = caretModel.getOffset(); + LogicalPosition pos = caretModel.getLogicalPosition(); + isApplicable = true; + caretWasAtLineStart = pos.column == 0; + precalculatedSpacing = null; + if (caretWasAtLineStart && pos.line > 0 && caretOffset < charSequence.length() && !StringUtil.isWhiteSpace(charSequence.charAt(caretOffset))) { + int prevLineEnd = document.getLineEndOffset(pos.line - 1); + if (prevLineEnd > 0 && !StringUtil.isWhiteSpace(charSequence.charAt(prevLineEnd - 1))) { + PsiDocumentManager.getInstance(file.getProject()).commitDocument(document); + precalculatedSpacing = getSpacing(file, caretOffset); + } + } + } + + @Override + public boolean charDeleted(char c, PsiFile file, Editor editor) { + if (!isApplicable) { return false; } + Project project = file.getProject(); Document document = editor.getDocument(); + CaretModel caretModel = editor.getCaretModel(); - int caretOffset = editor.getCaretModel().getOffset(); + int caretOffset = caretModel.getOffset(); int offset = CharArrayUtil.shiftForward(document.getCharsSequence(), caretOffset, " \t"); int beforeWhitespaceOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), offset - 1, " \t") + 1; - LogicalPosition logicalPosition = caretOffset < offset ? editor.offsetToLogicalPosition(offset) : editor.getCaretModel().getLogicalPosition(); + LogicalPosition logicalPosition = caretOffset < offset ? editor.offsetToLogicalPosition(offset) : caretModel.getLogicalPosition(); int lineStartOffset = document.getLineStartOffset(logicalPosition.line); if (lineStartOffset < beforeWhitespaceOffset) { - if (caretWasAtLineStart && beforeWhitespaceOffset < offset) { - document.deleteString(beforeWhitespaceOffset, offset); - return true; + if (caretWasAtLineStart && beforeWhitespaceOffset <= offset) { + String spacing; + if (precalculatedSpacing == null) { + PsiDocumentManager.getInstance(project).commitDocument(document); + spacing = getSpacing(file, offset); + } + else { + spacing = precalculatedSpacing; + } + if (beforeWhitespaceOffset < offset || !spacing.isEmpty()) { + document.replaceString(beforeWhitespaceOffset, offset, spacing); + caretModel.moveToOffset(beforeWhitespaceOffset + spacing.length()); + return true; + } } return false; } - CodeStyleFacade codeStyleFacade = CodeStyleFacade.getInstance(editor.getProject()); - String indent = codeStyleFacade.getLineIndent(document, lineStartOffset); + PsiDocumentManager.getInstance(project).commitDocument(document); + CodeStyleFacade codeStyleFacade = CodeStyleFacade.getInstance(project); + String indent = codeStyleFacade.getLineIndent(document, offset); if (indent == null) { return false; } @@ -79,7 +121,7 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { if (logicalPosition.column == targetColumn) { if (caretOffset < offset) { - editor.getCaretModel().moveToLogicalPosition(logicalPosition); + caretModel.moveToLogicalPosition(logicalPosition); return true; } return false; @@ -87,7 +129,7 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { if (caretWasAtLineStart || logicalPosition.column > targetColumn) { document.replaceString(lineStartOffset, offset, indent); - editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(logicalPosition.line, targetColumn)); + caretModel.moveToLogicalPosition(new LogicalPosition(logicalPosition.line, targetColumn)); return true; } @@ -100,12 +142,13 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { int targetOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), prevLineEndOffset - 1, " \t") + 1; if (prevLineStartOffset < targetOffset) { - document.deleteString(targetOffset, offset); - editor.getCaretModel().moveToOffset(targetOffset); + String spacing = getSpacing(file, offset); + document.replaceString(targetOffset, offset, spacing); + caretModel.moveToOffset(targetOffset + spacing.length()); } else { document.replaceString(prevLineStartOffset, offset, indent); - editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(logicalPosition.line - 1, targetColumn)); + caretModel.moveToLogicalPosition(new LogicalPosition(logicalPosition.line - 1, targetColumn)); } return true; } @@ -132,4 +175,15 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { } return width; } + + private static String getSpacing(PsiFile file, int offset) { + FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(file); + if (builder == null) { + return ""; + } + CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(file.getProject()); + FormattingModel model = builder.createModel(file, settings); + int spacing = FormatterEx.getInstance().getSpacingForBlockAtOffset(model, offset); + return StringUtil.repeatSymbol(' ', spacing); + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java index bb21eac69ecd..ba4768f73099 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java @@ -148,14 +148,17 @@ public class TypedHandler extends TypedActionHandlerBase { return; } + final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project); + final Document originalDocument = originalEditor.getDocument(); originalEditor.getCaretModel().runForEachCaret(new CaretAction() { @Override public void perform(Caret caret) { - PsiDocumentManager.getInstance(project) - .doPostponedOperationsAndUnblockDocument(originalEditor.getDocument()); // to clean up after previous caret processing + if (psiDocumentManager.isDocumentBlockedByPsi(originalDocument)) { + psiDocumentManager.doPostponedOperationsAndUnblockDocument(originalDocument); // to clean up after previous caret processing + } Editor editor = injectedEditorIfCharTypedIsSignificant(charTyped, originalEditor, originalFile); - PsiFile file = editor == originalEditor ? originalFile : PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); + PsiFile file = editor == originalEditor ? originalFile : psiDocumentManager.getPsiFile(editor.getDocument()); final TypedHandlerDelegate[] delegates = Extensions.getExtensions(TypedHandlerDelegate.EP_NAME); 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 3a1f04b80414..9a71909fcca8 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 @@ -372,31 +372,34 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, checkValid(); CollectionListModel<LookupElement> listModel = getListModel(); - synchronized (myList) { - Pair<List<LookupElement>, Integer> pair = myPresentableArranger.arrangeItems(this, onExplicitAction || reused); - List<LookupElement> items = pair.first; - Integer toSelect = pair.second; - if (toSelect == null || toSelect < 0 || items.size() > 0 && toSelect >= items.size()) { - LOG.error("Arranger " + myPresentableArranger + " returned invalid selection index=" + toSelect + "; items=" + items); - } - - myOffsets.checkMinPrefixLengthChanges(items, this); - List<LookupElement> oldModel = listModel.toList(); - listModel.removeAll(); - if (!items.isEmpty()) { - listModel.add(items); - } - else { - addEmptyItem(listModel); - } + Pair<List<LookupElement>, Integer> pair; + synchronized (myList) { + pair = myPresentableArranger.arrangeItems(this, onExplicitAction || reused); + } + + List<LookupElement> items = pair.first; + Integer toSelect = pair.second; + if (toSelect == null || toSelect < 0 || items.size() > 0 && toSelect >= items.size()) { + LOG.error("Arranger " + myPresentableArranger + " returned invalid selection index=" + toSelect + "; items=" + items); + toSelect = 0; + } - updateListHeight(listModel); + myOffsets.checkMinPrefixLengthChanges(items, this); + List<LookupElement> oldModel = listModel.toList(); - myList.setSelectedIndex(toSelect); - return !ContainerUtil.equalsIdentity(oldModel, items); + listModel.removeAll(); + if (!items.isEmpty()) { + listModel.add(items); } + else { + addEmptyItem(listModel); + } + + updateListHeight(listModel); + myList.setSelectedIndex(toSelect); + return !ContainerUtil.equalsIdentity(oldModel, items); } private boolean isSelectionVisible() { @@ -566,7 +569,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, public void perform(Caret caret) { EditorModificationUtil.deleteSelectedText(hostEditor); final int caretOffset = hostEditor.getCaretModel().getOffset(); - int lookupStart = caretOffset - prefix; + int lookupStart = Math.max(caretOffset - prefix, 0); int len = hostEditor.getDocument().getTextLength(); LOG.assertTrue(lookupStart >= 0 && lookupStart <= len, diff --git a/platform/lang-impl/src/com/intellij/codeInsight/navigation/NavigationUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/navigation/NavigationUtil.java index 3c0e84a11656..3617c944e888 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/navigation/NavigationUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/navigation/NavigationUtil.java @@ -19,30 +19,46 @@ package com.intellij.codeInsight.navigation; import com.intellij.ide.util.DefaultPsiElementCellRenderer; import com.intellij.ide.util.EditSourceUtil; import com.intellij.ide.util.PsiElementListCellRenderer; +import com.intellij.navigation.GotoRelatedItem; +import com.intellij.navigation.GotoRelatedProvider; import com.intellij.navigation.NavigationItem; +import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.markup.HighlighterTargetArea; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.fileEditor.impl.EditorHistoryManager; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.PopupChooserBuilder; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.Navigatable; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.ElementBase; import com.intellij.psi.search.PsiElementProcessor; -import com.intellij.ui.JBListWithHintProvider; +import com.intellij.ui.*; +import com.intellij.ui.popup.list.ListPopupImpl; +import com.intellij.ui.popup.list.PopupListElementRenderer; +import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.*; +import java.util.List; /** * @author ven @@ -220,4 +236,242 @@ public final class NavigationUtil { } return attributes; } + + @NotNull + public static JBPopup getRelatedItemsPopup(final List<? extends GotoRelatedItem> items, String title) { + Object[] elements = new Object[items.size()]; + //todo[nik] move presentation logic to GotoRelatedItem class + final Map<PsiElement, GotoRelatedItem> itemsMap = new HashMap<PsiElement, GotoRelatedItem>(); + for (int i = 0; i < items.size(); i++) { + GotoRelatedItem item = items.get(i); + elements[i] = item.getElement() != null ? item.getElement() : item; + itemsMap.put(item.getElement(), item); + } + + return getPsiElementPopup(elements, itemsMap, title, new Processor<Object>() { + @Override + public boolean process(Object element) { + if (element instanceof PsiElement) { + //noinspection SuspiciousMethodCalls + itemsMap.get(element).navigate(); + } + else { + ((GotoRelatedItem)element).navigate(); + } + return true; + } + } + ); + } + + private static JBPopup getPsiElementPopup(final Object[] elements, final Map<PsiElement, GotoRelatedItem> itemsMap, + final String title, final Processor<Object> processor) { + + final Ref<Boolean> hasMnemonic = Ref.create(false); + final DefaultPsiElementCellRenderer renderer = new DefaultPsiElementCellRenderer() { + { + setFocusBorderEnabled(false); + } + + @Override + public String getElementText(PsiElement element) { + String customName = itemsMap.get(element).getCustomName(); + return (customName != null ? customName : super.getElementText(element)); + } + + @Override + protected Icon getIcon(PsiElement element) { + Icon customIcon = itemsMap.get(element).getCustomIcon(); + return customIcon != null ? customIcon : super.getIcon(element); + } + + @Override + public String getContainerText(PsiElement element, String name) { + String customContainerName = itemsMap.get(element).getCustomContainerName(); + + if (customContainerName != null) { + return customContainerName; + } + PsiFile file = element.getContainingFile(); + return file != null && !getElementText(element).equals(file.getName()) + ? "(" + file.getName() + ")" + : null; + } + + @Override + protected DefaultListCellRenderer getRightCellRenderer(Object value) { + return null; + } + + @Override + protected boolean customizeNonPsiElementLeftRenderer(ColoredListCellRenderer renderer, + JList list, + Object value, + int index, + boolean selected, + boolean hasFocus) { + final GotoRelatedItem item = (GotoRelatedItem)value; + Color color = list.getForeground(); + final SimpleTextAttributes nameAttributes = new SimpleTextAttributes(Font.PLAIN, color); + final String name = item.getCustomName(); + if (name == null) return false; + renderer.append(name, nameAttributes); + renderer.setIcon(item.getCustomIcon()); + return true; + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + final JPanel component = (JPanel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (!hasMnemonic.get()) return component; + + final JPanel panelWithMnemonic = new JPanel(new BorderLayout()); + final int mnemonic = getMnemonic(value, itemsMap); + final JLabel label = new JLabel(""); + if (mnemonic != -1) { + label.setText(mnemonic + "."); + label.setDisplayedMnemonicIndex(0); + } + label.setPreferredSize(new JLabel("8.").getPreferredSize()); + + final JComponent leftRenderer = (JComponent)component.getComponents()[0]; + component.remove(leftRenderer); + panelWithMnemonic.setBorder(BorderFactory.createEmptyBorder(0, 7, 0, 0)); + panelWithMnemonic.setBackground(leftRenderer.getBackground()); + label.setBackground(leftRenderer.getBackground()); + panelWithMnemonic.add(label, BorderLayout.WEST); + panelWithMnemonic.add(leftRenderer, BorderLayout.CENTER); + component.add(panelWithMnemonic); + return component; + } + }; + final ListPopupImpl popup = new ListPopupImpl(new BaseListPopupStep<Object>(title, Arrays.asList(elements)) { + @Override + public boolean isSpeedSearchEnabled() { + return true; + } + + @Override + public String getIndexedString(Object value) { + if (value instanceof GotoRelatedItem) { + //noinspection ConstantConditions + return ((GotoRelatedItem)value).getCustomName(); + } + final PsiElement element = (PsiElement)value; + return renderer.getElementText(element) + " " + renderer.getContainerText(element, null); + } + + @Override + public PopupStep onChosen(Object selectedValue, boolean finalChoice) { + processor.process(selectedValue); + return super.onChosen(selectedValue, finalChoice); + } + }) { + }; + popup.getList().setCellRenderer(new PopupListElementRenderer(popup) { + Map<Object, String> separators = new HashMap<Object, String>(); + { + final ListModel model = popup.getList().getModel(); + String current = null; + boolean hasTitle = false; + for (int i = 0; i < model.getSize(); i++) { + final Object element = model.getElementAt(i); + final GotoRelatedItem item = itemsMap.get(element); + if (item != null && !StringUtil.equals(current, item.getGroup())) { + current = item.getGroup(); + separators.put(element, current); + if (!hasTitle && !StringUtil.isEmpty(current)) { + hasTitle = true; + } + } + } + + if (!hasTitle) { + separators.remove(model.getElementAt(0)); + } + } + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + final Component component = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + final String separator = separators.get(value); + + if (separator != null) { + JPanel panel = new JPanel(new BorderLayout()); + panel.add(component, BorderLayout.CENTER); + final SeparatorWithText sep = new SeparatorWithText() { + @Override + protected void paintComponent(Graphics g) { + g.setColor(new JBColor(Color.WHITE, UIUtil.getSeparatorColor())); + g.fillRect(0,0,getWidth(), getHeight()); + super.paintComponent(g); + } + }; + sep.setCaption(separator); + panel.add(sep, BorderLayout.NORTH); + return panel; + } + return component; + } + }); + + popup.setMinimumSize(new Dimension(200, -1)); + + for (Object item : elements) { + final int mnemonic = getMnemonic(item, itemsMap); + if (mnemonic != -1) { + final Action action = createNumberAction(mnemonic, popup, itemsMap, processor); + popup.registerAction(mnemonic + "Action", KeyStroke.getKeyStroke(String.valueOf(mnemonic)), action); + popup.registerAction(mnemonic + "Action", KeyStroke.getKeyStroke("NUMPAD" + String.valueOf(mnemonic)), action); + hasMnemonic.set(true); + } + } + return popup; + } + + private static Action createNumberAction(final int mnemonic, + final ListPopupImpl listPopup, + final Map<PsiElement, GotoRelatedItem> itemsMap, + final Processor<Object> processor) { + return new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + for (final Object item : listPopup.getListStep().getValues()) { + if (getMnemonic(item, itemsMap) == mnemonic) { + listPopup.setFinalRunnable(new Runnable() { + @Override + public void run() { + processor.process(item); + } + }); + listPopup.closeOk(null); + } + } + } + }; + } + + private static int getMnemonic(Object item, Map<PsiElement, GotoRelatedItem> itemsMap) { + return (item instanceof GotoRelatedItem ? (GotoRelatedItem)item : itemsMap.get((PsiElement)item)).getMnemonic(); + } + + @NotNull + public static List<GotoRelatedItem> collectRelatedItems(@NotNull PsiElement contextElement, @Nullable DataContext dataContext) { + Set<GotoRelatedItem> items = ContainerUtil.newLinkedHashSet(); + for (GotoRelatedProvider provider : Extensions.getExtensions(GotoRelatedProvider.EP_NAME)) { + items.addAll(provider.getItems(contextElement)); + if (dataContext != null) { + items.addAll(provider.getItems(dataContext)); + } + } + GotoRelatedItem[] result = items.toArray(new GotoRelatedItem[items.size()]); + Arrays.sort(result, new Comparator<GotoRelatedItem>() { + @Override + public int compare(GotoRelatedItem i1, GotoRelatedItem i2) { + String o1 = i1.getGroup(); + String o2 = i2.getGroup(); + return StringUtil.isEmpty(o1) ? 1 : StringUtil.isEmpty(o2) ? -1 : o1.compareTo(o2); + } + }); + return Arrays.asList(result); + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/problems/MockWolfTheProblemSolver.java b/platform/lang-impl/src/com/intellij/codeInsight/problems/MockWolfTheProblemSolver.java index eeb5e04b8aab..4ea365f34b0c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/problems/MockWolfTheProblemSolver.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/problems/MockWolfTheProblemSolver.java @@ -44,6 +44,11 @@ public class MockWolfTheProblemSolver extends WolfTheProblemSolver { } @Override + public void weHaveGotNonIgnorableProblems(@NotNull VirtualFile virtualFile, @NotNull List<Problem> problems) { + if (myDelegate != null) myDelegate.weHaveGotNonIgnorableProblems(virtualFile, problems); + } + + @Override public boolean hasProblemFilesBeneath(@NotNull final Condition<VirtualFile> condition) { return false; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/problems/WolfTheProblemSolverImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/problems/WolfTheProblemSolverImpl.java index 45b4532713c4..fab185e6ddc0 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/problems/WolfTheProblemSolverImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/problems/WolfTheProblemSolverImpl.java @@ -413,6 +413,12 @@ public class WolfTheProblemSolverImpl extends WolfTheProblemSolver { public void weHaveGotProblems(@NotNull final VirtualFile virtualFile, @NotNull List<Problem> problems) { if (problems.isEmpty()) return; if (!isToBeHighlighted(virtualFile)) return; + weHaveGotNonIgnorableProblems(virtualFile, problems); + } + + @Override + public void weHaveGotNonIgnorableProblems(@NotNull VirtualFile virtualFile, @NotNull List<Problem> problems) { + if (problems.isEmpty()) return; boolean fireListener = false; synchronized (myProblems) { ProblemFileInfo storedProblems = myProblems.get(virtualFile); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java index 516ed9118c8c..0c838cd05ec5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java @@ -212,8 +212,11 @@ public class TemplateState implements Disposable { if (variableName.equals(TemplateImpl.END)) { return new TextResult(""); } - if (myPredefinedVariableValues != null && myPredefinedVariableValues.containsKey(variableName)) { - return new TextResult(myPredefinedVariableValues.get(variableName)); + if (myPredefinedVariableValues != null) { + String text = myPredefinedVariableValues.get(variableName); + if (text != null) { + return new TextResult(text); + } } CharSequence text = myDocument.getCharsSequence(); int segmentNumber = myTemplate.getVariableSegmentNumber(variableName); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/ExpressionPostfixTemplateWithChooser.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/ChooserExpressionSelector.java index f6b0cedae31e..2989f0d0c97c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/ExpressionPostfixTemplateWithChooser.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/ChooserExpressionSelector.java @@ -15,6 +15,7 @@ */ package com.intellij.codeInsight.template.postfix.templates; + import com.intellij.codeInsight.unwrap.ScopeHighlighter; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; @@ -30,45 +31,43 @@ import org.jetbrains.annotations.NotNull; import java.util.List; /** - * @author ignatov + * See {@link PostfixTemplateExpressionSelector} for description */ -public abstract class ExpressionPostfixTemplateWithChooser extends PostfixTemplate { +public class ChooserExpressionSelector implements PostfixTemplateExpressionSelector { @NotNull - protected final PostfixTemplatePsiInfoBase myInfo; + private final Condition<PsiElement> myCondition; - protected ExpressionPostfixTemplateWithChooser(@NotNull String name, @NotNull String example, @NotNull PostfixTemplatePsiInfoBase info) { - super(name, example); - myInfo = info; + public ChooserExpressionSelector(@NotNull Condition<PsiElement> condition) { + myCondition = condition; } - protected ExpressionPostfixTemplateWithChooser(@NotNull String name, - @NotNull String key, - @NotNull String example, - @NotNull PostfixTemplatePsiInfoBase info) { - super(name, key, example); - myInfo = info; - } - @Override - public boolean isApplicable(@NotNull PsiElement context, @NotNull Document copyDocument, int newOffset) { - return !getExpressions(context, copyDocument, newOffset).isEmpty(); + public boolean hasExpression(@NotNull final PostfixTemplateWithExpressionSelector postfixTemplate, + @NotNull PsiElement context, + @NotNull Document copyDocument, + int newOffset) { + return !getExpressions(postfixTemplate, context, copyDocument, newOffset).isEmpty(); } - @Override - public void expand(@NotNull PsiElement context, @NotNull final Editor editor) { - List<PsiElement> expressions = getExpressions(context, editor.getDocument(), editor.getCaretModel().getOffset()); + public void expandTemplate(@NotNull final PostfixTemplateWithExpressionSelector postfixTemplate, + @NotNull PsiElement context, + @NotNull final Editor editor) { + List<PsiElement> expressions = + getExpressions(postfixTemplate, context, editor.getDocument(), editor.getCaretModel().getOffset()); if (expressions.isEmpty()) { PostfixTemplatesUtils.showErrorHint(context.getProject(), editor); } else if (expressions.size() == 1) { - doIt(editor, expressions.get(0)); + postfixTemplate.expandForChooseExpression(expressions.get(0), editor); } else { if (ApplicationManager.getApplication().isUnitTestMode()) { - doIt(editor, expressions.get(expressions.size() - 1)); + PsiElement item = ContainerUtil.getLastItem(expressions); + assert item != null; + postfixTemplate.expandForChooseExpression(item, editor); return; } @@ -81,22 +80,25 @@ public abstract class ExpressionPostfixTemplateWithChooser extends PostfixTempla public void run() { CommandProcessor.getInstance().executeCommand(e.getProject(), new Runnable() { public void run() { - doIt(editor, e); + postfixTemplate.expandForChooseExpression(e, editor); } }, "Expand postfix template", PostfixLiveTemplate.POSTFIX_TEMPLATE_ID); } }); } }, - myInfo.getRenderer(), + postfixTemplate.getPsiInfo().getRenderer(), "Expressions", 0, ScopeHighlighter.NATURAL_RANGER ); } } @NotNull - protected List<PsiElement> getExpressions(@NotNull PsiElement context, @NotNull Document document, final int offset) { - List<PsiElement> possibleExpressions = myInfo.getExpressions(context, document, offset); + private List<PsiElement> getExpressions(@NotNull PostfixTemplateWithExpressionSelector postfixTemplate, + @NotNull PsiElement context, + @NotNull Document document, + final int offset) { + List<PsiElement> possibleExpressions = postfixTemplate.getPsiInfo().getExpressions(context, document, offset); List<PsiElement> expressions = ContainerUtil.filter(possibleExpressions, new Condition<PsiElement>() { @Override @@ -105,19 +107,13 @@ public abstract class ExpressionPostfixTemplateWithChooser extends PostfixTempla } } ); - return ContainerUtil.filter(expressions.isEmpty() ? maybeTopmostExpression(context) : expressions, getTypeCondition()); + return ContainerUtil + .filter(expressions.isEmpty() ? maybeTopmostExpression(postfixTemplate, context) : expressions, myCondition); } - @NotNull - @SuppressWarnings("unchecked") - protected Condition<PsiElement> getTypeCondition() { - return Condition.TRUE; - } @NotNull - private List<PsiElement> maybeTopmostExpression(@NotNull PsiElement context) { - return ContainerUtil.createMaybeSingletonList(myInfo.getTopmostExpression(context)); + private static List<PsiElement> maybeTopmostExpression(@NotNull PostfixTemplateWithExpressionSelector postfixTemplate, @NotNull PsiElement context) { + return ContainerUtil.createMaybeSingletonList(postfixTemplate.getPsiInfo().getTopmostExpression(context)); } - - protected abstract void doIt(@NotNull Editor editor, @NotNull PsiElement expression); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/NotPostfixTemplate.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/NotPostfixTemplate.java index 9c1d9c66b94d..3944cd930af0 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/NotPostfixTemplate.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/NotPostfixTemplate.java @@ -16,26 +16,34 @@ package com.intellij.codeInsight.template.postfix.templates; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.Condition; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; -public class NotPostfixTemplate extends ExpressionPostfixTemplateWithChooser { +import static com.intellij.codeInsight.template.postfix.templates.PostfixTemplatesUtils.selectorWithChooser; - public NotPostfixTemplate(@NotNull PostfixTemplatePsiInfoBase info) { - super("not", "!expr", info); +public class NotPostfixTemplate extends PostfixTemplateWithExpressionSelector { + + public NotPostfixTemplate(@NotNull PostfixTemplatePsiInfo info, @NotNull Condition<PsiElement> typeChecker) { + super("not", "!expr", info, selectorWithChooser(typeChecker)); } + public NotPostfixTemplate(@NotNull PostfixTemplatePsiInfo info) { + super("not", "!expr", info, selectorWithChooser()); + } public NotPostfixTemplate(@NotNull String name, @NotNull String key, @NotNull String example, - @NotNull PostfixTemplatePsiInfoBase info) { - super(name, key, example, info); + @NotNull PostfixTemplatePsiInfo info, + @NotNull Condition<PsiElement> typeChecker + ) { + super(name, key, example, info, selectorWithChooser(typeChecker)); } @Override - protected void doIt(@NotNull Editor editor, @NotNull PsiElement expression) { - PsiElement element = myInfo.getNegatedExpression(expression); + protected void expandForChooseExpression(@NotNull PsiElement expression, @NotNull Editor editor) { + PsiElement element = myPsiInfo.getNegatedExpression(expression); expression.replace(element); } }
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/ParenthesizedPostfixTemplate.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/ParenthesizedPostfixTemplate.java index 9a5d03a9359c..3f1dd1143348 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/ParenthesizedPostfixTemplate.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/ParenthesizedPostfixTemplate.java @@ -16,16 +16,25 @@ package com.intellij.codeInsight.template.postfix.templates; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.Condition; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; -public class ParenthesizedPostfixTemplate extends ExpressionPostfixTemplateWithChooser { - public ParenthesizedPostfixTemplate(PostfixTemplatePsiInfoBase psiInfo) { - super("par", "(expr)", psiInfo); +import static com.intellij.codeInsight.template.postfix.templates.PostfixTemplatesUtils.selectorWithChooser; + +public class ParenthesizedPostfixTemplate extends PostfixTemplateWithExpressionSelector { + + public ParenthesizedPostfixTemplate(PostfixTemplatePsiInfo psiInfo, Condition<PsiElement> condition) { + super("par", "(expr)", psiInfo, selectorWithChooser(condition)); + } + + + public ParenthesizedPostfixTemplate(PostfixTemplatePsiInfo psiInfo) { + super("par", "(expr)", psiInfo, selectorWithChooser()); } @Override - protected void doIt(@NotNull Editor editor, @NotNull PsiElement expression) { - expression.replace(myInfo.createExpression(expression, "(", ")")); + protected void expandForChooseExpression(@NotNull PsiElement expression, @NotNull Editor editor) { + expression.replace(myPsiInfo.createExpression(expression, "(", ")")); } }
\ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplateExpressionSelector.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplateExpressionSelector.java new file mode 100644 index 000000000000..6b5d559da1de --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplateExpressionSelector.java @@ -0,0 +1,54 @@ +/* + * 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.template.postfix.templates; + + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; + +/** + * Interface provides method used in {@link com.intellij.codeInsight.template.postfix.templates.PostfixTemplateWithExpressionSelector} + * + * You should implement the interface if you have non-trivial logic how to determine expression for next processing in postfix template + * Otherwise, you can use one of existing simple implementations: + * + * 1) {@link com.intellij.codeInsight.template.postfix.templates.ChooserExpressionSelector} - The selector get all expression + * in the current position and show to user chooser for these expressions. + * + * 2) {@link com.intellij.codeInsight.template.postfix.templates.TopmostExpressionSelector} - The selector pass to postfix template + * top most expression in the current position + * + * + */ +public interface PostfixTemplateExpressionSelector { + + /** + * Check that we can select not-null expression(PsiElement) in current context + */ + boolean hasExpression(@NotNull final PostfixTemplateWithExpressionSelector postfixTemplate, + @NotNull PsiElement context, + @NotNull Document copyDocument, + int newOffset); + + /** + * Select expression(PsiElement) and call postfixTemplate.expandForChooseExpression for selected expression + */ + void expandTemplate(@NotNull final PostfixTemplateWithExpressionSelector postfixTemplate, + @NotNull PsiElement context, + @NotNull final Editor editor); +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatePsiInfo.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatePsiInfo.java index 8f4716787453..6664d6404d64 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatePsiInfo.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatePsiInfo.java @@ -16,25 +16,42 @@ package com.intellij.codeInsight.template.postfix.templates; +import com.intellij.openapi.editor.Document; import com.intellij.psi.PsiElement; +import com.intellij.util.Function; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public interface PostfixTemplatePsiInfo { +import java.util.List; + +public abstract class PostfixTemplatePsiInfo { @NotNull - PsiElement createStatement(@NotNull PsiElement context, - @NotNull String prefix, - @NotNull String suffix); + public abstract PsiElement createStatement(@NotNull PsiElement context, + @NotNull String prefix, + @NotNull String suffix); @NotNull - PsiElement createExpression(@NotNull PsiElement context, - @NotNull String prefix, - @NotNull String suffix); + public abstract PsiElement createExpression(@NotNull PsiElement context, + @NotNull String prefix, + @NotNull String suffix); @Nullable - PsiElement getTopmostExpression(@NotNull PsiElement element); + public abstract PsiElement getTopmostExpression(@NotNull PsiElement element); + + @NotNull + public abstract PsiElement getNegatedExpression(@NotNull PsiElement element); + + @NotNull + public abstract List<PsiElement> getExpressions(@NotNull PsiElement context, @NotNull Document document, int offset); @NotNull - PsiElement getNegatedExpression(@NotNull PsiElement element); + public Function<PsiElement, String> getRenderer() { + return new Function<PsiElement, String>() { + @Override + public String fun(@NotNull PsiElement element) { + return element.getText(); + } + }; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatePsiInfoBase.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatePsiInfoBase.java deleted file mode 100644 index 7254e651ce41..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatePsiInfoBase.java +++ /dev/null @@ -1,39 +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.template.postfix.templates; - -import com.intellij.openapi.editor.Document; -import com.intellij.psi.PsiElement; -import com.intellij.util.Function; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public abstract class PostfixTemplatePsiInfoBase implements PostfixTemplatePsiInfo { - - @NotNull - public abstract List<PsiElement> getExpressions(@NotNull PsiElement context, @NotNull Document document, int newOffset); - - @NotNull - public Function<PsiElement, String> getRenderer() { - return new Function<PsiElement, String>() { - @Override - public String fun(@NotNull PsiElement element) { - return element.getText(); - } - }; - } -} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplateWithExpressionSelector.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplateWithExpressionSelector.java new file mode 100644 index 000000000000..d7cfbafdf7a8 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplateWithExpressionSelector.java @@ -0,0 +1,83 @@ +/* + * 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.template.postfix.templates; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.Condition; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; + +import static com.intellij.codeInsight.template.postfix.templates.PostfixTemplatesUtils.selectorTopmost; + +public abstract class PostfixTemplateWithExpressionSelector extends PostfixTemplate { + + @NotNull + protected final PostfixTemplatePsiInfo myPsiInfo; + @NotNull + private final PostfixTemplateExpressionSelector mySelector; + + protected PostfixTemplateWithExpressionSelector(@NotNull String name, + @NotNull String key, + @NotNull String example, + @NotNull PostfixTemplatePsiInfo psiInfo, + @NotNull PostfixTemplateExpressionSelector selector) { + super(name, key, example); + myPsiInfo = psiInfo; + mySelector = selector; + } + + + protected PostfixTemplateWithExpressionSelector(@NotNull String name, + @NotNull String example, + @NotNull PostfixTemplatePsiInfo psiInfo, + @NotNull PostfixTemplateExpressionSelector selector) { + super(name, example); + myPsiInfo = psiInfo; + mySelector = selector; + } + + protected PostfixTemplateWithExpressionSelector(@NotNull String name, + @NotNull String example, + @NotNull PostfixTemplatePsiInfo psiInfo, + @NotNull Condition<PsiElement> typeChecker) { + this(name, example, psiInfo, selectorTopmost(typeChecker)); + } + + protected PostfixTemplateWithExpressionSelector(@NotNull String name, + @NotNull String example, + @NotNull PostfixTemplatePsiInfo psiInfo) { + this(name, example, psiInfo, selectorTopmost()); + } + + + @Override + public final boolean isApplicable(@NotNull PsiElement context, @NotNull Document copyDocument, int newOffset) { + return mySelector.hasExpression(this, context, copyDocument, newOffset); + } + + @Override + public final void expand(@NotNull PsiElement context, @NotNull Editor editor) { + mySelector.expandTemplate(this, context, editor); + } + + protected abstract void expandForChooseExpression(@NotNull PsiElement expression, @NotNull Editor editor); + + @NotNull + PostfixTemplatePsiInfo getPsiInfo() { + return myPsiInfo; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatesUtils.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatesUtils.java index 877e10b53490..5ff744e3d72a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatesUtils.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplatesUtils.java @@ -21,6 +21,8 @@ import com.intellij.lang.surroundWith.Surrounder; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Conditions; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.refactoring.util.CommonRefactoringUtil; @@ -31,6 +33,22 @@ public abstract class PostfixTemplatesUtils { private PostfixTemplatesUtils() { } + public static PostfixTemplateExpressionSelector selectorWithChooser() { + return selectorWithChooser(Conditions.<PsiElement>alwaysTrue()); + } + + public static PostfixTemplateExpressionSelector selectorTopmost() { + return selectorTopmost(Conditions.<PsiElement>alwaysTrue()); + } + + public static PostfixTemplateExpressionSelector selectorWithChooser(Condition<PsiElement> condition) { + return new ChooserExpressionSelector(condition); + } + + public static PostfixTemplateExpressionSelector selectorTopmost(Condition<PsiElement> condition) { + return new TopmostExpressionSelector(condition); + } + @Nullable public static TextRange surround(@NotNull Surrounder surrounder, @NotNull Editor editor, diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/StatementWrapPostfixTemplate.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/StatementWrapPostfixTemplate.java index ed1605d3a9fb..5b5c3624f33b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/StatementWrapPostfixTemplate.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/StatementWrapPostfixTemplate.java @@ -22,7 +22,7 @@ import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; -public abstract class StatementWrapPostfixTemplate extends TypedPostfixTemplate { +public abstract class StatementWrapPostfixTemplate extends PostfixTemplateWithExpressionSelector { @SuppressWarnings("unchecked") protected StatementWrapPostfixTemplate(@NotNull String name, @@ -39,9 +39,7 @@ public abstract class StatementWrapPostfixTemplate extends TypedPostfixTemplate } @Override - public void expand(@NotNull PsiElement context, @NotNull Editor editor) { - PsiElement topmostExpression = myPsiInfo.getTopmostExpression(context); - assert topmostExpression != null; + public void expandForChooseExpression(@NotNull PsiElement topmostExpression, @NotNull Editor editor) { PsiElement parent = topmostExpression.getParent(); PsiElement expression = getWrappedExpression(topmostExpression); PsiElement replace = parent.replace(expression); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/StringBasedPostfixTemplate.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/StringBasedPostfixTemplate.java index 4d6582f071de..9f19b2011a49 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/StringBasedPostfixTemplate.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/StringBasedPostfixTemplate.java @@ -26,7 +26,7 @@ import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public abstract class StringBasedPostfixTemplate extends TypedPostfixTemplate { +public abstract class StringBasedPostfixTemplate extends PostfixTemplateWithExpressionSelector { public StringBasedPostfixTemplate(@NotNull String name, @NotNull String example, @@ -36,10 +36,8 @@ public abstract class StringBasedPostfixTemplate extends TypedPostfixTemplate { } @Override - public final void expand(@NotNull PsiElement context, @NotNull Editor editor) { - PsiElement expr = myPsiInfo.getTopmostExpression(context); - assert expr != null; - Project project = context.getProject(); + public final void expandForChooseExpression(@NotNull PsiElement expr, @NotNull Editor editor) { + Project project = expr.getProject(); Document document = editor.getDocument(); PsiElement elementForRemoving = shouldRemoveParent() ? expr.getParent() : expr; document.deleteString(elementForRemoving.getTextRange().getStartOffset(), elementForRemoving.getTextRange().getEndOffset()); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/SurroundPostfixTemplateBase.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/SurroundPostfixTemplateBase.java index 52ad0c4cdde8..29ea4d39ac64 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/SurroundPostfixTemplateBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/SurroundPostfixTemplateBase.java @@ -40,11 +40,9 @@ public abstract class SurroundPostfixTemplateBase extends StatementWrapPostfixTe @Override - public void expand(@NotNull PsiElement context, @NotNull final Editor editor) { + public final void expandForChooseExpression(@NotNull PsiElement context, @NotNull final Editor editor) { PsiElement topmostExpression = myPsiInfo.getTopmostExpression(context); - PsiElement expression = getWrappedExpression(topmostExpression); - assert topmostExpression != null; - PsiElement replace = topmostExpression.replace(expression); + PsiElement replace = getReplacedExpression(topmostExpression); TextRange range = PostfixTemplatesUtils.surround(getSurrounder(), editor, replace); if (range != null) { @@ -52,6 +50,12 @@ public abstract class SurroundPostfixTemplateBase extends StatementWrapPostfixTe } } + protected PsiElement getReplacedExpression(PsiElement topmostExpression) { + PsiElement expression = getWrappedExpression(topmostExpression); + assert topmostExpression != null; + return topmostExpression.replace(expression); + } + public boolean isStatement() { return false; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/TopmostExpressionSelector.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/TopmostExpressionSelector.java new file mode 100644 index 000000000000..bd6028d2d8d2 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/TopmostExpressionSelector.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.codeInsight.template.postfix.templates; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.Condition; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; + + +/** + * See {@link PostfixTemplateExpressionSelector} for description + */ +public class TopmostExpressionSelector implements PostfixTemplateExpressionSelector { + + @NotNull + private final Condition<PsiElement> myCondition; + + public TopmostExpressionSelector(@NotNull Condition<PsiElement> condition) { + + myCondition = condition; + } + + @Override + public boolean hasExpression(@NotNull PostfixTemplateWithExpressionSelector template, + @NotNull PsiElement context, + @NotNull Document copyDocument, + int newOffset) { + PsiElement topmostExpression = template.getPsiInfo().getTopmostExpression(context); + return topmostExpression != null && myCondition.value(topmostExpression); + } + + @Override + public void expandTemplate(@NotNull PostfixTemplateWithExpressionSelector template, + @NotNull PsiElement context, + @NotNull Editor editor) { + PostfixTemplatePsiInfo info = template.getPsiInfo(); + PsiElement expression = info.getTopmostExpression(context); + if (expression == null) { + return; + } + template.expandForChooseExpression(expression, editor); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/TypedPostfixTemplate.java b/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/TypedPostfixTemplate.java deleted file mode 100644 index 0d7d424d99a8..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/postfix/templates/TypedPostfixTemplate.java +++ /dev/null @@ -1,42 +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.template.postfix.templates; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.Condition; -import com.intellij.psi.PsiElement; -import org.jetbrains.annotations.NotNull; - -public abstract class TypedPostfixTemplate extends PostfixTemplate { - - protected final PostfixTemplatePsiInfo myPsiInfo; - protected final Condition<PsiElement> myTypeChecker; - - protected TypedPostfixTemplate(@NotNull String name, - @NotNull String example, - @NotNull PostfixTemplatePsiInfo psiInfo, - @NotNull Condition<PsiElement> typeChecker) { - super(name, example); - this.myPsiInfo = psiInfo; - this.myTypeChecker = typeChecker; - } - - @Override - public boolean isApplicable(@NotNull PsiElement context, @NotNull Document copyDocument, int newOffset) { - PsiElement topmostExpression = myPsiInfo.getTopmostExpression(context); - return topmostExpression != null && myTypeChecker.value(topmostExpression); - } -} |