/*
* Copyright 2000-2016 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.xdebugger.impl.ui;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageUtil;
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.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComponentWithBrowseButton;
import com.intellij.openapi.ui.DialogWrapper;
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.PsiElement;
import com.intellij.reference.SoftReference;
import com.intellij.ui.ClickListener;
import com.intellij.ui.LayeredIcon;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.components.BorderLayoutPanel;
import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.XExpression;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.evaluation.EvaluationMode;
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProviderBase;
import com.intellij.xdebugger.impl.XDebuggerHistoryManager;
import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
import com.intellij.xdebugger.impl.evaluate.CodeFragmentInputComponent;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author nik
*/
public abstract class XDebuggerEditorBase {
private final Project myProject;
private final XDebuggerEditorsProvider myDebuggerEditorsProvider;
@NotNull private final EvaluationMode myMode;
@Nullable private final String myHistoryId;
@Nullable private XSourcePosition mySourcePosition;
private int myHistoryIndex = -1;
@Nullable private PsiElement myContext;
private final JLabel myChooseFactory = new JLabel();
private WeakReference<ListPopup> myPopup;
protected XDebuggerEditorBase(final Project project,
@NotNull XDebuggerEditorsProvider debuggerEditorsProvider,
@NotNull EvaluationMode mode,
@Nullable @NonNls String historyId,
final @Nullable XSourcePosition sourcePosition) {
myProject = project;
myDebuggerEditorsProvider = debuggerEditorsProvider;
myMode = mode;
myHistoryId = historyId;
mySourcePosition = sourcePosition;
myChooseFactory.setToolTipText(XDebuggerBundle.message("xdebugger.evaluate.language.hint"));
myChooseFactory.setBorder(JBUI.Borders.empty(0, 3, 0, 3));
new ClickListener() {
@Override
public boolean onClick(@NotNull MouseEvent e, int clickCount) {
if (myChooseFactory.isEnabled()) {
ListPopup oldPopup = SoftReference.dereference(myPopup);
if (oldPopup != null && !oldPopup.isDisposed()) {
oldPopup.cancel();
myPopup = null;
return true;
}
ListPopup popup = createLanguagePopup();
popup.showUnderneathOf(myChooseFactory);
myPopup = new WeakReference<>(popup);
return true;
}
return false;
}
}.installOn(myChooseFactory);
}
private ListPopup createLanguagePopup() {
DefaultActionGroup actions = new DefaultActionGroup();
for (Language language : getSupportedLanguages()) {
//noinspection ConstantConditions
actions.add(new AnAction(language.getDisplayName(), null, language.getAssociatedFileType().getIcon()) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
XExpression currentExpression = getExpression();
setExpression(new XExpressionImpl(currentExpression.getExpression(), language, currentExpression.getCustomInfo()));
requestFocusInEditor();
}
});
}
DataContext dataContext = DataManager.getInstance().getDataContext(getComponent());
return JBPopupFactory.getInstance().createActionGroupPopup("Choose Language", actions, dataContext,
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,
false);
}
@NotNull
private Collection<Language> getSupportedLanguages() {
XDebuggerEditorsProvider editorsProvider = getEditorsProvider();
if (myContext != null && editorsProvider instanceof XDebuggerEditorsProviderBase) {
return ((XDebuggerEditorsProviderBase)editorsProvider).getSupportedLanguages(myContext);
}
else {
return editorsProvider.getSupportedLanguages(myProject, mySourcePosition);
}
}
protected JPanel decorate(JComponent component, boolean multiline, boolean showEditor) {
BorderLayoutPanel panel = JBUI.Panels.simplePanel();
JPanel factoryPanel = JBUI.Panels.simplePanel();
factoryPanel.add(myChooseFactory, multiline ? BorderLayout.NORTH : BorderLayout.CENTER);
panel.add(factoryPanel, BorderLayout.WEST);
if (!multiline && showEditor) {
component = addMultilineButton(component);
}
panel.addToCenter(component);
return panel;
}
protected JPanel addMultilineButton(JComponent component) {
ComponentWithBrowseButton<JComponent> componentWithButton =
new ComponentWithBrowseButton<>(component, e -> showCodeFragmentEditor(component, this));
componentWithButton.setButtonIcon(AllIcons.Actions.ShowViewer);
componentWithButton.getButton().setDisabledIcon(IconLoader.getDisabledIcon(AllIcons.Actions.ShowViewer));
return componentWithButton;
}
protected JComponent addChooser(JComponent component) {
BorderLayoutPanel panel = JBUI.Panels.simplePanel(component);
panel.setBackground(component.getBackground());
panel.addToRight(myChooseFactory);
return panel;
}
public void setContext(@Nullable PsiElement context) {
if (myContext != context) {
myContext = context;
setExpression(getExpression());
}
}
public void setSourcePosition(@Nullable XSourcePosition sourcePosition) {
if (mySourcePosition != sourcePosition) {
mySourcePosition = sourcePosition;
setExpression(getExpression());
}
}
@NotNull
public EvaluationMode getMode() {
return myMode;
}
@Nullable
public abstract Editor getEditor();
public abstract JComponent getComponent();
public JComponent getEditorComponent() {
return getComponent();
}
protected abstract void doSetText(XExpression text);
public void setExpression(@Nullable XExpression text) {
if (text == null) {
text = getMode() == EvaluationMode.EXPRESSION ? XExpressionImpl.EMPTY_EXPRESSION : XExpressionImpl.EMPTY_CODE_FRAGMENT;
}
Language language = text.getLanguage();
if (language == null) {
if (myContext != null) {
language = myContext.getLanguage();
}
if (language == null && mySourcePosition != null) {
language = LanguageUtil.getFileLanguage(mySourcePosition.getFile());
}
if (language == null) {
language = LanguageUtil.getFileTypeLanguage(getEditorsProvider().getFileType());
}
text = new XExpressionImpl(text.getExpression(), language, text.getCustomInfo(), text.getMode());
}
Collection<Language> languages = getSupportedLanguages();
boolean many = languages.size() > 1;
if (language != null) {
myChooseFactory.setVisible(many);
}
myChooseFactory.setVisible(myChooseFactory.isVisible() || many);
//myChooseFactory.setEnabled(many && languages.contains(language));
if (language != null && language.getAssociatedFileType() != null) {
LayeredIcon icon = new LayeredIcon(2);
icon.setIcon(language.getAssociatedFileType().getIcon(), 0);
icon.setIcon(AllIcons.General.Dropdown, 1, 3, 0);
myChooseFactory.setIcon(icon);
myChooseFactory.setDisabledIcon(IconLoader.getDisabledIcon(icon));
}
doSetText(text);
}
public abstract XExpression getExpression();
@Nullable
public abstract JComponent getPreferredFocusedComponent();
public void requestFocusInEditor() {
JComponent preferredFocusedComponent = getPreferredFocusedComponent();
if (preferredFocusedComponent != null) {
IdeFocusManager.getInstance(myProject).requestFocus(preferredFocusedComponent, true);
}
}
public abstract void selectAll();
protected void onHistoryChanged() {
}
public List<XExpression> getRecentExpressions() {
if (myHistoryId != null) {
return XDebuggerHistoryManager.getInstance(myProject).getRecentExpressions(myHistoryId);
}
return Collections.emptyList();
}
public void saveTextInHistory() {
saveTextInHistory(getExpression());
}
private void saveTextInHistory(final XExpression text) {
if (myHistoryId != null) {
boolean update = XDebuggerHistoryManager.getInstance(myProject).addRecentExpression(myHistoryId, text);
myHistoryIndex = -1; //meaning not from the history list
if (update) {
onHistoryChanged();
}
}
}
@NotNull
protected FileType getFileType(@NotNull XExpression expression) {
FileType fileType = LanguageUtil.getLanguageFileType(expression.getLanguage());
if (fileType != null) {
return fileType;
}
return getEditorsProvider().getFileType();
}
public XDebuggerEditorsProvider getEditorsProvider() {
return myDebuggerEditorsProvider;
}
public Project getProject() {
return myProject;
}
protected Document createDocument(final XExpression text) {
XDebuggerEditorsProvider provider = getEditorsProvider();
if (myContext != null && provider instanceof XDebuggerEditorsProviderBase) {
return ((XDebuggerEditorsProviderBase)provider).createDocument(getProject(), text, myContext, myMode);
}
else {
return provider.createDocument(getProject(), text, mySourcePosition, myMode);
}
}
public boolean canGoBackward() {
return myHistoryIndex < getRecentExpressions().size()-1;
}
public boolean canGoForward() {
return myHistoryIndex > 0;
}
public void goBackward() {
final List<XExpression> expressions = getRecentExpressions();
if (myHistoryIndex < expressions.size() - 1) {
myHistoryIndex++;
setExpression(expressions.get(myHistoryIndex));
}
}
public void goForward() {
final List<XExpression> expressions = getRecentExpressions();
if (myHistoryIndex > 0) {
myHistoryIndex--;
setExpression(expressions.get(myHistoryIndex));
}
}
private void showCodeFragmentEditor(Component parent, XDebuggerEditorBase baseEditor) {
DialogWrapper dialog = new DialogWrapper(parent, true) {
CodeFragmentInputComponent inputComponent =
new CodeFragmentInputComponent(baseEditor.getProject(), baseEditor.getEditorsProvider(), mySourcePosition,
XExpressionImpl.changeMode(baseEditor.getExpression(), EvaluationMode.CODE_FRAGMENT),
null, null);
{
setTitle("Edit");
init();
}
@Nullable
@Override
protected String getDimensionServiceKey() {
return "#xdebugger.code.fragment.editor";
}
@Nullable
@Override
protected JComponent createCenterPanel() {
JPanel component = inputComponent.getMainComponent();
component.setPreferredSize(JBUI.size(300, 200));
return component;
}
@Override
protected void doOKAction() {
super.doOKAction();
baseEditor.setExpression(inputComponent.getInputEditor().getExpression());
JComponent component = baseEditor.getPreferredFocusedComponent();
if (component != null) {
IdeFocusManager.findInstance().requestFocus(component, false);
}
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return inputComponent.getInputEditor().getPreferredFocusedComponent();
}
};
dialog.show();
}
}