| /* |
| * Copyright 2000-2013 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.debugger.ui; |
| |
| import com.intellij.debugger.DebuggerManagerEx; |
| import com.intellij.debugger.engine.evaluation.*; |
| import com.intellij.debugger.impl.DebuggerContextImpl; |
| import com.intellij.debugger.impl.DebuggerUtilsEx; |
| import com.intellij.debugger.impl.PositionUtil; |
| import com.intellij.ide.DataManager; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.actionSystem.AnActionEvent; |
| import com.intellij.openapi.actionSystem.DataContext; |
| import com.intellij.openapi.actionSystem.DefaultActionGroup; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.event.DocumentListener; |
| import com.intellij.openapi.fileTypes.FileType; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.popup.JBPopupFactory; |
| import com.intellij.openapi.ui.popup.ListPopup; |
| import com.intellij.openapi.util.IconLoader; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.PsiDocumentManagerBase; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.reference.SoftReference; |
| import com.intellij.ui.ClickListener; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.xdebugger.impl.XDebuggerHistoryManager; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.border.EmptyBorder; |
| import java.awt.*; |
| import java.awt.event.MouseEvent; |
| import java.lang.ref.WeakReference; |
| import java.util.List; |
| |
| /** |
| * @author lex |
| */ |
| public abstract class DebuggerEditorImpl extends CompletionEditor{ |
| private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.DebuggerEditorImpl"); |
| |
| public static final char SEPARATOR = 13; |
| |
| private final Project myProject; |
| private PsiElement myContext; |
| private PsiType myThisType; |
| |
| private final String myRecentsId; |
| |
| private final List<DocumentListener> myDocumentListeners = ContainerUtil.createLockFreeCopyOnWriteList(); |
| private Document myCurrentDocument; |
| private final JLabel myChooseFactory = new JLabel(); |
| private WeakReference<ListPopup> myPopup; |
| |
| private final PsiTreeChangeListener myPsiListener = new PsiTreeChangeAdapter() { |
| @Override |
| public void childRemoved(@NotNull PsiTreeChangeEvent event) { |
| checkContext(); |
| } |
| @Override |
| public void childReplaced(@NotNull PsiTreeChangeEvent event) { |
| checkContext(); |
| } |
| @Override |
| public void childMoved(@NotNull PsiTreeChangeEvent event) { |
| checkContext(); |
| } |
| private void checkContext() { |
| final PsiElement contextElement = getContext(); |
| if(contextElement == null || !contextElement.isValid()) { |
| final DebuggerManagerEx manager = DebuggerManagerEx.getInstanceEx(myProject); |
| if (manager == null) { |
| LOG.error("Cannot obtain debugger manager for project " + myProject); |
| } |
| final DebuggerContextImpl context = manager.getContextManager().getContext(); |
| final PsiElement newContextElement = PositionUtil.getContextElement(context); |
| setContext(newContextElement != null && newContextElement.isValid()? newContextElement : null); |
| } |
| } |
| }; |
| private CodeFragmentFactory myFactory; |
| protected boolean myInitialFactory; |
| |
| public DebuggerEditorImpl(Project project, PsiElement context, String recentsId, final CodeFragmentFactory factory) { |
| myProject = project; |
| myContext = context; |
| myRecentsId = recentsId; |
| PsiManager.getInstance(project).addPsiTreeChangeListener(myPsiListener); |
| setFactory(factory); |
| myInitialFactory = true; |
| |
| setFocusable(false); |
| |
| myChooseFactory.setToolTipText("Click to change the language"); |
| myChooseFactory.setBorder(new EmptyBorder(0, 3, 0, 3)); |
| new ClickListener() { |
| @Override |
| public boolean onClick(@NotNull MouseEvent e, int clickCount) { |
| ListPopup oldPopup = SoftReference.dereference(myPopup); |
| if (oldPopup != null && !oldPopup.isDisposed()) { |
| oldPopup.cancel(); |
| myPopup = null; |
| return true; |
| } |
| |
| if (myContext == null) { |
| return true; |
| } |
| |
| ListPopup popup = createLanguagePopup(); |
| popup.showUnderneathOf(myChooseFactory); |
| myPopup = new WeakReference<ListPopup>(popup); |
| return true; |
| } |
| }.installOn(myChooseFactory); |
| } |
| |
| private ListPopup createLanguagePopup() { |
| DefaultActionGroup actions = new DefaultActionGroup(); |
| for (final CodeFragmentFactory fragmentFactory : DebuggerUtilsEx.getCodeFragmentFactories(myContext)) { |
| actions.add(new AnAction(fragmentFactory.getFileType().getLanguage().getDisplayName(), null, fragmentFactory.getFileType().getIcon()) { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| setFactory(fragmentFactory); |
| setText(getText()); |
| IdeFocusManager.getInstance(getProject()).requestFocus(DebuggerEditorImpl.this, true); |
| } |
| }); |
| } |
| |
| DataContext dataContext = DataManager.getInstance().getDataContext(this); |
| return JBPopupFactory.getInstance().createActionGroupPopup("Choose language", actions, dataContext, |
| JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, |
| false); |
| } |
| |
| @Override |
| public void setEnabled(boolean enabled) { |
| myChooseFactory.setEnabled(enabled); |
| super.setEnabled(enabled); |
| } |
| |
| protected TextWithImports createItem(Document document, Project project) { |
| if (document != null) { |
| PsiDocumentManager.getInstance(project).commitDocument(document); |
| PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); |
| if (psiFile != null) { |
| return createText(psiFile.getText(), ((JavaCodeFragment)psiFile).importsToString()); |
| } |
| } |
| |
| return createText(""); |
| } |
| |
| protected TextWithImports createText(String text) { |
| return createText(text, ""); |
| } |
| |
| protected abstract TextWithImports createText(String text, String importsString); |
| |
| public abstract JComponent getPreferredFocusedComponent(); |
| |
| @Override |
| public void setContext(@Nullable PsiElement context) { |
| myContext = context; |
| |
| List<CodeFragmentFactory> factories = DebuggerUtilsEx.getCodeFragmentFactories(context); |
| boolean many = factories.size() > 1; |
| if (myInitialFactory) { |
| myInitialFactory = false; |
| setFactory(factories.get(0)); |
| myChooseFactory.setVisible(many); |
| } |
| myChooseFactory.setVisible(myChooseFactory.isVisible() || many); |
| myChooseFactory.setEnabled(many && factories.contains(myFactory)); |
| |
| updateEditorUi(); |
| } |
| |
| @Override |
| public void setText(TextWithImports text) { |
| doSetText(text); |
| updateEditorUi(); |
| } |
| |
| protected abstract void doSetText(TextWithImports text); |
| |
| protected abstract void updateEditorUi(); |
| |
| @Override |
| public PsiElement getContext() { |
| return myContext; |
| } |
| |
| protected Project getProject() { |
| return myProject; |
| } |
| |
| @Override |
| public void requestFocus() { |
| getPreferredFocusedComponent().requestFocus(); |
| } |
| |
| public void setThisType(PsiType thisType) { |
| myThisType = thisType; |
| } |
| |
| @Nullable |
| protected Document createDocument(TextWithImports item) { |
| LOG.assertTrue(myContext == null || myContext.isValid()); |
| |
| if(item == null) { |
| item = createText(""); |
| } |
| JavaCodeFragment codeFragment = getCurrentFactory().createPresentationCodeFragment(item, myContext, getProject()); |
| codeFragment.forceResolveScope(GlobalSearchScope.allScope(myProject)); |
| if (myThisType != null) { |
| codeFragment.setThisType(myThisType); |
| } |
| else if (myContext != null) { |
| final PsiClass contextClass = PsiTreeUtil.getNonStrictParentOfType(myContext, PsiClass.class); |
| if (contextClass != null) { |
| final PsiClassType contextType = JavaPsiFacade.getInstance(codeFragment.getProject()).getElementFactory().createType(contextClass); |
| codeFragment.setThisType(contextType); |
| } |
| } |
| |
| if(myCurrentDocument != null) { |
| for (DocumentListener documentListener : myDocumentListeners) { |
| myCurrentDocument.removeDocumentListener(documentListener); |
| } |
| } |
| |
| myCurrentDocument = PsiDocumentManager.getInstance(getProject()).getDocument(codeFragment); |
| |
| if (myCurrentDocument != null) { |
| PsiDocumentManagerBase.cachePsi(myCurrentDocument, codeFragment); |
| for (DocumentListener documentListener : myDocumentListeners) { |
| myCurrentDocument.addDocumentListener(documentListener); |
| } |
| } |
| |
| return myCurrentDocument; |
| } |
| |
| @Override |
| public String getRecentsId() { |
| return myRecentsId; |
| } |
| |
| public void addRecent(TextWithImports text) { |
| if(getRecentsId() != null && text != null && !text.isEmpty()){ |
| XDebuggerHistoryManager.getInstance(getProject()).addRecentExpression(getRecentsId(), TextWithImportsImpl.toXExpression(text)); |
| } |
| } |
| |
| public void addDocumentListener(DocumentListener listener) { |
| myDocumentListeners.add(listener); |
| if(myCurrentDocument != null) { |
| myCurrentDocument.addDocumentListener(listener); |
| } |
| } |
| |
| public void removeDocumentListener(DocumentListener listener) { |
| myDocumentListeners.remove(listener); |
| if(myCurrentDocument != null) { |
| myCurrentDocument.removeDocumentListener(listener); |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| PsiManager.getInstance(myProject).removePsiTreeChangeListener(myPsiListener); |
| myCurrentDocument = null; |
| } |
| |
| @NotNull |
| private static CodeFragmentFactory findAppropriateFactory(@NotNull TextWithImports text, @Nullable PsiElement context) { |
| for (CodeFragmentFactory factory : DebuggerUtilsEx.getCodeFragmentFactories(context)) { |
| if (factory.getFileType().equals(text.getFileType())) { |
| return factory; |
| } |
| } |
| return DefaultCodeFragmentFactory.getInstance(); |
| } |
| |
| protected void restoreFactory(TextWithImports text) { |
| FileType fileType = text.getFileType(); |
| if (fileType == null) return; |
| if (myContext == null) return; |
| |
| setFactory(findAppropriateFactory(text, myContext)); |
| } |
| |
| private void setFactory(@NotNull final CodeFragmentFactory factory) { |
| myFactory = factory; |
| Icon icon = getCurrentFactory().getFileType().getIcon(); |
| myChooseFactory.setIcon(icon); |
| myChooseFactory.setDisabledIcon(IconLoader.getDisabledIcon(icon)); |
| } |
| |
| protected CodeFragmentFactory getCurrentFactory() { |
| return myFactory instanceof CodeFragmentFactoryContextWrapper ? myFactory : new CodeFragmentFactoryContextWrapper(myFactory); |
| } |
| |
| protected JPanel addChooseFactoryLabel(JComponent component, boolean top) { |
| JPanel panel = new JPanel(new BorderLayout()); |
| panel.add(component, BorderLayout.CENTER); |
| |
| JPanel factoryPanel = new JPanel(new BorderLayout()); |
| factoryPanel.add(myChooseFactory, top ? BorderLayout.NORTH : BorderLayout.CENTER); |
| panel.add(factoryPanel, BorderLayout.WEST); |
| return panel; |
| } |
| } |