diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java
index e4719bace..1c2d20794 100644
--- a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java
@@ -15,11 +15,13 @@
import com.intellij.openapi.startup.StartupActivity;
import com.redhat.devtools.intellij.lsp4mp4ij.classpath.ClasspathResourceChangedManager;
import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project.PsiMicroProfileProjectManager;
+import com.redhat.devtools.intellij.quarkus.run.QuarkusRunConfigurationManager;
import org.jetbrains.annotations.NotNull;
public class QuarkusPostStartupActivity implements StartupActivity, DumbAware {
@Override
public void runActivity(@NotNull Project project) {
+ QuarkusRunConfigurationManager.getInstance(project);
ClasspathResourceChangedManager.getInstance(project);
// Force the instantiation of the manager to be sure that classpath listener
// are registered before QuarkusLanguageClient classpath listener
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunConfigurationManager.java b/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunConfigurationManager.java
new file mode 100644
index 000000000..6d889e6be
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunConfigurationManager.java
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package com.redhat.devtools.intellij.quarkus.run;
+
+import com.intellij.ProjectTopics;
+import com.intellij.execution.RunManager;
+import com.intellij.execution.RunnerAndConfigurationSettings;
+import com.intellij.execution.dashboard.RunDashboardManager;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectListener;
+import com.intellij.openapi.externalSystem.service.project.ProjectDataManager;
+import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener;
+import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
+import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.project.ModuleListener;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.concurrency.AppExecutorUtil;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.messages.MessageBusConnection;
+import com.redhat.devtools.intellij.quarkus.settings.UserDefinedQuarkusSettings;
+import com.redhat.microprofile.psi.quarkus.PsiQuarkusUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.idea.maven.MavenDisposable;
+import org.jetbrains.idea.maven.project.MavenImportListener;
+import org.jetbrains.idea.maven.project.MavenProject;
+import org.jetbrains.idea.maven.project.MavenProjectsManager;
+import org.jetbrains.plugins.gradle.util.GradleConstants;
+
+import java.util.*;
+
+/**
+ * Quarkus run configuration manager used to:
+ *
+ *
+ * - auto create Quarkus run configuration when a Quarkus Maven/Gradle is imported
+ * - provides methods to find existing/ create Quarkus run configuration from a given module
+ *
+ */
+public class QuarkusRunConfigurationManager implements Disposable {
+
+ public static QuarkusRunConfigurationManager getInstance(Project project) {
+ return ServiceManager.getService(project, QuarkusRunConfigurationManager.class);
+ }
+
+ private final Project project;
+ private final MessageBusConnection projectConnection;
+
+ public QuarkusRunConfigurationManager(Project project) {
+ this.project = project;
+ projectConnection = initializeAutoCreateRunConfigurationWhileImporting(project);
+ }
+
+ public @Nullable RunnerAndConfigurationSettings findExistingConfigurationFor(@NotNull Module module) {
+ List configurations = RunManager.getInstance(project).getConfigurationSettingsList(QuarkusRunConfigurationType.class);
+ if (!configurations.isEmpty()) {
+ for (RunnerAndConfigurationSettings settings : configurations) {
+ QuarkusRunConfiguration configuration = (QuarkusRunConfiguration) settings.getConfiguration();
+ if (module.equals(configuration.getModule())) {
+ return settings;
+ }
+ }
+ }
+ return null;
+ }
+
+ public @NotNull RunnerAndConfigurationSettings createConfiguration(@NotNull Module module, boolean save) {
+ var runManager = RunManager.getInstance(module.getProject());
+ RunnerAndConfigurationSettings quarkusSettings = runManager.createConfiguration(generateConfigurationName(module, runManager), QuarkusRunConfigurationType.class);
+ ((QuarkusRunConfiguration) quarkusSettings.getConfiguration()).setModule(module);
+ if (save) {
+ quarkusSettings.storeInLocalWorkspace();
+ // Save the configuration
+ runManager.addConfiguration(quarkusSettings);
+ }
+ return quarkusSettings;
+ }
+
+ @NotNull
+ private static String generateConfigurationName(@NotNull Module module, RunManager runManager) {
+ String configurationName = module.getName();
+ RunnerAndConfigurationSettings existingConfiguration = runManager.findConfigurationByTypeAndName(QuarkusRunConfigurationType.ID, configurationName);
+ int index = 0;
+ while (existingConfiguration != null) {
+ configurationName = module.getName() + " (" + index++ + ")";
+ existingConfiguration = runManager.findConfigurationByTypeAndName(QuarkusRunConfigurationType.ID, configurationName);
+ }
+ return configurationName;
+ }
+
+ @NotNull
+ private MessageBusConnection initializeAutoCreateRunConfigurationWhileImporting(Project project) {
+ MessageBusConnection projectConnection = project.getMessageBus().connect(MavenDisposable.getInstance(project));
+ autoCreateWithMaven(project, projectConnection);
+ autoCreateWithGradle(project, projectConnection);
+ return projectConnection;
+ }
+
+ private void autoCreateWithMaven(Project project, MessageBusConnection projectConnection) {
+ projectConnection.subscribe(MavenImportListener.TOPIC, new MavenImportListener() {
+ @Override
+ public void importFinished(@NotNull Collection importedProjects, @NotNull List newModules) {
+ if (!UserDefinedQuarkusSettings.getInstance(project).isCreateQuarkusRunConfigurationOnProjectImport()) {
+ return;
+ }
+
+ VirtualFile projectDir = ProjectUtil.guessProjectDir(project);
+ assert projectDir != null;
+ VirtualFile[] children = projectDir.getChildren();
+ boolean isGradleProject = ContainerUtil.exists(children, file -> GradleConstants.KNOWN_GRADLE_FILES.contains(file.getName()));
+
+ List modules = new ArrayList<>();
+ for (MavenProject mavenProject : importedProjects) {
+ MavenProjectsManager projectsManager = MavenProjectsManager.getInstance(project);
+ Module module = projectsManager.findModule(mavenProject);
+ if (module != null) {
+ modules.add(module);
+ }
+ }
+ tryToCreateRunConfigurations(modules);
+ }
+ });
+ }
+
+ private void autoCreateWithGradle(Project project, MessageBusConnection projectConnection) {
+ projectConnection.subscribe(ProjectDataImportListener.TOPIC, new ProjectDataImportListener() {
+
+ @Override
+ public void onImportFinished(@Nullable String projectPath) {
+ if (!UserDefinedQuarkusSettings.getInstance(project).isCreateQuarkusRunConfigurationOnProjectImport()) {
+ return;
+ }
+ List modules = new ArrayList<>();
+ Module[] existingModules = ModuleManager.getInstance(project).getModules();
+ for (Module module : existingModules) {
+ // Check if the module is a Gradle project
+ if (ExternalSystemApiUtil.isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module)) {
+ modules.add(module);
+ }
+ }
+ tryToCreateRunConfigurations(modules);
+ }
+ });
+ }
+
+ private void tryToCreateRunConfigurations(List modules) {
+ if (modules.isEmpty()) {
+ return;
+ }
+ ReadAction.nonBlocking(() -> doTryToCreateRunConfigurations(modules))
+ .inSmartMode(project)
+ .submit(AppExecutorUtil.getAppExecutorService());
+ }
+
+ private void doTryToCreateRunConfigurations(List modules) {
+ boolean runConfigurationCreated = false;
+ for (Module module : modules) {
+ if (tryToCreateRunConfiguration(module)) {
+ runConfigurationCreated = true;
+ }
+ }
+ if (runConfigurationCreated) {
+ addQuarkusRunConfigurationTypeInServicesViewIfNeeded();
+ }
+ }
+
+ private boolean tryToCreateRunConfiguration(Module module) {
+ if (!PsiQuarkusUtils.isQuarkusProject(module)) {
+ return false;
+ }
+ // Find existing Quarkus run configuration
+ RunnerAndConfigurationSettings quarkusSettings = findExistingConfigurationFor(module);
+ if (quarkusSettings == null) {
+ // No Quarkus run configuration for the module, create it and save it in the .idea/workspace.xml file
+ createConfiguration(module, true);
+ return true;
+ }
+ return false;
+ }
+
+ private void addQuarkusRunConfigurationTypeInServicesViewIfNeeded() {
+ RunDashboardManager runDashboardManager = RunDashboardManager.getInstance(project);
+ Set types = new HashSet<>(runDashboardManager.getTypes());
+ if (!types.contains(QuarkusRunConfigurationType.ID)) {
+ types.add(QuarkusRunConfigurationType.ID);
+ runDashboardManager.setTypes(types);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ projectConnection.disconnect();
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunConfigurationType.java b/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunConfigurationType.java
index 843f5a4f0..d011b42e3 100644
--- a/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunConfigurationType.java
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunConfigurationType.java
@@ -19,7 +19,7 @@
import javax.swing.Icon;
public class QuarkusRunConfigurationType implements ConfigurationType {
- private static final String ID = "QuarkusRunConfiguration";
+ public static final String ID = "QuarkusRunConfiguration";
@NotNull
@Override
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/settings/QuarkusConfigurable.java b/src/main/java/com/redhat/devtools/intellij/quarkus/settings/QuarkusConfigurable.java
new file mode 100644
index 000000000..369e54167
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/settings/QuarkusConfigurable.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package com.redhat.devtools.intellij.quarkus.settings;
+
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.NamedConfigurable;
+import com.intellij.openapi.util.NlsContexts;
+import com.redhat.devtools.intellij.quarkus.QuarkusBundle;
+
+import javax.swing.*;
+
+/**
+ * Quarkus configuration.
+ */
+public class QuarkusConfigurable extends NamedConfigurable {
+
+ private final Project project;
+ private QuarkusView myView;
+
+ public QuarkusConfigurable(Project project) {
+ this.project = project;
+ }
+
+ @Override
+ public UserDefinedQuarkusSettings getEditableObject() {
+ return null;
+ }
+
+ @Override
+ public @NlsContexts.DetailedDescription String getBannerSlogan() {
+ return null;
+ }
+
+ @Override
+ public JComponent createOptionsPanel() {
+ if (myView == null) {
+ myView = new QuarkusView();
+ }
+ return myView.getComponent();
+ }
+
+ @Override
+ public void setDisplayName(String name) {
+ }
+
+ @Override
+ public @NlsContexts.ConfigurableName String getDisplayName() {
+ return QuarkusBundle.message("quarkus");
+ }
+
+
+ @Override
+ public void reset() {
+ if (myView == null) return;
+ UserDefinedQuarkusSettings settings = UserDefinedQuarkusSettings.getInstance(project);
+ myView.setCreateQuarkusRunConfigurationOnProjectImport(settings.isCreateQuarkusRunConfigurationOnProjectImport());
+ }
+
+ @Override
+ public boolean isModified() {
+ if (myView == null) return false;
+ UserDefinedQuarkusSettings settings = UserDefinedQuarkusSettings.getInstance(project);
+ return !(myView.isCreateQuarkusRunConfigurationOnProjectImport() == settings.isCreateQuarkusRunConfigurationOnProjectImport());
+ }
+
+ @Override
+ public void apply() throws ConfigurationException {
+ if (myView == null) return;
+ UserDefinedQuarkusSettings settings = UserDefinedQuarkusSettings.getInstance(project);
+ settings.setCreateQuarkusRunConfigurationOnProjectImport(myView.isCreateQuarkusRunConfigurationOnProjectImport());
+ settings.fireStateChanged();
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/settings/QuarkusView.java b/src/main/java/com/redhat/devtools/intellij/quarkus/settings/QuarkusView.java
new file mode 100644
index 000000000..9517ec3dc
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/settings/QuarkusView.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package com.redhat.devtools.intellij.quarkus.settings;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.ui.IdeBorderFactory;
+import com.intellij.ui.components.JBCheckBox;
+import com.intellij.util.ui.FormBuilder;
+import com.intellij.util.ui.JBUI;
+import com.intellij.util.ui.UI;
+import com.redhat.devtools.intellij.quarkus.QuarkusBundle;
+
+import javax.swing.*;
+import javax.swing.border.TitledBorder;
+
+/**
+ * Quarkus view.
+ */
+public class QuarkusView implements Disposable {
+
+ private final JPanel myMainPanel;
+
+ private JBCheckBox createQuarkusRunConfigurationOnProjectImportCheckBox = new JBCheckBox(QuarkusBundle.message("quarkus.create.quarkus.run.configuration.on.project.import"));
+
+ public QuarkusView() {
+ JComponent descriptionPanel = createDescription(null);
+ JPanel settingsPanel = createSettings(descriptionPanel);
+ TitledBorder title = IdeBorderFactory.createTitledBorder(QuarkusBundle.message("quarkus.title"));
+ settingsPanel.setBorder(title);
+ this.myMainPanel = JBUI.Panels.simplePanel(10, 10)
+ .addToLeft(JBUI.Panels.simplePanel())
+ .addToCenter(settingsPanel);
+ }
+
+ private JPanel createSettings(JComponent description) {
+ return FormBuilder.createFormBuilder()
+ .addComponent(description, 0)
+ .addComponent(createQuarkusRunConfigurationOnProjectImportCheckBox, 5)
+ .addComponentFillVertically(new JPanel(), 0)
+ .getPanel();
+ }
+
+ private JComponent createDescription(String description) {
+ /**
+ * Normally comments are below the controls.
+ * Here we want the comments to precede the controls, we therefore create an empty, 0-sized panel.
+ */
+ JPanel titledComponent = UI.PanelFactory.grid().createPanel();
+ titledComponent.setMinimumSize(JBUI.emptySize());
+ titledComponent.setPreferredSize(JBUI.emptySize());
+ if (description != null && !description.isBlank()) {
+ titledComponent = UI.PanelFactory.panel(titledComponent)
+ .withComment(description)
+ .resizeX(true)
+ .resizeY(true)
+ .createPanel();
+ }
+ return titledComponent;
+ }
+
+ public JComponent getComponent() {
+ return myMainPanel;
+ }
+
+
+ public boolean isCreateQuarkusRunConfigurationOnProjectImport() {
+ return createQuarkusRunConfigurationOnProjectImportCheckBox.isSelected();
+ }
+
+ public void setCreateQuarkusRunConfigurationOnProjectImport(boolean createQuarkusRunConfigurationOnProjectImport) {
+ createQuarkusRunConfigurationOnProjectImportCheckBox.setSelected(createQuarkusRunConfigurationOnProjectImport);
+ }
+
+ @Override
+ public void dispose() {
+
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/settings/UserDefinedQuarkusSettings.java b/src/main/java/com/redhat/devtools/intellij/quarkus/settings/UserDefinedQuarkusSettings.java
new file mode 100644
index 000000000..019c6ffc1
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/settings/UserDefinedQuarkusSettings.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package com.redhat.devtools.intellij.quarkus.settings;
+
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.xmlb.annotations.Tag;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * User defined Quarkus settings for:
+ *
+ *
+ * - auto create Quarkus run configuration
+ *
+ */
+@State(
+ name = "QuarkusSettingsState",
+ storages = {@Storage("quarkusSettings.xml")}
+)
+public class UserDefinedQuarkusSettings implements PersistentStateComponent {
+
+ private volatile MyState myState = new MyState();
+
+ private final Project project;
+
+ public UserDefinedQuarkusSettings(Project project) {
+ this.project = project;
+ }
+
+ private final List myChangeHandlers = ContainerUtil.createConcurrentList();
+
+ public static @NotNull UserDefinedQuarkusSettings getInstance(@NotNull Project project) {
+ return project.getService(UserDefinedQuarkusSettings.class);
+ }
+
+ public void addChangeHandler(Runnable runnable) {
+ myChangeHandlers.add(runnable);
+ }
+
+ public void removeChangeHandler(Runnable runnable) {
+ myChangeHandlers.remove(runnable);
+ }
+
+ public void fireStateChanged() {
+ for (Runnable handler : myChangeHandlers) {
+ handler.run();
+ }
+ }
+ public boolean isCreateQuarkusRunConfigurationOnProjectImport() {
+ return myState.myCreateQuarkusRunConfigurationOnProjectImport;
+ }
+
+ public void setCreateQuarkusRunConfigurationOnProjectImport(boolean createQuarkusRunConfigurationOnProjectImport) {
+ myState.myCreateQuarkusRunConfigurationOnProjectImport = createQuarkusRunConfigurationOnProjectImport;
+ }
+
+ @Nullable
+ @Override
+ public MyState getState() {
+ return myState;
+ }
+
+ @Override
+ public void loadState(@NotNull MyState state) {
+ myState = state;
+ for (Runnable handler : myChangeHandlers) {
+ handler.run();
+ }
+ }
+
+ public static class MyState {
+
+ @Tag("createQuarkusRunConfigurationOnProjectImport")
+ public boolean myCreateQuarkusRunConfigurationOnProjectImport = true;
+
+ MyState() {
+ }
+
+ }
+
+}
diff --git a/src/main/resources/META-INF/lsp4ij-quarkus.xml b/src/main/resources/META-INF/lsp4ij-quarkus.xml
index a2892bf07..0bf0fe817 100644
--- a/src/main/resources/META-INF/lsp4ij-quarkus.xml
+++ b/src/main/resources/META-INF/lsp4ij-quarkus.xml
@@ -47,6 +47,15 @@
+
+
+
+
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index d62a9349d..ae4b81bbf 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -338,6 +338,7 @@
+
diff --git a/src/main/resources/messages/QuarkusBundle.properties b/src/main/resources/messages/QuarkusBundle.properties
index 0aa090119..e6b477648 100644
--- a/src/main/resources/messages/QuarkusBundle.properties
+++ b/src/main/resources/messages/QuarkusBundle.properties
@@ -11,6 +11,11 @@
# Red Hat Inc. - initial API and implementation
###############################################################################
+## Quarkus UI settings page
+quarkus=Quarkus
+quarkus.title=Quarkus configuration
+quarkus.create.quarkus.run.configuration.on.project.import=Create "Quarkus Dev Mode" run configuration on project import
+
## Quarkus explorer
quarkus.tool.window.display.name=Projects
@@ -22,4 +27,4 @@ quarkus.wizard.loading.streams=Loading Quarkus platform streams...
quarkus.wizard.error.extensions.loading=Failed to load Quarkus extensions
quarkus.wizard.error.extensions.loading.message=Failed to load extensions for Quarkus {0}:\n{1}
-quarkus.wizard.loading.extensions=Loading Quarkus extensions...
\ No newline at end of file
+quarkus.wizard.loading.extensions=Loading Quarkus extensions...