package com.coffeelint.settings; import com.coffeelint.CoffeeLintProjectComponent; import com.coffeelint.cli.CoffeeLintFinder; import com.coffeelint.cli.CoffeeLintRunner; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.execution.ExecutionException; //import com.intellij.javascript.nodejs.NodeDetectionUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiManager; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.HyperlinkLabel; import com.intellij.ui.TextFieldWithHistory; import com.intellij.ui.TextFieldWithHistoryWithBrowseButton; import com.intellij.util.NotNullProducer; import com.intellij.util.ui.UIUtil; import com.intellij.webcore.ui.SwingHelper; import com.wix.nodejs.NodeDetectionUtil; import com.wix.settings.ValidationInfo; import com.wix.ui.PackagesNotificationPanel; import com.wix.utils.FileUtils; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.DocumentEvent; import java.awt.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.File; import java.util.ArrayList; import java.util.List; public class CoffeeLintSettingsPage implements Configurable { public static final String FIX_IT = "Fix it"; public static final String HOW_TO_USE_COFFEE_LINT = "How to Use CoffeeLint"; public static final String HOW_TO_USE_LINK = "https://github.com/idok/coffee-lint-plugin"; protected Project project; private JCheckBox pluginEnabledCheckbox; private JTextField customRulesPathField; private JPanel panel; private JPanel errorPanel; private TextFieldWithHistoryWithBrowseButton coffeelintBinField; private TextFieldWithHistoryWithBrowseButton nodeInterpreterField; private TextFieldWithHistoryWithBrowseButton eslintrcFile; private JRadioButton searchForEslintrcInRadioButton; private JRadioButton useProjectEslintrcRadioButton; private HyperlinkLabel usageLink; private JLabel coffeeLintConfigFilePathLabel; private JLabel rulesDirectoryLabel; private JLabel pathToCoffeelintBinLabel; private JLabel nodeInterpreterLabel; private JLabel versionLabel; private JTextField extensionsField; // private TextFieldWithHistoryWithBrowseButton rulesPathField; // private JLabel rulesDirectoryLabel1; private final PackagesNotificationPanel packagesNotificationPanel; public CoffeeLintSettingsPage(@NotNull final Project project) { this.project = project; configLintBinField(); configConfigFileField(); // configESLintRulesField(); configNodeField(); // searchForEslintrcInRadioButton.addItemListener(new ItemListener() { // public void itemStateChanged(ItemEvent e) { // eslintrcFile.setEnabled(e.getStateChange() == ItemEvent.DESELECTED); // System.out.println("searchForEslintrcInRadioButton: " + (e.getStateChange() == ItemEvent.SELECTED ? "checked" : "unchecked")); // } // }); useProjectEslintrcRadioButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { eslintrcFile.setEnabled(e.getStateChange() == ItemEvent.SELECTED); // System.out.println("useProjectEslintrcRadioButton: " + (e.getStateChange() == ItemEvent.SELECTED ? "checked" : "unchecked")); } }); pluginEnabledCheckbox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { boolean enabled = e.getStateChange() == ItemEvent.SELECTED; setEnabledState(enabled); } }); this.packagesNotificationPanel = new PackagesNotificationPanel(project); // GridConstraints gridConstraints = new GridConstraints(5, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, // GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, // null, new Dimension(250, 150), null); errorPanel.add(this.packagesNotificationPanel.getComponent(), BorderLayout.CENTER); DocumentAdapter docAdp = new DocumentAdapter() { protected void textChanged(DocumentEvent e) { updateLaterInEDT(); } }; coffeelintBinField.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp); eslintrcFile.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp); nodeInterpreterField.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp); // rulesPathField.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp); customRulesPathField.getDocument().addDocumentListener(docAdp); } private File getProjectPath() { return new File(project.getBaseDir().getPath()); } private void updateLaterInEDT() { UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { CoffeeLintSettingsPage.this.update(); } }); } private void update() { ApplicationManager.getApplication().assertIsDispatchThread(); validate(); } private void setEnabledState(boolean enabled) { eslintrcFile.setEnabled(enabled); customRulesPathField.setEnabled(enabled); searchForEslintrcInRadioButton.setEnabled(enabled); useProjectEslintrcRadioButton.setEnabled(enabled); coffeelintBinField.setEnabled(enabled); nodeInterpreterField.setEnabled(enabled); coffeeLintConfigFilePathLabel.setEnabled(enabled); rulesDirectoryLabel.setEnabled(enabled); pathToCoffeelintBinLabel.setEnabled(enabled); nodeInterpreterLabel.setEnabled(enabled); extensionsField.setEnabled(enabled); } private void validateField(List<ValidationInfo> errors, TextFieldWithHistoryWithBrowseButton field, boolean allowEmpty, String message) { if (!validatePath(field.getChildComponent().getText(), allowEmpty)) { ValidationInfo error = new ValidationInfo(field.getChildComponent().getTextEditor(), message, FIX_IT); errors.add(error); } } private void validate() { if (!pluginEnabledCheckbox.isSelected()) { return; } List<ValidationInfo> errors = new ArrayList<ValidationInfo>(); validateField(errors, coffeelintBinField, false, "Path to coffeelint is invalid {{LINK}}"); validateField(errors, eslintrcFile, true, "Path to config file is invalid {{LINK}}"); //Please correct path to validateField(errors, nodeInterpreterField, false, "Path to node interpreter is invalid {{LINK}}"); if (!validateDirectory(customRulesPathField.getText(), true)) { ValidationInfo error = new ValidationInfo(customRulesPathField, "Path to custom rules is invalid {{LINK}}", FIX_IT); errors.add(error); } if (errors.isEmpty()) { getVersion(); } packagesNotificationPanel.processErrors(errors); } private CoffeeLintRunner.CoffeeLintSettings settings; private void getVersion() { if (settings != null && areEqual(nodeInterpreterField, settings.node) && areEqual(coffeelintBinField, settings.executablePath) && settings.cwd.equals(project.getBasePath()) ) { return; } settings = new CoffeeLintRunner.CoffeeLintSettings(); settings.node = nodeInterpreterField.getChildComponent().getText(); settings.executablePath = coffeelintBinField.getChildComponent().getText(); settings.cwd = project.getBasePath(); try { String version = CoffeeLintRunner.runVersion(settings); versionLabel.setText(version.trim()); } catch (ExecutionException e) { e.printStackTrace(); } } private boolean validatePath(String path, boolean allowEmpty) { if (StringUtils.isEmpty(path)) { return allowEmpty; } File filePath = new File(path); if (filePath.isAbsolute()) { if (!filePath.exists() || !filePath.isFile()) { return false; } } else { VirtualFile child = project.getBaseDir().findFileByRelativePath(path); if (child == null || !child.exists() || child.isDirectory()) { return false; } } return true; } private boolean validateDirectory(String path, boolean allowEmpty) { if (StringUtils.isEmpty(path)) { return allowEmpty; } File filePath = new File(path); if (filePath.isAbsolute()) { if (!filePath.exists() || !filePath.isDirectory()) { return false; } } else { VirtualFile child = project.getBaseDir().findFileByRelativePath(path); if (child == null || !child.exists() || !child.isDirectory()) { return false; } } return true; } private static TextFieldWithHistory configWithDefaults(TextFieldWithHistoryWithBrowseButton field) { TextFieldWithHistory textFieldWithHistory = field.getChildComponent(); textFieldWithHistory.setHistorySize(-1); textFieldWithHistory.setMinimumAndPreferredWidth(0); return textFieldWithHistory; } private void configLintBinField() { configWithDefaults(coffeelintBinField); SwingHelper.addHistoryOnExpansion(coffeelintBinField.getChildComponent(), new NotNullProducer<List<String>>() { @NotNull public List<String> produce() { List<File> newFiles = CoffeeLintFinder.searchForCoffeeLintExe(getProjectPath()); return FileUtils.toAbsolutePath(newFiles); } }); SwingHelper.installFileCompletionAndBrowseDialog(project, coffeelintBinField, "Select CoffeeLint cli", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); } // private void configESLintRulesField() { // TextFieldWithHistory textFieldWithHistory = rulesPathField.getChildComponent(); // SwingHelper.addHistoryOnExpansion(textFieldWithHistory, new NotNullProducer<List<String>>() { // @NotNull // public List<String> produce() { // return CoffeeLintFinder.tryFindRulesAsString(getProjectPath()); // } // }); // SwingHelper.installFileCompletionAndBrowseDialog(project, rulesPathField, "Select Built in rules", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); // } private void configConfigFileField() { TextFieldWithHistory textFieldWithHistory = configWithDefaults(eslintrcFile); SwingHelper.addHistoryOnExpansion(textFieldWithHistory, new NotNullProducer<List<String>>() { @NotNull public List<String> produce() { return CoffeeLintFinder.searchForConfigFiles(getProjectPath()); } }); SwingHelper.installFileCompletionAndBrowseDialog(project, eslintrcFile, "Select CoffeeLint config", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); } private void configNodeField() { TextFieldWithHistory textFieldWithHistory = configWithDefaults(nodeInterpreterField); SwingHelper.addHistoryOnExpansion(textFieldWithHistory, new NotNullProducer<List<String>>() { @NotNull public List<String> produce() { List<File> newFiles = NodeDetectionUtil.listAllPossibleNodeInterpreters(); return FileUtils.toAbsolutePath(newFiles); } }); SwingHelper.installFileCompletionAndBrowseDialog(project, nodeInterpreterField, "Select Node interpreter", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); } @Nls @Override public String getDisplayName() { return "CoffeeLint"; } @Nullable @Override public String getHelpTopic() { return null; } @Nullable @Override public JComponent createComponent() { loadSettings(); return panel; } private static boolean areEqual(TextFieldWithHistoryWithBrowseButton field, String value) { return field.getChildComponent().getText().equals(value); } @Override public boolean isModified() { Settings s = getSettings(); return pluginEnabledCheckbox.isSelected() != s.pluginEnabled || !areEqual(coffeelintBinField, s.lintExecutable) || !areEqual(nodeInterpreterField, s.nodeInterpreter) || // treatAllLintIssuesCheckBox.isSelected() != s.treatAllIssuesAsWarnings || !customRulesPathField.getText().equals(s.rulesPath) || !extensionsField.getText().equals(s.extensions) || // !areEqual(rulesPathField, s.builtinRulesPath) || !getConfigFile().equals(s.configFile); } private String getConfigFile() { return useProjectEslintrcRadioButton.isSelected() ? eslintrcFile.getChildComponent().getText() : ""; } @Override public void apply() throws ConfigurationException { saveSettings(); PsiManager.getInstance(project).dropResolveCaches(); } protected void saveSettings() { Settings settings = getSettings(); settings.pluginEnabled = pluginEnabledCheckbox.isSelected(); settings.lintExecutable = coffeelintBinField.getChildComponent().getText(); settings.nodeInterpreter = nodeInterpreterField.getChildComponent().getText(); settings.configFile = getConfigFile(); settings.rulesPath = customRulesPathField.getText(); settings.extensions = extensionsField.getText(); // settings.builtinRulesPath = rulesPathField.getChildComponent().getText(); // settings.treatAllIssuesAsWarnings = treatAllLintIssuesCheckBox.isSelected(); project.getComponent(CoffeeLintProjectComponent.class).validateSettings(); DaemonCodeAnalyzer.getInstance(project).restart(); } protected void loadSettings() { Settings settings = getSettings(); pluginEnabledCheckbox.setSelected(settings.pluginEnabled); coffeelintBinField.getChildComponent().setText(settings.lintExecutable); eslintrcFile.getChildComponent().setText(settings.configFile); nodeInterpreterField.getChildComponent().setText(settings.nodeInterpreter); customRulesPathField.setText(settings.rulesPath); extensionsField.setText(settings.extensions); // rulesPathField.getChildComponent().setText(settings.builtinRulesPath); useProjectEslintrcRadioButton.setSelected(StringUtils.isNotEmpty(settings.configFile)); searchForEslintrcInRadioButton.setSelected(StringUtils.isEmpty(settings.configFile)); eslintrcFile.setEnabled(useProjectEslintrcRadioButton.isSelected()); // treatAllLintIssuesCheckBox.setSelected(settings.treatAllIssuesAsWarnings); setEnabledState(settings.pluginEnabled); } @Override public void reset() { loadSettings(); } @Override public void disposeUIResources() { } protected Settings getSettings() { return Settings.getInstance(project); } private void createUIComponents() { // TODO: place custom component creation code here usageLink = SwingHelper.createWebHyperlink(HOW_TO_USE_COFFEE_LINT, HOW_TO_USE_LINK); } }