diff --git a/.gitignore b/.gitignore index 2f27b49e..fac87f98 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ Desktop.ini .DS_Store /tools/ /BuildArtifacts/ +.history/ diff --git a/GitReleaseManager.yaml b/GitReleaseManager.yaml index 2c325c05..ff139ac9 100644 --- a/GitReleaseManager.yaml +++ b/GitReleaseManager.yaml @@ -3,7 +3,7 @@ create: footer-heading: Where to get it footer-content: > You can download this release from the - [marketplace](https://plugins.jetbrains.com/plugin/15729-cake-rider/versions/). + [marketplace](https://plugins.jetbrains.com/plugin/15729-cake-rider/versions/), after it has been released. footer-includes-milestone: true milestone-replace-text: "{milestone}" export: diff --git a/README.md b/README.md index ef817fcf..f0d471a2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ typing the following in an elevated command prompt: ## Install Cake for Rider -Go to File -> Settings -> Plugins -> Marketplace and search for *Cake Rider* +Go to File -> Settings -> Plugins -> Marketplace and search for *Cake for Rider* ![Cake Rider Plugin](./images/riderPlugin.png) @@ -29,53 +29,40 @@ however, Cake for Rider might make use of two other channels: To use one of the channels open the [plugin page in the JetBrains Marketplace](https://plugins.jetbrains.com/plugin/15729-cake-rider/) select *Versions* and switch the channel on the left side of the page. Download the desired version and install it manually. -## Build scripts tool window +## Documentation -Cake files in the project are automatically found by extension and their tasks are displayed in a tool window: +The full documentation of the latest release of Cake for Rider can be found at [https://cakebuild.net/](https://cakebuild.net/docs/integrations/editors/rider/) -![Cake tasks tool window](./images/toolWindow.png) +### Documentation changes in the upcoming release -Here, a double click on the task will run the task immediately: +#### File icons -![Running a Cake task](./images/cake-run.png) +Files with the extension `.cake` have the Cake logo as file icons now. -Alternatively, the buttons at the top of the tool window can be used to either run the task directly, -or create a new run configuration. +#### Custom arguments in run configurations -## Run configurations: +A new setting has been added to run configuration: *Arguments*. It can be used to supply custom arguments when running that configuration. -It is possible to have Cake tasks as run configurations: +![arguments](./images/runConfiguration-editor.png) -![Run configurations](./images/runConfigurations.png) +#### Search path and excludes -The configurations can either be created from an existing Cake task, using the tool window or -created manually using the run configuration editor: +The search path for where Cake files are searched to populate the Cake Tasks Window are now +configurable. Additionally excludes can be defined. -![Run configuration editor](./images/runConfiguration-editor.png) +The Paths are relative to the project root and should use "/" as separator between directories. +(e.g.`../build`.) Default is `.`. -## Settings +Excludes are regular expressions that each path is match against. Paths that match one of the expressions will be excluded. +Default: `.*/tools/.*`. -There are multiple configuration settings available under File -> Settings -> Build, Execution, Deployment -> Cake. +![Search paths settings](./images/searchPathsSettings.png) -All settings are project - specific and stored in the `.idea` folder. To share settings between project members, make sure to include `CakeRider.xml` in your source control. +## Discussion -### Generic settings +If you have questions, search for an existing one, or create a new discussion on the Cake GitHub repository. -* *Cake file extension* - This setting is used to find all Cake files and display them in the tool window. - Default: `cake` -* *Task Regex* - This regex is used to parse tasks from the Cake files. - Default: `Task\s*?\(\s*?"(.*?)"\s*?\)` -* *Verbosity* - This is the default verbosity to use, when running a task directly from the tool window or when creating a new run configuration. - Default: `normal` - -### Runner settings - -This window contains the runner to use when starting Cake. Additionally, a set of overrides can be added: Here, a regular expression is matched against the system property `os.name` and, if it matches, the runner is overridden. - -Default value is `dotnet-cake` and default override is `dotnet-cake.exe` for the regex `^.*windows.*$`. This default requires the [Cake .NET tool](https://cakebuild.net/docs/running-builds/runners/dotnet-tool) being globally installed. +[![Join in the discussion on the Cake repository](https://img.shields.io/badge/GitHub-Discussions-green?logo=github)](https://github.com/cake-build/cake/discussions) ## Contribute diff --git a/images/cake-run.png b/images/cake-run.png deleted file mode 100644 index e3e1ef66..00000000 Binary files a/images/cake-run.png and /dev/null differ diff --git a/images/runConfiguration-editor.png b/images/runConfiguration-editor.png index 3409836a..6b13af6a 100644 Binary files a/images/runConfiguration-editor.png and b/images/runConfiguration-editor.png differ diff --git a/images/runConfigurations.png b/images/runConfigurations.png deleted file mode 100644 index bda8a19a..00000000 Binary files a/images/runConfigurations.png and /dev/null differ diff --git a/images/searchPathsSettings.png b/images/searchPathsSettings.png new file mode 100644 index 00000000..fe3109e1 Binary files /dev/null and b/images/searchPathsSettings.png differ diff --git a/images/toolWindow.png b/images/toolWindow.png deleted file mode 100644 index 647da52e..00000000 Binary files a/images/toolWindow.png and /dev/null differ diff --git a/rider/build.gradle.kts b/rider/build.gradle.kts index 0c3df9f9..4b9cb21c 100644 --- a/rider/build.gradle.kts +++ b/rider/build.gradle.kts @@ -1,4 +1,5 @@ import io.gitlab.arturbosch.detekt.Detekt +import org.gradle.internal.os.OperatingSystem import org.jetbrains.changelog.closure import org.jetbrains.changelog.markdownToHTML import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -11,7 +12,7 @@ plugins { // gradle-intellij-plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin id("org.jetbrains.intellij") version "0.6.5" // gradle-changelog-plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin - id("org.jetbrains.changelog") version "0.6.2" + id("org.jetbrains.changelog") version "1.0.1" // detekt linter - read more: https://detekt.github.io/detekt/gradle.html id("io.gitlab.arturbosch.detekt") version "1.15.0" // ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle @@ -83,10 +84,26 @@ tasks { kotlinOptions.jvmTarget = "1.8" } } - withType { jvmTarget = "1.8" } + // workaround for https://youtrack.jetbrains.com/issue/IDEA-210683 + getByName("buildSearchableOptions") { + jvmArgs( + "--illegal-access=deny", + "--add-opens=java.desktop/sun.awt=ALL-UNNAMED", + "--add-opens=java.desktop/java.awt=ALL-UNNAMED", + "--add-opens=java.base/java.lang=ALL-UNNAMED", + "--add-opens=java.desktop/javax.swing=ALL-UNNAMED", + "--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED", + "--add-opens=java.desktop/sun.font=ALL-UNNAMED", + "--add-opens=java.desktop/sun.swing=ALL-UNNAMED" + ) + + if (OperatingSystem.current() == OperatingSystem.MAC_OS) { + jvmArgs("--add-opens=java.desktop/com.apple.eawt.event=ALL-UNNAMED") + } + } patchPluginXml { version(pluginVersion) @@ -125,6 +142,7 @@ tasks { runPluginVerifier { ideVersions(pluginVerifierIdeVersions) + // reports are in ${project.buildDir}/reports/pluginVerifier - or set verificationReportsDirectory() } publishPlugin { diff --git a/rider/detekt-config.yml b/rider/detekt-config.yml index d73d78a2..0bbce3d3 100644 --- a/rider/detekt-config.yml +++ b/rider/detekt-config.yml @@ -9,3 +9,5 @@ formatting: style: ReturnCount: max: 10 + LoopWithTooManyJumpStatements: + maxJumpCount: 5 \ No newline at end of file diff --git a/rider/gradle.properties b/rider/gradle.properties index 88395cd0..077233ff 100644 --- a/rider/gradle.properties +++ b/rider/gradle.properties @@ -8,7 +8,7 @@ pluginSinceBuild = 193 pluginUntilBuild = 203.* # Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl # See https://jb.gg/intellij-platform-builds-list for available build versions -pluginVerifierIdeVersions = 2019.3.5, 2020.1.4, 2020.2.3, 2020.3 +pluginVerifierIdeVersions = RD-2019.3.4, RD-2020.1.4, RD-2020.2.3, RD-2020.3.2 platformType = RD platformVersion = 2019.3 diff --git a/rider/src/main/java/net/cakebuild/run/CakeConfigurationEditor.form b/rider/src/main/java/net/cakebuild/run/CakeConfigurationEditor.form index 04dde4d3..4e039bb4 100644 --- a/rider/src/main/java/net/cakebuild/run/CakeConfigurationEditor.form +++ b/rider/src/main/java/net/cakebuild/run/CakeConfigurationEditor.form @@ -1,6 +1,6 @@
- + @@ -56,9 +56,25 @@ - + + + + + + + + + + + + + + + + + diff --git a/rider/src/main/java/net/cakebuild/run/CakeConfigurationEditor.java b/rider/src/main/java/net/cakebuild/run/CakeConfigurationEditor.java index bd891e00..fd0d642e 100644 --- a/rider/src/main/java/net/cakebuild/run/CakeConfigurationEditor.java +++ b/rider/src/main/java/net/cakebuild/run/CakeConfigurationEditor.java @@ -2,6 +2,7 @@ import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SettingsEditor; +import com.intellij.ui.components.fields.ExpandableTextField; import net.cakebuild.shared.ui.VerbosityComboBox; import org.jetbrains.annotations.NotNull; @@ -15,6 +16,7 @@ public class CakeConfigurationEditor extends SettingsEditor { private JTextField scriptPathField; private JTextField taskField; private JComboBox verbosityBox; + private JTextField argumentsField; @Override protected void resetEditorFrom(@NotNull CakeConfiguration configuration) { @@ -25,18 +27,20 @@ protected void resetEditorFrom(@NotNull CakeConfiguration configuration) { scriptPathField.setText(state.getScriptPath()); taskField.setText(state.getTaskName()); ((VerbosityComboBox)verbosityBox).setVerbosity(state.getVerbosity()); + argumentsField.setText((state.getAdditionalArguments())); } @Override protected void applyEditorTo(@NotNull CakeConfiguration configuration) throws ConfigurationException { CakeConfigurationOptions state = configuration.getState(); if(state == null) { - throw new ConfigurationException("state is null: Can not set state."); + throw new ConfigurationException("State is null: can not set state."); } state.setScriptPath(scriptPathField.getText()); state.setTaskName(taskField.getText()); state.setVerbosity(((VerbosityComboBox)verbosityBox).getVerbosity()); + state.setAdditionalArguments(argumentsField.getText()); } @NotNull @@ -47,5 +51,6 @@ protected JComponent createEditor() { private void createUIComponents() { verbosityBox = new VerbosityComboBox(); + argumentsField = new ExpandableTextField(); } } diff --git a/rider/src/main/java/net/cakebuild/settings/CakeSearchPathSettingsEditor.form b/rider/src/main/java/net/cakebuild/settings/CakeSearchPathSettingsEditor.form new file mode 100644 index 00000000..7ee4a996 --- /dev/null +++ b/rider/src/main/java/net/cakebuild/settings/CakeSearchPathSettingsEditor.form @@ -0,0 +1,57 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/rider/src/main/java/net/cakebuild/settings/CakeSearchPathSettingsEditor.java b/rider/src/main/java/net/cakebuild/settings/CakeSearchPathSettingsEditor.java new file mode 100644 index 00000000..4de2f178 --- /dev/null +++ b/rider/src/main/java/net/cakebuild/settings/CakeSearchPathSettingsEditor.java @@ -0,0 +1,46 @@ +package net.cakebuild.settings; + +import net.cakebuild.settings.controls.SimpleAddEditControl; +import net.cakebuild.shared.ui.RegexCellEditor; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.util.Collection; + +public class CakeSearchPathSettingsEditor { + private JPanel content; + private JPanel searchPathsPanel; + private JPanel excludesPanel; + private SimpleAddEditControl mySearchPathsPanel; + private SimpleAddEditControl myExcludesPanel; + + public JPanel getContent() { return content; } + + public void setScriptSearchPaths(@NotNull Collection paths){ + mySearchPathsPanel.setData(paths); + } + + public Collection getScriptSearchPaths() { + return mySearchPathsPanel.getData(); + } + + public void setScriptSearchIgnores(@NotNull Collection expressions) { + myExcludesPanel.setData(expressions); + } + + public Collection getScriptSearchIgnores() { + return myExcludesPanel.getData(); + } + + private void createUIComponents() { + mySearchPathsPanel = new SimpleAddEditControl(new String[] { "Path" }, () -> new String[] { "" } ); + searchPathsPanel = mySearchPathsPanel.getContent(); + + myExcludesPanel = new SimpleAddEditControl(new String[] { "Exclude Regex" }, () -> new String[] { "" }); + excludesPanel = myExcludesPanel.getContent(); + RegexCellEditor regexCellEditor = new RegexCellEditor(); + regexCellEditor.setOnValidationError(s -> { myExcludesPanel.setValidationError(s); return null; }); + regexCellEditor.setOnValidationSuccess(() -> { myExcludesPanel.setValidationError(null); return null; }); + myExcludesPanel.setCellEditor(0, regexCellEditor); + } +} diff --git a/rider/src/main/java/net/cakebuild/settings/controls/SimpleAddEditControl.form b/rider/src/main/java/net/cakebuild/settings/controls/SimpleAddEditControl.form new file mode 100644 index 00000000..784f0f53 --- /dev/null +++ b/rider/src/main/java/net/cakebuild/settings/controls/SimpleAddEditControl.form @@ -0,0 +1,67 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/rider/src/main/java/net/cakebuild/settings/controls/SimpleAddEditControl.java b/rider/src/main/java/net/cakebuild/settings/controls/SimpleAddEditControl.java new file mode 100644 index 00000000..47a5a418 --- /dev/null +++ b/rider/src/main/java/net/cakebuild/settings/controls/SimpleAddEditControl.java @@ -0,0 +1,90 @@ +package net.cakebuild.settings.controls; + +import javax.swing.*; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableColumnModel; +import java.awt.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.function.Supplier; + +public class SimpleAddEditControl { + private JTable table; + private JButton addItem; + private JButton removeItem; + private JPanel panel; + private JLabel errorText; + private final DefaultTableModel model; + + public SimpleAddEditControl(String[] headers, Supplier newItemGenerator){ + model = (DefaultTableModel) table.getModel(); + for(int i = 0; i< headers.length; i++){ + model.addColumn("col"+i); + } + TableColumnModel columnModel = table.getColumnModel(); + for(int i = 0; i< headers.length; i++){ + columnModel.getColumn(i).setHeaderValue(headers[i]); + } + table.putClientProperty("terminateEditOnFocusLost", true); + table.setCellSelectionEnabled(false); + table.setRowSelectionAllowed(true); + table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + table.getSelectionModel().addListSelectionListener(e -> { + int row = table.getSelectedRow(); + removeItem.setEnabled(row >= 0); + }); + addItem.addActionListener(e -> { + model.addRow(newItemGenerator.get()); + int row = model.getRowCount()-1; + table.editCellAt(row, 0); + table.changeSelection(row, 0, false, false); + TableCellEditor editor = table.getCellEditor(row, 0); + Component textEdit = table.prepareEditor(editor, row, 0); + textEdit.requestFocus(); + }); + removeItem.setEnabled(false); + removeItem.addActionListener(e -> { + int row = table.getSelectedRow(); + if(row < 0) { + return; + } + + model.removeRow(row); + }); + } + + public void setCellEditor(int columnIndex, TableCellEditor cellEditor){ + table.getColumnModel().getColumn(columnIndex).setCellEditor(cellEditor); + } + + public Collection getData() { + Collection items = new HashSet<>(); + int rows = model.getRowCount(); + for(int i =0; i < rows; i++) { + String item = (String)model.getValueAt(i, 0); + + items.add(item); + } + + return items; + } + + public void setData(Collection data) { + while (model.getRowCount() > 0){ + model.removeRow(0); + } + data.forEach(it -> model.addRow(new Object[]{ it })); + } + + public void setValidationError(String error) { + if(error == null) { + errorText.setText(""); + return; + } + + errorText.setText(error); + } + + public JPanel getContent() { return panel; } +} diff --git a/rider/src/main/kotlin/icons/CakeIcons.kt b/rider/src/main/kotlin/icons/CakeIcons.kt index 22661208..c0de673e 100644 --- a/rider/src/main/kotlin/icons/CakeIcons.kt +++ b/rider/src/main/kotlin/icons/CakeIcons.kt @@ -3,5 +3,9 @@ package icons import com.intellij.openapi.util.IconLoader object CakeIcons { - val Cake = IconLoader.getIcon("/icons/CakeIcon.png") + @JvmField + val CakeAction = IconLoader.getIcon("/icons/CakeIcon16.svg") + + @JvmField + val CakeFileType = IconLoader.getIcon("/icons/CakeIcon16.svg") } diff --git a/rider/src/main/kotlin/net/cakebuild/fileTypes/CakeFileType.kt b/rider/src/main/kotlin/net/cakebuild/fileTypes/CakeFileType.kt new file mode 100644 index 00000000..40dac0ff --- /dev/null +++ b/rider/src/main/kotlin/net/cakebuild/fileTypes/CakeFileType.kt @@ -0,0 +1,30 @@ +package net.cakebuild.fileTypes + +import com.intellij.openapi.fileTypes.LanguageFileType +import com.intellij.openapi.fileTypes.ex.FileTypeIdentifiableByVirtualFile +import com.intellij.openapi.project.ProjectLocator +import com.intellij.openapi.vfs.VirtualFile +import icons.CakeIcons +import net.cakebuild.settings.CakeSettings + +class CakeFileType : LanguageFileType(CakeLanguage), FileTypeIdentifiableByVirtualFile { + override fun getName() = CakeLanguage.displayName + override fun getDescription() = "Cake scripts" + override fun getIcon() = CakeIcons.CakeFileType + override fun getDefaultExtension() = "cake" + + override fun isMyFileType(file: VirtualFile): Boolean { + val extension = file.extension?.toLowerCase() ?: "" + + if (extension.equals(defaultExtension, true)) { + return true + } + + val projects = ProjectLocator.getInstance().getProjectsForFile(file) + val extensions = projects.map { + CakeSettings.getInstance(it).cakeFileExtension.toLowerCase() + }.distinct() + + return extensions.contains(extension.toLowerCase()) + } +} diff --git a/rider/src/main/kotlin/net/cakebuild/fileTypes/CakeLanguage.kt b/rider/src/main/kotlin/net/cakebuild/fileTypes/CakeLanguage.kt new file mode 100644 index 00000000..87de23ce --- /dev/null +++ b/rider/src/main/kotlin/net/cakebuild/fileTypes/CakeLanguage.kt @@ -0,0 +1,7 @@ +package net.cakebuild.fileTypes + +import com.intellij.lang.Language + +object CakeLanguage : Language("Cake") { + override fun isCaseSensitive() = true +} diff --git a/rider/src/main/kotlin/net/cakebuild/run/CakeConfiguration.kt b/rider/src/main/kotlin/net/cakebuild/run/CakeConfiguration.kt index 7bfff6e9..092d23d6 100644 --- a/rider/src/main/kotlin/net/cakebuild/run/CakeConfiguration.kt +++ b/rider/src/main/kotlin/net/cakebuild/run/CakeConfiguration.kt @@ -13,6 +13,7 @@ import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.project.Project +import com.intellij.util.execution.ParametersListUtil import net.cakebuild.settings.CakeSettings import java.nio.file.FileSystems @@ -49,15 +50,18 @@ class CakeConfiguration(project: Project, factory: CakeConfigurationFactory) : val scriptPath = fileSystems .getPath(project.basePath!!) .resolve(fileSystems.getPath(options.scriptPath!!)) + val arguments = mutableListOf() + arguments.add(scriptPath.toString()) + arguments.add("--target=${options.taskName}") + arguments.add("--verbosity=${options.verbosity}") + if (!options.additionalArguments.isNullOrEmpty()) { + arguments.addAll(ParametersListUtil.parseToArray(options.additionalArguments!!)) + } val commandLine = GeneralCommandLine() .withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE) .withWorkDirectory(scriptPath.parent.toString()) .withExePath(exe) - .withParameters( - scriptPath.toString(), - "--target=${options.taskName}", - "--verbosity=${options.verbosity}" - ) + .withParameters(arguments) val processHandler = ProcessHandlerFactory.getInstance().createColoredProcessHandler(commandLine) ProcessTerminatedListener.attach(processHandler) return processHandler diff --git a/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationFactory.kt b/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationFactory.kt index 2b67af2e..423bc821 100644 --- a/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationFactory.kt +++ b/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationFactory.kt @@ -2,6 +2,7 @@ package net.cakebuild.run import com.intellij.execution.BeforeRunTask import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.execution.configurations.RunConfigurationSingletonPolicy import com.intellij.openapi.components.BaseState import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key @@ -11,7 +12,7 @@ import com.jetbrains.rider.build.tasks.BuildProjectBeforeRunTaskProvider class CakeConfigurationFactory(cakeConfigurationType: CakeConfigurationType) : ConfigurationFactory(cakeConfigurationType) { - override fun isConfigurationSingletonByDefault() = true + override fun getSingletonPolicy() = RunConfigurationSingletonPolicy.SINGLE_INSTANCE override fun createTemplateConfiguration(project: Project): CakeConfiguration { return CakeConfiguration(project, this) diff --git a/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationOptions.kt b/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationOptions.kt index 10f2d90e..635c54c3 100644 --- a/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationOptions.kt +++ b/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationOptions.kt @@ -11,7 +11,7 @@ class CakeConfigurationOptions : RunConfigurationOptions() { set(v) { storedTaskName.setValue(this, v) } private val storedScriptPath: StoredProperty = - string("build.cake").provideDelegate(this, "file") + string("build.cake").provideDelegate(this, "scriptPath") var scriptPath: String? get() = storedScriptPath.getValue(this) set(v) { storedScriptPath.setValue(this, v) } @@ -21,4 +21,10 @@ class CakeConfigurationOptions : RunConfigurationOptions() { var verbosity: String? get() = storedVerbosity.getValue(this) set(v) { storedVerbosity.setValue(this, v) } + + private val storedAdditionalArguments: StoredProperty = + string("").provideDelegate(this, "additionalArguments") + var additionalArguments: String? + get() = storedAdditionalArguments.getValue(this) + set(v) { storedAdditionalArguments.setValue(this, v) } } diff --git a/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationType.kt b/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationType.kt index 495df5e6..a46dafb7 100644 --- a/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationType.kt +++ b/rider/src/main/kotlin/net/cakebuild/run/CakeConfigurationType.kt @@ -4,7 +4,7 @@ import com.intellij.execution.configurations.ConfigurationTypeBase import icons.CakeIcons class CakeConfigurationType : - ConfigurationTypeBase("CAKE_CONFIGURATION", "Cake", "Cake", CakeIcons.Cake) { + ConfigurationTypeBase("CAKE_CONFIGURATION", "Cake", "Cake", CakeIcons.CakeAction) { val cakeFactory = CakeConfigurationFactory(this) diff --git a/rider/src/main/kotlin/net/cakebuild/settings/CakeSearchPathSettingsConfigurable.kt b/rider/src/main/kotlin/net/cakebuild/settings/CakeSearchPathSettingsConfigurable.kt new file mode 100644 index 00000000..b4180c4d --- /dev/null +++ b/rider/src/main/kotlin/net/cakebuild/settings/CakeSearchPathSettingsConfigurable.kt @@ -0,0 +1,49 @@ +package net.cakebuild.settings + +import com.intellij.openapi.options.Configurable +import com.intellij.openapi.project.Project +import javax.swing.JComponent + +class CakeSearchPathSettingsConfigurable(private val project: Project) : Configurable { + private val editor = CakeSearchPathSettingsEditor() + + override fun createComponent(): JComponent? { + return editor.content + } + + override fun getDisplayName(): String { + return "Search Paths" + } + + override fun apply() { + val settings = CakeSettings.getInstance(project) + settings.cakeScriptSearchPaths = editor.scriptSearchPaths.toList() + settings.cakeScriptSearchIgnores = editor.scriptSearchIgnores.toList() + } + + override fun reset() { + val settings = CakeSettings.getInstance(project) + editor.scriptSearchPaths = settings.cakeScriptSearchPaths.toMutableList() + editor.scriptSearchIgnores = settings.cakeScriptSearchIgnores.toMutableList() + } + + override fun isModified(): Boolean { + val settings = CakeSettings.getInstance(project) + return isModified(editor.scriptSearchPaths, settings.cakeScriptSearchPaths) || + isModified(editor.scriptSearchIgnores, settings.cakeScriptSearchIgnores) + } + + private fun isModified(left: Collection, right: Collection): Boolean { + if (left.size != right.size) { + return true + } + + left.forEach { + if (!right.contains(it)) { + return true + } + } + + return false + } +} diff --git a/rider/src/main/kotlin/net/cakebuild/settings/CakeSettings.kt b/rider/src/main/kotlin/net/cakebuild/settings/CakeSettings.kt index 603bf817..e9d2b0d4 100644 --- a/rider/src/main/kotlin/net/cakebuild/settings/CakeSettings.kt +++ b/rider/src/main/kotlin/net/cakebuild/settings/CakeSettings.kt @@ -24,6 +24,8 @@ class CakeSettings : PersistentStateComponent { var cakeVerbosity = "normal" var cakeRunner = "dotnet-cake" var cakeRunnerOverrides = mapOf(Pair("^.*windows.*$", "dotnet-cake.exe")) + var cakeScriptSearchPaths: Collection = mutableListOf(".") + var cakeScriptSearchIgnores: Collection = mutableListOf(".*/tools/.*") override fun getState(): CakeSettings { return this diff --git a/rider/src/main/kotlin/net/cakebuild/shared/CakeBalloonNotifications.kt b/rider/src/main/kotlin/net/cakebuild/shared/CakeBalloonNotifications.kt index deca1bf0..cb97c12b 100644 --- a/rider/src/main/kotlin/net/cakebuild/shared/CakeBalloonNotifications.kt +++ b/rider/src/main/kotlin/net/cakebuild/shared/CakeBalloonNotifications.kt @@ -6,7 +6,9 @@ import com.intellij.notification.NotificationType import com.intellij.openapi.project.Project object CakeBalloonNotifications { - // after 2020.03 this can be done in pluxin.xml, see https://jetbrains.org/intellij/sdk/docs/user_interface_components/notifications.html + // this will raise a deprecation notice. Sadly that's unavoidable until + // we drop support for all versions < 2020.3. + // after 2020.3 this should be done in pluxin.xml, see https://jetbrains.org/intellij/sdk/docs/user_interface_components/notifications.html private val notificationGroup = NotificationGroup("Cake", NotificationDisplayType.BALLOON, true) diff --git a/rider/src/main/kotlin/net/cakebuild/shared/CakeProject.kt b/rider/src/main/kotlin/net/cakebuild/shared/CakeProject.kt index 9969d852..c1dcc5b1 100644 --- a/rider/src/main/kotlin/net/cakebuild/shared/CakeProject.kt +++ b/rider/src/main/kotlin/net/cakebuild/shared/CakeProject.kt @@ -8,8 +8,11 @@ import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir +import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile +import com.jetbrains.rider.projectView.hasSolution +import com.jetbrains.rider.projectView.solutionDirectory import net.cakebuild.run.CakeConfiguration import net.cakebuild.run.CakeConfigurationType import net.cakebuild.settings.CakeSettings @@ -20,18 +23,70 @@ class CakeProject(private val project: Project) { private val log = Logger.getInstance(CakeProject::class.java) + private fun getProjectDir(): VirtualFile? { + var projectDir = project.guessProjectDir() + if (project.hasSolution) { + // projectDir is weird, if a solution is loaded (probably because + // guessProjectDir is intellij-code and does not know about rider and solutions) + projectDir = LocalFileSystem.getInstance().findFileByIoFile(project.solutionDirectory) + } + return projectDir + } + + private fun getSearchPaths(settings: CakeSettings, projectDir: VirtualFile): Collection { + return settings.cakeScriptSearchPaths.mapNotNull { + // VfsUtil.findRelativeFile does not work for "./foo" or "../foo" + val parts = it.split("/", "\\") + var path: VirtualFile? = projectDir + for (p in parts) { + if (path == null) { + break + } + if (p == ".") { + continue + } + if (p == "..") { + path = path.parent + continue + } + + val child = path.findChild(p) + path = if (child == null) { + log.warn("could not access $p as child of ${path.path}") + null + } else { + child + } + } + path + } + } + fun getCakeFiles() = sequence { - val extension = CakeSettings.getInstance(project).cakeFileExtension - val projectDir: VirtualFile? = project.guessProjectDir() - val bucket = Stack() + val settings = CakeSettings.getInstance(project) + val extension = settings.cakeFileExtension + val projectDir = getProjectDir() if (projectDir == null) { + log.warn("Unable to find a folder to search for cake files.") return@sequence } - bucket.add(projectDir) + val searchPaths = getSearchPaths(settings, projectDir) + val bucket = Stack() + bucket.addAll(searchPaths) + val excludePatterns = settings.cakeScriptSearchIgnores.map { + Regex(it) + } while (!bucket.isEmpty()) { val folder = bucket.pop() log.trace("searching for *.$extension in folder ${folder.path}") - for (child in folder.children) { + children@ for (child in folder.children) { + val normalizedPath = child.path.replace("\\", "/") + for (exclude in excludePatterns) { + if (normalizedPath.matches(exclude)) { + log.trace("$normalizedPath excluded by pattern ${exclude.pattern}") + continue@children + } + } if (child.isDirectory) { bucket.push(child) continue @@ -59,11 +114,6 @@ class CakeProject(private val project: Project) { data class CakeTask(private val project: Project, val file: VirtualFile, val taskName: String) { - // to make the task look good when placed in a tree-node. - override fun toString(): String { - return taskName - } - fun run(mode: CakeTaskRunMode) { val runManager = project.getService(RunManager::class.java) val configurationType = ConfigurationTypeUtil.findConfigurationType(CakeConfigurationType::class.java) @@ -80,7 +130,11 @@ class CakeProject(private val project: Project) { CakeTaskRunMode.Debug -> DefaultDebugExecutor.getDebugExecutorInstance() CakeTaskRunMode.Run -> DefaultRunExecutor.getRunExecutorInstance() else -> { - runManager.addConfiguration(runConfiguration, true) + // this line will cause a deprecation warning. + // when we drop support for versions < 2020.1 + // we can call the new runConfiguration.storeInDotIdeaFolder() + runConfiguration.isShared = true + runManager.addConfiguration(runConfiguration) runManager.selectedConfiguration = runConfiguration null } diff --git a/rider/src/main/kotlin/net/cakebuild/toolwindow/CakeTasksWindow.kt b/rider/src/main/kotlin/net/cakebuild/toolwindow/CakeTasksWindow.kt index 3e97a75c..7f4da680 100644 --- a/rider/src/main/kotlin/net/cakebuild/toolwindow/CakeTasksWindow.kt +++ b/rider/src/main/kotlin/net/cakebuild/toolwindow/CakeTasksWindow.kt @@ -8,9 +8,15 @@ import com.intellij.ui.ScrollPaneFactory import com.intellij.ui.treeStructure.Tree import net.cakebuild.shared.CakeDataKeys import net.cakebuild.shared.CakeProject +import java.awt.Component import java.awt.event.MouseAdapter import java.awt.event.MouseEvent +import javax.swing.JComponent +import javax.swing.JLabel +import javax.swing.JTree +import javax.swing.ToolTipManager import javax.swing.tree.DefaultMutableTreeNode +import javax.swing.tree.DefaultTreeCellRenderer import javax.swing.tree.DefaultTreeModel import javax.swing.tree.TreeNode import javax.swing.tree.TreePath @@ -23,6 +29,8 @@ class CakeTasksWindow(private val project: Project) : SimpleToolWindowPanel(true init { val scrollPane = ScrollPaneFactory.createScrollPane(tree) setContent(scrollPane) + ToolTipManager.sharedInstance().registerComponent(tree) + tree.cellRenderer = MyTreeCellRenderer() tree.selectionModel.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION refreshTree() initToolbar() @@ -84,7 +92,10 @@ class CakeTasksWindow(private val project: Project) : SimpleToolWindowPanel(true private fun getSelectedTask(): CakeProject.CakeTask? { val selected = tree.getSelectedNodes(DefaultMutableTreeNode::class.java) { it.isLeaf }.firstOrNull() ?: return null - return selected.userObject as CakeProject.CakeTask + return when (selected.userObject) { + is CakeProject.CakeTask -> selected.userObject as CakeProject.CakeTask + else -> null + } } fun isTaskSelected(): Boolean { @@ -106,7 +117,7 @@ class CakeTasksWindow(private val project: Project) : SimpleToolWindowPanel(true val cakeProject = CakeProject(project) for (cakeFile in cakeProject.getCakeFiles()) { - val fileNode = DefaultMutableTreeNode(cakeFile.file.name) + val fileNode = DefaultMutableTreeNode(cakeFile) rootNode.add(fileNode) for (task in cakeFile.getTasks()) { @@ -122,4 +133,42 @@ class CakeTasksWindow(private val project: Project) : SimpleToolWindowPanel(true if (CakeDataKeys.TASKS_WINDOW.`is`(dataId)) return this return super.getData(dataId) } + + class MyTreeCellRenderer : DefaultTreeCellRenderer() { + + init { + setClosedIcon(null) + setOpenIcon(null) + setLeafIcon(null) + } + + override fun getTreeCellRendererComponent( + tree: JTree, + value: Any, + sel: Boolean, + expanded: Boolean, + leaf: Boolean, + row: Int, + hasFocus: Boolean + ): Component { + val cell: Component = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus) + if (cell is JComponent) { + val label = cell as JLabel + cell.toolTipText = null + when (val data = (value as DefaultMutableTreeNode).userObject) { + is CakeProject.CakeTask -> { + label.text = data.taskName + } + is CakeProject.CakeFile -> { + cell.toolTipText = data.file.path + label.text = data.file.nameWithoutExtension + } + else -> { + label.text = data.toString() + } + } + } + return cell + } + } } diff --git a/rider/src/main/resources/META-INF/plugin.xml b/rider/src/main/resources/META-INF/plugin.xml index 7184d8c8..5610f82c 100644 --- a/rider/src/main/resources/META-INF/plugin.xml +++ b/rider/src/main/resources/META-INF/plugin.xml @@ -9,10 +9,13 @@ com.intellij.modules.rider + - + + diff --git a/rider/src/main/resources/icons/CakeIcon.png b/rider/src/main/resources/icons/CakeIcon.png deleted file mode 100644 index 00a76006..00000000 Binary files a/rider/src/main/resources/icons/CakeIcon.png and /dev/null differ diff --git a/rider/src/main/resources/icons/CakeIcon13.svg b/rider/src/main/resources/icons/CakeIcon13.svg new file mode 100644 index 00000000..720c9189 --- /dev/null +++ b/rider/src/main/resources/icons/CakeIcon13.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rider/src/main/resources/icons/CakeIcon16.svg b/rider/src/main/resources/icons/CakeIcon16.svg new file mode 100644 index 00000000..cb5d5099 --- /dev/null +++ b/rider/src/main/resources/icons/CakeIcon16.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rider/src/main/resources/icons/CakeIcon@2.png b/rider/src/main/resources/icons/CakeIcon@2.png deleted file mode 100644 index b6d13627..00000000 Binary files a/rider/src/main/resources/icons/CakeIcon@2.png and /dev/null differ