From 46df6707119d221c813a219c08d0ce2698d90397 Mon Sep 17 00:00:00 2001 From: azerr Date: Thu, 9 Nov 2023 16:46:25 +0100 Subject: [PATCH] feat: Auto create Quarkus run config while importing project Fixes #1233 Signed-off-by: azerr --- .../quarkus/QuarkusPostStartupActivity.java | 2 + .../run/QuarkusRunConfigurationManager.java | 142 ++++++++++++++++++ .../run/QuarkusRunConfigurationType.java | 2 +- .../quarkus/settings/QuarkusConfigurable.java | 86 +++++++++++ .../quarkus/settings/QuarkusView.java | 89 +++++++++++ .../settings/UserDefinedQuarkusSettings.java | 99 ++++++++++++ .../resources/META-INF/lsp4ij-quarkus.xml | 9 ++ src/main/resources/META-INF/plugin.xml | 1 + .../messages/QuarkusBundle.properties | 7 +- 9 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunConfigurationManager.java create mode 100644 src/main/java/com/redhat/devtools/intellij/quarkus/settings/QuarkusConfigurable.java create mode 100644 src/main/java/com/redhat/devtools/intellij/quarkus/settings/QuarkusView.java create mode 100644 src/main/java/com/redhat/devtools/intellij/quarkus/settings/UserDefinedQuarkusSettings.java 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..406c15d9a --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunConfigurationManager.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * 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.execution.RunManager; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +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 java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Quarkus run configuration manager used to: + * + * + */ +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).isCreateQuarkusRunConfigurationWhileProjectImport()) { + return; + } + for (MavenProject mavenProject : importedProjects) { + MavenProjectsManager projectsManager = MavenProjectsManager.getInstance(project); + Module module = projectsManager.findModule(mavenProject); + if (module != null) { + tryToCreateRunConfiguration(module); + } + } + } + }); + } + + private void autoCreateWithGradle(Project project, MessageBusConnection projectConnection) { + // TODO : track the Gradle import projects + } + + private void tryToCreateRunConfiguration(Module module) { + if (!ReadAction.compute(()-> PsiQuarkusUtils.isQuarkusProject(module))) { + return; + } + // 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); + } + } + + @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..ca4ec70a7 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/settings/QuarkusConfigurable.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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.lsp4mp4ij.settings.UserDefinedMicroProfileSettings; +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.setCreateQuarkusRunConfigurationWhileProjectImport(settings.isCreateQuarkusRunConfigurationWhileProjectImport()); + } + + @Override + public boolean isModified() { + if (myView == null) return false; + UserDefinedQuarkusSettings settings = UserDefinedQuarkusSettings.getInstance(project); + return !(myView.isCreateQuarkusRunConfigurationWhileProjectImport() == settings.isCreateQuarkusRunConfigurationWhileProjectImport()); + } + + @Override + public void apply() throws ConfigurationException { + if (myView == null) return; + UserDefinedQuarkusSettings settings = UserDefinedQuarkusSettings.getInstance(project); + settings.setCreateQuarkusRunConfigurationWhileImport(myView.isCreateQuarkusRunConfigurationWhileProjectImport()); + 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..fb4da1bc1 --- /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 createQuarkusRunConfigurationWhileProjectImportCheckBox = new JBCheckBox(QuarkusBundle.message("quarkus.create.quarkus.run.configuration.while.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(createQuarkusRunConfigurationWhileProjectImportCheckBox, 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 isCreateQuarkusRunConfigurationWhileProjectImport() { + return createQuarkusRunConfigurationWhileProjectImportCheckBox.isSelected(); + } + + public void setCreateQuarkusRunConfigurationWhileProjectImport(boolean createQuarkusRunConfigurationWhileProjectImport) { + createQuarkusRunConfigurationWhileProjectImportCheckBox.setSelected(createQuarkusRunConfigurationWhileProjectImport); + } + + @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..3dcc9abfc --- /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 isCreateQuarkusRunConfigurationWhileProjectImport() { + return myState.myCreateQuarkusRunConfigurationWhileProjectImport; + } + + public void setCreateQuarkusRunConfigurationWhileImport(boolean createQuarkusRunConfigurationWhileImport) { + myState.myCreateQuarkusRunConfigurationWhileProjectImport = createQuarkusRunConfigurationWhileImport; + } + + @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("createQuarkusRunConfigurationWhileProjectImport") + public boolean myCreateQuarkusRunConfigurationWhileProjectImport = 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..590696895 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.while.project.import=Create Quarkus run configuration while 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...