js-karma/src/com/intellij/javascript/karma/execution/KarmaRunConfigurationEditor.java (227 lines of code) (raw):
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.javascript.karma.execution;
import com.intellij.execution.configuration.EnvironmentVariablesTextFieldWithBrowseButton;
import com.intellij.javascript.karma.KarmaBundle;
import com.intellij.javascript.karma.scope.KarmaScopeKind;
import com.intellij.javascript.karma.scope.KarmaScopeView;
import com.intellij.javascript.karma.util.KarmaUtil;
import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterField;
import com.intellij.javascript.nodejs.util.NodePackageField;
import com.intellij.lang.javascript.JavaScriptBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.options.ex.SingleConfigurableEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.RawCommandLineEditor;
import com.intellij.ui.TextFieldWithHistory;
import com.intellij.ui.TextFieldWithHistoryWithBrowseButton;
import com.intellij.ui.components.fields.ExpandableTextField;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.*;
import com.intellij.webcore.ui.PathShortener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class KarmaRunConfigurationEditor extends SettingsEditor<KarmaRunConfiguration> {
private final Project myProject;
private final NodeJsInterpreterField myNodeInterpreterField;
private final RawCommandLineEditor myNodeOptionsEditor;
private final NodePackageField myKarmaPackageField;
private final TextFieldWithBrowseButton myWorkingDirComponent;
private final TextFieldWithHistoryWithBrowseButton myConfigPathField;
private final EnvironmentVariablesTextFieldWithBrowseButton myEnvVarsComponent;
private final RawCommandLineEditor myKarmaOptionsEditor;
private final Map<KarmaScopeKind, JRadioButton> myRadioButtonMap = new HashMap<>();
private final Map<KarmaScopeKind, KarmaScopeView> myScopeKindViewMap = new HashMap<>();
private final JPanel mySelectedScopeKindPanel;
private final JPanel myRootComponent;
private final int myLongestLabelWidth = new JLabel(UIUtil.removeMnemonic(KarmaBundle.message("runConfiguration.environment.label"))).getPreferredSize().width;
public KarmaRunConfigurationEditor(@NotNull Project project) {
myProject = project;
myNodeInterpreterField = new NodeJsInterpreterField(project, true);
myNodeOptionsEditor = createOptionsEditor(null);
myKarmaPackageField = new NodePackageField(myNodeInterpreterField, KarmaUtil.PKG_DESCRIPTOR, null);
myWorkingDirComponent = createWorkingDirComponent(project);
myConfigPathField = createConfigurationFileTextField(project);
myEnvVarsComponent = new EnvironmentVariablesTextFieldWithBrowseButton();
myKarmaOptionsEditor = createOptionsEditor(KarmaBundle.message("run_config.karma_options.placeholder.text"));
JPanel scopeKindPanel = createScopeKindRadioButtonPanel();
mySelectedScopeKindPanel = new JPanel(new BorderLayout());
myRootComponent = new FormBuilder()
.setAlignLabelOnRight(false)
.addLabeledComponent(KarmaBundle.message("runConfiguration.config_file.label"), myConfigPathField)
.addLabeledComponent(KarmaBundle.message("runConfiguration.karmaOptions.label"), myKarmaOptionsEditor)
.addComponent(new JSeparator(), 8)
.addLabeledComponent(NodeJsInterpreterField.getLabelTextForComponent(), myNodeInterpreterField, 8)
.addLabeledComponent(JavaScriptBundle.message("rc.nodeOptions.label"), myNodeOptionsEditor)
.addLabeledComponent(KarmaBundle.message("runConfiguration.karma_package_dir.label"), myKarmaPackageField)
.addLabeledComponent(JavaScriptBundle.message("rc.workingDirectory.label"), myWorkingDirComponent)
.addLabeledComponent(KarmaBundle.message("runConfiguration.environment.label"), myEnvVarsComponent)
.addSeparator(8)
.addComponent(scopeKindPanel)
.addComponent(mySelectedScopeKindPanel)
.getPanel();
}
private static @NotNull RawCommandLineEditor createOptionsEditor(@Nullable @NlsContexts.StatusText String emptyText) {
RawCommandLineEditor editor = new RawCommandLineEditor();
JTextField field = editor.getTextField();
if (field instanceof ExpandableTextField) {
((ExpandableTextField)field).setMonospaced(false);
}
if (field instanceof ComponentWithEmptyText && emptyText != null) {
((ComponentWithEmptyText)field).getEmptyText().setText(emptyText);
}
return editor;
}
private static @NotNull TextFieldWithBrowseButton createWorkingDirComponent(@NotNull Project project) {
TextFieldWithBrowseButton textFieldWithBrowseButton = new TextFieldWithBrowseButton();
var descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(JavaScriptBundle.message("rc.workingDirectory.browseDialogTitle"));
SwingHelper.installFileCompletionAndBrowseDialog(project, textFieldWithBrowseButton, descriptor);
PathShortener.enablePathShortening(textFieldWithBrowseButton.getTextField(), null);
return textFieldWithBrowseButton;
}
private @NotNull JPanel createScopeKindRadioButtonPanel() {
JPanel testKindPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, JBUIScale.scale(40), 0));
testKindPanel.setBorder(JBUI.Borders.emptyLeft(10));
ButtonGroup buttonGroup = new ButtonGroup();
for (KarmaScopeKind scopeKind : KarmaScopeKind.values()) {
JRadioButton radioButton = new JRadioButton(UIUtil.replaceMnemonicAmpersand(scopeKind.getName()));
radioButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setScopeKind(scopeKind);
}
});
myRadioButtonMap.put(scopeKind, radioButton);
testKindPanel.add(radioButton);
buttonGroup.add(radioButton);
}
return testKindPanel;
}
private void setScopeKind(@NotNull KarmaScopeKind scopeKind) {
KarmaScopeKind selectedScopeKind = getScopeKind();
if (selectedScopeKind != scopeKind) {
JRadioButton radioButton = myRadioButtonMap.get(scopeKind);
radioButton.setSelected(true);
}
KarmaScopeView view = getScopeKindView(scopeKind);
setCenterBorderLayoutComponent(mySelectedScopeKindPanel, view.getComponent());
}
private @Nullable KarmaScopeKind getScopeKind() {
for (Map.Entry<KarmaScopeKind, JRadioButton> entry : myRadioButtonMap.entrySet()) {
if (entry.getValue().isSelected()) {
return entry.getKey();
}
}
return null;
}
private @NotNull KarmaScopeView getScopeKindView(@NotNull KarmaScopeKind scopeKind) {
KarmaScopeView view = myScopeKindViewMap.get(scopeKind);
if (view == null) {
view = scopeKind.createView(myProject);
myScopeKindViewMap.put(scopeKind, view);
JComponent component = view.getComponent();
if (component.getLayout() instanceof GridBagLayout) {
component.add(Box.createHorizontalStrut(myLongestLabelWidth), new GridBagConstraints(
0, GridBagConstraints.RELATIVE,
1, 1,
0.0, 0.0,
GridBagConstraints.EAST,
GridBagConstraints.NONE,
JBUI.insetsRight(UIUtil.DEFAULT_HGAP),
0, 0
));
}
}
return view;
}
private static void setCenterBorderLayoutComponent(@NotNull JPanel panel, @NotNull Component child) {
Component prevChild = ((BorderLayout)panel.getLayout()).getLayoutComponent(BorderLayout.CENTER);
if (prevChild != null) {
panel.remove(prevChild);
}
panel.add(child, BorderLayout.CENTER);
panel.revalidate();
panel.repaint();
}
private static @NotNull TextFieldWithHistoryWithBrowseButton createConfigurationFileTextField(final @NotNull Project project) {
TextFieldWithHistoryWithBrowseButton textFieldWithHistoryWithBrowseButton = new TextFieldWithHistoryWithBrowseButton();
final TextFieldWithHistory textFieldWithHistory = textFieldWithHistoryWithBrowseButton.getChildComponent();
textFieldWithHistory.setHistorySize(-1);
textFieldWithHistory.setMinimumAndPreferredWidth(0);
// add a fake empty element as 'Down' key doesn't show popup if the combobox model is empty
textFieldWithHistory.setHistory(Collections.singletonList(""));
PathShortener.enablePathShortening(textFieldWithHistory.getTextEditor(), null);
SwingHelper.addHistoryOnExpansion(textFieldWithHistory, () -> {
textFieldWithHistory.setHistory(Collections.emptyList());
List<VirtualFile> newFiles = KarmaUtil.listPossibleConfigFilesInProject(project);
return ContainerUtil.sorted(ContainerUtil.map(newFiles, file -> {
String path = FileUtil.toSystemDependentName(file.getPath());
return FileUtil.getLocationRelativeToUserHome(path, false);
}));
});
var descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor().withTitle(KarmaBundle.message("runConfiguration.config_file.browse_dialog.title"));
SwingHelper.installFileCompletionAndBrowseDialog(project, textFieldWithHistoryWithBrowseButton, descriptor);
return textFieldWithHistoryWithBrowseButton;
}
@Override
protected void resetEditorFrom(@NotNull KarmaRunConfiguration runConfiguration) {
KarmaRunSettings runSettings = runConfiguration.getRunSettings();
myNodeInterpreterField.setInterpreterRef(runSettings.getInterpreterRef());
myNodeOptionsEditor.setText(runSettings.getNodeOptions());
myKarmaPackageField.setSelected(runConfiguration.getKarmaPackage());
myConfigPathField.setTextAndAddToHistory(runSettings.getConfigPathSystemDependent());
myKarmaOptionsEditor.setText(runSettings.getKarmaOptions());
myWorkingDirComponent.setText(runSettings.getWorkingDirectorySystemDependent());
myEnvVarsComponent.setData(runSettings.getEnvData());
setScopeKind(runSettings.getScopeKind());
KarmaScopeView view = getScopeKindView(runSettings.getScopeKind());
view.resetFrom(runSettings);
updatePreferredWidth();
}
private void updatePreferredWidth() {
DialogWrapper dialogWrapper = DialogWrapper.findInstance(myRootComponent);
if (dialogWrapper instanceof SingleConfigurableEditor) {
// dialog for single run configuration
myNodeInterpreterField.setPreferredWidthToFitText();
myKarmaPackageField.setPreferredWidthToFitText();
SwingHelper.setPreferredWidthToFitText(myConfigPathField);
ApplicationManager.getApplication().invokeLater(() -> SwingHelper.adjustDialogSizeToFitPreferredSize(dialogWrapper), ModalityState.any());
}
}
@Override
protected void applyEditorTo(@NotNull KarmaRunConfiguration runConfiguration) {
KarmaRunSettings.Builder builder = new KarmaRunSettings.Builder();
builder.setConfigPath(PathShortener.getAbsolutePath(myConfigPathField.getChildComponent().getTextEditor()));
builder.setKarmaOptions(StringUtil.notNullize(myKarmaOptionsEditor.getText()));
builder.setInterpreterRef(myNodeInterpreterField.getInterpreterRef());
builder.setNodeOptions(myNodeOptionsEditor.getText());
builder.setKarmaPackage(myKarmaPackageField.getSelected());
builder.setWorkingDirectory(PathShortener.getAbsolutePath(myWorkingDirComponent.getTextField()));
builder.setEnvData(myEnvVarsComponent.getData());
KarmaScopeKind scopeKind = getScopeKind();
if (scopeKind != null) {
builder.setScopeKind(scopeKind);
KarmaScopeView view = getScopeKindView(scopeKind);
view.applyTo(builder);
}
runConfiguration.setRunSettings(builder.build());
}
@Override
protected @NotNull JComponent createEditor() {
return myRootComponent;
}
}