Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,9 @@
<text resource-bundle="messages/DartBundle" key="remote.debug.search.sources.in"/>
</properties>
</component>
<component id="e3762" class="com.intellij.ui.ComboboxWithBrowseButton" binding="myDartProjectCombo">
<component id="e3762" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myDartProjectCombo">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false">
<minimum-size width="100" height="-1"/>
<preferred-size width="100" height="-1"/>
</grid>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,80 +4,28 @@
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.search.FileTypeIndex;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.GlobalSearchScopesCore;
import com.intellij.ui.ComboboxWithBrowseButton;
import com.intellij.ui.SimpleListCellRenderer;
import com.intellij.ui.components.JBLabel;
import com.jetbrains.lang.dart.DartFileType;
import com.jetbrains.lang.dart.ide.runner.server.DartRemoteDebugConfiguration;
import com.jetbrains.lang.dart.ide.runner.server.DartRemoteDebugParameters;
import com.jetbrains.lang.dart.util.PubspecYamlUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.SortedSet;
import java.util.TreeSet;

import static com.jetbrains.lang.dart.util.PubspecYamlUtil.PUBSPEC_YAML;

public class DartRemoteDebugConfigurationEditor extends SettingsEditor<DartRemoteDebugConfiguration> {

private JPanel myMainPanel;
private ComboboxWithBrowseButton myDartProjectCombo;
private TextFieldWithBrowseButton myDartProjectCombo;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The replacement of ComboBoxWithBrowseButton with TextFieldWithBrowseButton has resulted in the removal of the project discovery logic and the dropdown list of projects. This is a regression in functionality, as users can no longer easily select from detected Dart projects within the workspace. Consider using a ComboBox with an ExtendableTextField and a browse extension (similar to the implementation in DartGeneratorPeer) to maintain the original behavior while adhering to the new API.

References
  1. Readability and Maintainability: The removal of useful features during refactoring reduces the overall quality and usability of the tool. (link)

private JBLabel myHintLabel;

private final SortedSet<NameAndPath> myComboItems = new TreeSet<>();

public DartRemoteDebugConfigurationEditor(final @NotNull Project project) {
initDartProjectsCombo(project);
myDartProjectCombo.addBrowseFolderListener(null, null, project, FileChooserDescriptorFactory.createSingleFolderDescriptor(),
TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
myHintLabel.setCopyable(true);
}

private void initDartProjectsCombo(final @NotNull Project project) {
myDartProjectCombo.getComboBox().setRenderer(SimpleListCellRenderer.create("", NameAndPath::getPresentableText));

if (!project.isDefault()) {
for (VirtualFile pubspecFile : FilenameIndex.getVirtualFilesByName(PUBSPEC_YAML, GlobalSearchScope.projectScope(project))) {
myComboItems.add(new NameAndPath(PubspecYamlUtil.getDartProjectName(pubspecFile), pubspecFile.getParent().getPath()));
}

if (myComboItems.isEmpty()) {
for (VirtualFile contentRoot : ProjectRootManager.getInstance(project).getContentRoots()) {
if (FileTypeIndex.containsFileOfType(DartFileType.INSTANCE, GlobalSearchScopesCore.directoryScope(project, contentRoot, true))) {
myComboItems.add(new NameAndPath(null, contentRoot.getPath()));
}
}
}
}

myDartProjectCombo.getComboBox().setModel(new DefaultComboBoxModel<>(myComboItems.toArray()));

myDartProjectCombo.addBrowseFolderListener(
project,
FileChooserDescriptorFactory.createSingleFolderDescriptor(),
new TextComponentAccessor<>() {
@Override
public String getText(final JComboBox combo) {
final Object item = combo.getSelectedItem();
return item instanceof NameAndPath ? ((NameAndPath)item).myPath : "";
}

@Override
public void setText(final JComboBox combo, final @NotNull String path) {
setSelectedProjectPath(FileUtil.toSystemIndependentName(path));
}
});
}

@Override
protected @NotNull JComponent createEditor() {
return myMainPanel;
Expand All @@ -86,62 +34,13 @@ public void setText(final JComboBox combo, final @NotNull String path) {
@Override
protected void resetEditorFrom(final @NotNull DartRemoteDebugConfiguration config) {
final DartRemoteDebugParameters params = config.getParameters();
setSelectedProjectPath(params.getDartProjectPath());
}

private void setSelectedProjectPath(final @NotNull String projectPath) {
if (projectPath.isEmpty()) return;

final VirtualFile pubspecFile = LocalFileSystem.getInstance().findFileByPath(projectPath + "/" + PUBSPEC_YAML);
final String projectName = pubspecFile == null ? null : PubspecYamlUtil.getDartProjectName(pubspecFile);
final NameAndPath item = new NameAndPath(projectName, projectPath);

if (!myComboItems.contains(item)) {
myComboItems.add(item);
myDartProjectCombo.getComboBox().setModel(new DefaultComboBoxModel(myComboItems.toArray()));
}

myDartProjectCombo.getComboBox().setSelectedItem(item);
myDartProjectCombo.setText(FileUtil.toSystemDependentName(params.getDartProjectPath()));
}

@Override
protected void applyEditorTo(final @NotNull DartRemoteDebugConfiguration config) {
final DartRemoteDebugParameters params = config.getParameters();
final Object selectedItem = myDartProjectCombo.getComboBox().getSelectedItem();
params.setDartProjectPath(selectedItem instanceof NameAndPath ? ((NameAndPath)selectedItem).myPath : "");
}

private static class NameAndPath implements Comparable<NameAndPath> {
private final @Nullable String myName;
private final @NotNull String myPath;

NameAndPath(final @Nullable String name, final @NotNull String path) {
myName = name;
myPath = path;
}

public String getPresentableText() {
return myName == null ? FileUtil.toSystemDependentName(myPath) : myName + " (" + FileUtil.toSystemDependentName(myPath) + ")";
}

@Override
public String toString() {
return getPresentableText();
}

@Override
public boolean equals(final Object o) {
return (o instanceof NameAndPath) && myPath.equals(((NameAndPath)o).myPath);
}

@Override
public int hashCode() {
return myPath.hashCode();
}

@Override
public int compareTo(final NameAndPath o) {
return myPath.compareTo(o.myPath); // root project goes first, before its subprojects
}
params.setDartProjectPath(FileUtil.toSystemIndependentName(myDartProjectCombo.getText().trim()));
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<text resource-bundle="messages/DartBundle" key="dart.sdk.path.label"/>
</properties>
</component>
<component id="b09db" class="com.intellij.ui.ComboboxWithBrowseButton" binding="mySdkPathComboWithBrowse" custom-create="true">
<component id="b09db" class="com.intellij.openapi.ui.ComboBox" binding="mySdkPathComboWithBrowse" custom-create="true">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.ide.util.projectWizard.SettingsStep;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.ui.ValidationInfo;
Expand All @@ -13,15 +16,17 @@
import com.intellij.openapi.util.text.HtmlBuilder;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.platform.ProjectGeneratorPeer;
import com.intellij.platform.WebProjectGenerator;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.ComboboxWithBrowseButton;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBList;
import com.intellij.ui.components.fields.ExtendableTextComponent;
import com.intellij.ui.components.fields.ExtendableTextField;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.util.ui.AsyncProcessIcon;
import com.jetbrains.lang.dart.DartBundle;
Expand All @@ -31,6 +36,7 @@

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.util.List;
Expand All @@ -41,7 +47,7 @@ public class DartGeneratorPeer implements ProjectGeneratorPeer<DartProjectWizard
private static final String CREATE_SAMPLE_UNCHECKED = "CREATE_SAMPLE_UNCHECKED";

private JPanel myMainPanel;
private ComboboxWithBrowseButton mySdkPathComboWithBrowse;
private ComboBox<String> mySdkPathComboWithBrowse;
private JBLabel myVersionLabel;

private JPanel myTemplatesPanel;
Expand All @@ -59,12 +65,8 @@ public class DartGeneratorPeer implements ProjectGeneratorPeer<DartProjectWizard
private String myDartCreateTemplatesSdkPath; //used to expire the above cache if the sdk is changed an alternative would be to use the same cache method as com.jetbrains.lang.dart.sdk.DartSdkUtil.getSdkVersion

public DartGeneratorPeer() {
// set initial values before initDartSdkControls() because listeners should not be triggered on initialization
mySdkPathComboWithBrowse.getComboBox().setEditable(true);
//mySdkPathComboWithBrowse.getComboBox().getEditor().setItem(...); initial sdk path will be correctly taken from known paths history

// now setup controls
DartSdkUtil.initDartSdkControls(null, mySdkPathComboWithBrowse, myVersionLabel);
mySdkPathComboWithBrowse.setEditable(true);
DartSdkUtil.addKnownSDKPathsToCombo(mySdkPathComboWithBrowse);

myCreateSampleProjectCheckBox.addActionListener(e -> myTemplatesList.setEnabled(myCreateSampleProjectCheckBox.isSelected()));
String selectedTemplateName = PropertiesComponent.getInstance().getValue(DART_PROJECT_TEMPLATE);
Expand Down Expand Up @@ -92,11 +94,14 @@ public Component getListCellRendererComponent(JList list, Object value, int inde
myCreateSampleProjectCheckBox.setEnabled(false);
myTemplatesList.setEnabled(false);

final JTextComponent editorComponent = (JTextComponent)mySdkPathComboWithBrowse.getComboBox().getEditor().getEditorComponent();
final JTextComponent editorComponent = (JTextComponent)mySdkPathComboWithBrowse.getEditor().getEditorComponent();
editorComponent.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(final @NotNull DocumentEvent e) {
if(e.getType() == DocumentEvent.EventType.INSERT){
final String sdkHomePath = getSdkPathText();
final String sdkVersion = DartSdkUtil.getSdkVersion(sdkHomePath);
myVersionLabel.setText(sdkVersion == null ? "" : sdkVersion);
onSdkPathChanged();
}
Comment on lines 101 to 106
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The DocumentListener here only reacts to INSERT events. This means if a user deletes characters from the SDK path (e.g., to correct a typo), the version label and the project templates list will not update. This leads to an inconsistent UI state. The check should be removed to handle all text changes, including deletions.

        final String sdkHomePath = getSdkPathText();
        final String sdkVersion = DartSdkUtil.getSdkVersion(sdkHomePath);
        myVersionLabel.setText(sdkVersion == null ? "" : sdkVersion);
        onSdkPathChanged();

}
Expand All @@ -105,8 +110,12 @@ protected void textChanged(final @NotNull DocumentEvent e) {
onSdkPathChanged();
}

private String getSdkPathText() {
return mySdkPathComboWithBrowse.getEditor().getItem().toString().trim();
}

private void onSdkPathChanged() {
String sdkPath = mySdkPathComboWithBrowse.getComboBox().getEditor().getItem().toString().trim();
String sdkPath = getSdkPathText();
// If the sdk path has changed, recalculate the create template options
if (myDartCreateTemplatesSdkPath != null && !myDartCreateTemplatesSdkPath.equals(sdkPath)) {
clearTemplates();
Expand Down Expand Up @@ -159,7 +168,7 @@ private void startLoadingTemplates() {
asyncProcessIcon.resume();

ApplicationManager.getApplication().executeOnPooledThread(() -> {
final String comboSdkPath = mySdkPathComboWithBrowse.getComboBox().getEditor().getItem().toString().trim();
final String comboSdkPath = getSdkPathText();
final String sdkPath =
FileUtil.toSystemIndependentName(comboSdkPath);
DartProjectTemplate.loadTemplatesAsync(sdkPath, templates -> {
Expand Down Expand Up @@ -236,7 +245,7 @@ public void buildUI(final @NotNull SettingsStep settingsStep) {

@Override
public @NotNull DartProjectWizardData getSettings() {
final String sdkPath = FileUtil.toSystemIndependentName(mySdkPathComboWithBrowse.getComboBox().getEditor().getItem().toString().trim());
final String sdkPath = FileUtil.toSystemIndependentName(getSdkPathText());
final DartProjectTemplate template = myCreateSampleProjectCheckBox.isSelected() ? myTemplatesList.getSelectedValue() : null;
PropertiesComponent.getInstance().setValue(DART_PROJECT_TEMPLATE, template == null ? CREATE_SAMPLE_UNCHECKED : template.getName());

Expand All @@ -245,7 +254,7 @@ public void buildUI(final @NotNull SettingsStep settingsStep) {

@Override
public @Nullable ValidationInfo validate() {
final String sdkPath = mySdkPathComboWithBrowse.getComboBox().getEditor().getItem().toString().trim();
final String sdkPath = getSdkPathText();
final String message = DartSdkUtil.getErrorMessageIfWrongSdkRootPath(sdkPath);
if (message != null) {
return new ValidationInfo(message, mySdkPathComboWithBrowse);
Expand Down Expand Up @@ -284,7 +293,7 @@ public boolean validateInIntelliJ() {
}

private void enableIntellijLiveValidation() {
final JTextComponent editorComponent = (JTextComponent)mySdkPathComboWithBrowse.getComboBox().getEditor().getEditorComponent();
final JTextComponent editorComponent = (JTextComponent)mySdkPathComboWithBrowse.getEditor().getEditorComponent();
editorComponent.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(final @NotNull DocumentEvent e) {
Expand All @@ -304,7 +313,7 @@ public boolean isBackgroundJobRunning() {

@Override
public void addSettingsStateListener(final @NotNull WebProjectGenerator.SettingsStateListener stateListener) {
final JTextComponent editorComponent = (JTextComponent)mySdkPathComboWithBrowse.getComboBox().getEditor().getEditorComponent();
final JTextComponent editorComponent = (JTextComponent)mySdkPathComboWithBrowse.getEditor().getEditorComponent();
editorComponent.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(final @NotNull DocumentEvent e) {
Expand All @@ -318,6 +327,29 @@ protected void textChanged(final @NotNull DocumentEvent e) {
}

private void createUIComponents() {
mySdkPathComboWithBrowse = new ComboboxWithBrowseButton(new ComboBox<>());
ExtendableTextComponent.Extension browseExtension =
ExtendableTextComponent.Extension.create(AllIcons.General.OpenDisk, AllIcons.General.OpenDiskHover,
DartBundle.message("dart.sdk.path.label"),
() -> {
final FileChooserDescriptor descriptor =
FileChooserDescriptorFactory.createSingleFolderDescriptor();
final VirtualFile file =
FileChooser.chooseFile(descriptor, mySdkPathComboWithBrowse, null, null);
if (file != null) {
mySdkPathComboWithBrowse.getEditor().setItem(file.getPath());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When a folder is selected via the browse button, file.getPath() returns a system-independent path (using forward slashes). For better consistency with the IntelliJ UI, especially on Windows, it is recommended to convert this to a system-dependent path before setting it in the editor.

                                                   mySdkPathComboWithBrowse.getEditor().setItem(FileUtil.toSystemDependentName(file.getPath()));
References
  1. Consistency: Adhering to a consistent style across all projects improves collaboration and reduces errors. (link)

}
});

mySdkPathComboWithBrowse = new ComboBox<>();
mySdkPathComboWithBrowse.setEditable(true);
mySdkPathComboWithBrowse.setEditor(new BasicComboBoxEditor() {
@Override
protected JTextField createEditorComponent() {
final ExtendableTextField ecbEditor = new ExtendableTextField();
ecbEditor.addExtension(browseExtension);
ecbEditor.setBorder(null);
return ecbEditor;
}
});
}
Comment on lines 329 to 354
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for creating a ComboBox with a browse extension is duplicated here and in DartConfigurable.java. To improve maintainability and reduce duplication, this logic should be extracted into a reusable utility method, for example in DartSdkUtil.

References
  1. Maintainability: Code should be easy to modify and extend. Reducing duplication is key to maintainability. (link)

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<properties/>
<border type="none"/>
<children>
<component id="80a7b" class="com.intellij.ui.ComboboxWithBrowseButton" binding="mySdkPathComboWithBrowse" custom-create="true">
<component id="80a7b" class="com.intellij.openapi.ui.ComboBox" binding="mySdkPathComboWithBrowse" custom-create="true">
<constraints>
<grid row="0" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
Expand Down
Loading
Loading