diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index bd742fe61..9234cc526 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -7,7 +7,8 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/628[#638]: Fixed update fails on first error -* https://github.com/devonfw/IDEasy/issues/553[#554]: Missmatch of IDE_ROOT +* https://github.com/devonfw/IDEasy/issues/37[#37]: Added Visual Studio Code (vscode) with plugin installation and plugin recommendation support +* https://github.com/devonfw/IDEasy/issues/553[#553]: Mismatch of IDE_ROOT * https://github.com/devonfw/IDEasy/issues/556[#556]: ProcessContext should compute PATH on run and not in constructor * https://github.com/devonfw/IDEasy/issues/557[#557]: Failed to update tomcat: Cannot find a (Map) Key deserializer for type VersionRange * https://github.com/devonfw/IDEasy/issues/623[#623]: CliArgument prepand and append methods inconsistent diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/PluginBasedCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/PluginBasedCommandlet.java index 51017a444..121ccb8f2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/PluginBasedCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/PluginBasedCommandlet.java @@ -2,6 +2,7 @@ import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -151,19 +152,35 @@ protected boolean doInstall(boolean silent) { } else if (!Files.isDirectory(pluginsInstallationPath)) { installPlugins = true; } + createExtensionFolder(); if (installPlugins) { PluginMaps pluginMaps = getPluginsMap(); - for (PluginDescriptor plugin : pluginMaps.getPlugins()) { - if (plugin.isActive()) { - installPlugin(plugin); - } else { - handleInstall4InactivePlugin(plugin); - } - } + Collection plugins = pluginMaps.getPlugins(); + installPlugins(plugins); } return newlyInstalled; } + private void createExtensionFolder() { + Path extensionFolder = this.context.getIdeHome().resolve("plugins").resolve(this.tool); + this.context.getFileAccess().mkdirs(extensionFolder); + } + + /** + * Method to install active plugins or to handle install for inactive plugins + * + * @param plugins as {@link Collection} of plugins to install. + */ + protected void installPlugins(Collection plugins) { + for (PluginDescriptor plugin : plugins) { + if (plugin.isActive()) { + installPlugin(plugin); + } else { + handleInstall4InactivePlugin(plugin); + } + } + } + /** * @param plugin the in{@link PluginDescriptor#isActive() active} {@link PluginDescriptor} that is skipped for regular plugin installation. */ diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java b/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java index 563a6586a..32c75413e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/vscode/Vscode.java @@ -1,16 +1,33 @@ package com.devonfw.tools.ide.tool.vscode; +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; -import com.devonfw.tools.ide.tool.LocalToolCommandlet; +import com.devonfw.tools.ide.process.ProcessContext; +import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet; +import com.devonfw.tools.ide.tool.plugin.PluginDescriptor; +import com.devonfw.tools.ide.version.VersionIdentifier; +import com.fasterxml.jackson.databind.ObjectMapper; /** * {@link ToolCommandlet} for vscode. */ -public class Vscode extends LocalToolCommandlet { +public class Vscode extends IdeToolCommandlet { /** * The constructor. @@ -28,4 +45,102 @@ protected String getBinaryName() { return "code"; } + @Override + public void installPlugin(PluginDescriptor plugin) { + + } + + @Override + protected void installPlugins(Collection plugins) { + + List pluginsToInstall = new ArrayList<>(); + List pluginsToRecommend = new ArrayList<>(); + + for (PluginDescriptor plugin : plugins) { + if (plugin.isActive()) { + pluginsToInstall.add(plugin); + } else { + pluginsToRecommend.add(plugin); + } + } + doAddRecommendations(pluginsToRecommend); + doInstallPlugins(pluginsToInstall); + + } + + private void doInstallPlugins(List pluginsToInstall) { + + List extensionsCommands = new ArrayList<>(); + + if (pluginsToInstall.isEmpty()) { + this.context.info("No plugins to be installed"); + } else { + + for (PluginDescriptor plugin : pluginsToInstall) { + extensionsCommands.add("--install-extension"); + extensionsCommands.add(plugin.getId()); + } + } + runTool(ProcessMode.DEFAULT, null, extensionsCommands.toArray(new String[0])); + } + + private void doAddRecommendations(List recommendations) { + Path extensionsJsonPath = this.context.getWorkspacePath().resolve(".vscode/extensions.json"); + + ObjectMapper objectMapper = new ObjectMapper(); + Map recommendationsMap; + + if (Files.exists(extensionsJsonPath)) { + try (BufferedReader reader = Files.newBufferedReader(extensionsJsonPath)) { + recommendationsMap = objectMapper.readValue(reader, Map.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + recommendationsMap = new HashMap<>(); + } + + List existingRecommendations = (List) recommendationsMap.getOrDefault("recommendations", new ArrayList<>()); + + try { + int addedRecommendations = 0; + Set existingRecommendationsSet = new HashSet<>(existingRecommendations); + for (PluginDescriptor recommendation : recommendations) { + String recommendationId = recommendation.getId(); + if (existingRecommendationsSet.add(recommendationId)) { + existingRecommendations.add(recommendationId); + addedRecommendations++; + } + } + + if (addedRecommendations > 0) { + objectMapper.writeValue(extensionsJsonPath.toFile(), recommendationsMap); + } + + } catch (IOException e) { + this.context.error(e); + } + } + + @Override + public void runTool(ProcessMode processMode, VersionIdentifier toolVersion, String... args) { + + install(true); + + Path vsCodeConf = this.context.getWorkspacePath().resolve(".vscode/.userdata"); + Path vsCodeExtensionFolder = this.context.getIdeHome().resolve("plugins/vscode"); + + List command = new ArrayList<>(); + command.add("--new-window"); + command.add("--user-data-dir=" + vsCodeConf); + command.add("--extensions-dir=" + vsCodeExtensionFolder); + + command.addAll(Arrays.asList(args)); + + Path binaryPath; + binaryPath = Path.of(getBinaryName()); + ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.THROW).executable(binaryPath).addArgs(command.toArray()); + pc.run(processMode); + } + } diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/vscode/VscodeTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/vscode/VscodeTest.java new file mode 100644 index 000000000..d0cbff671 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/vscode/VscodeTest.java @@ -0,0 +1,45 @@ +package com.devonfw.tools.ide.tool.vscode; + +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeTestContext; + +/** + * Test of {@link Vscode} class. + */ +public class VscodeTest extends AbstractIdeContextTest { + + private static final String PROJECT_VSCODE = "vscode"; + + + @Test + public void testVscodeInstall() { + + // arrange + IdeTestContext context = newContext(PROJECT_VSCODE); + Vscode vscodeCommandlet = new Vscode(context); + + // install + vscodeCommandlet.install(); + + // assert + checkInstallation(context); + } + + private void checkInstallation(IdeTestContext context) { + + assertThat(context.getSoftwarePath().resolve("vscode/bin/code.cmd")).exists().hasContent("@echo test for windows"); + assertThat(context.getSoftwarePath().resolve("vscode/bin/code")).exists().hasContent("#!/bin/bash\n" + "echo \"Test for linux and Mac\""); + + assertThat(context.getSoftwarePath().resolve("vscode/.ide.software.version")).exists().hasContent("1.92.1"); + assertThat(context).logAtSuccess().hasMessage("Successfully installed vscode in version 1.92.1"); + + //check plugins folder + assertThat(context.getIdeHome().resolve("plugins").resolve("vscode")).exists(); + + //check Recommendations + assertThat(context.getWorkspacePath().resolve(".vscode").resolve("extensions.json")).exists() + .hasContent("{\"recommendations\":[\"esbenp.prettier-vscode\",\"mockedPlugin2\"]}"); + } +} diff --git a/cli/src/test/resources/ide-projects/vscode/project/settings/ide.properties b/cli/src/test/resources/ide-projects/vscode/project/settings/ide.properties new file mode 100644 index 000000000..fc5fc9522 --- /dev/null +++ b/cli/src/test/resources/ide-projects/vscode/project/settings/ide.properties @@ -0,0 +1 @@ +VSCODE_VERSION=1.92.1 diff --git a/cli/src/test/resources/ide-projects/vscode/project/settings/vscode/plugins/mocedPlugin2.properties b/cli/src/test/resources/ide-projects/vscode/project/settings/vscode/plugins/mocedPlugin2.properties new file mode 100644 index 000000000..128be815c --- /dev/null +++ b/cli/src/test/resources/ide-projects/vscode/project/settings/vscode/plugins/mocedPlugin2.properties @@ -0,0 +1,3 @@ +plugin_id=mockedPlugin2 +plugin_active=false +tags=mocking diff --git a/cli/src/test/resources/ide-projects/vscode/project/settings/vscode/plugins/mockedPlugin.properties b/cli/src/test/resources/ide-projects/vscode/project/settings/vscode/plugins/mockedPlugin.properties new file mode 100644 index 000000000..b3a05455f --- /dev/null +++ b/cli/src/test/resources/ide-projects/vscode/project/settings/vscode/plugins/mockedPlugin.properties @@ -0,0 +1,3 @@ +plugin_id=mockedPlugin +plugin_active=true +tags=mocking diff --git a/cli/src/test/resources/ide-projects/vscode/project/workspaces/main/.vscode/extensions.json b/cli/src/test/resources/ide-projects/vscode/project/workspaces/main/.vscode/extensions.json new file mode 100644 index 000000000..26454ac66 --- /dev/null +++ b/cli/src/test/resources/ide-projects/vscode/project/workspaces/main/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode" + ] +} diff --git a/cli/src/test/resources/ide-projects/vscode/repository/vscode/vscode/default/bin/code b/cli/src/test/resources/ide-projects/vscode/repository/vscode/vscode/default/bin/code new file mode 100644 index 000000000..73d55c222 --- /dev/null +++ b/cli/src/test/resources/ide-projects/vscode/repository/vscode/vscode/default/bin/code @@ -0,0 +1,2 @@ +#!/bin/bash +echo "Test for linux and Mac" \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/vscode/repository/vscode/vscode/default/bin/code.cmd b/cli/src/test/resources/ide-projects/vscode/repository/vscode/vscode/default/bin/code.cmd new file mode 100644 index 000000000..5fc84a864 --- /dev/null +++ b/cli/src/test/resources/ide-projects/vscode/repository/vscode/vscode/default/bin/code.cmd @@ -0,0 +1 @@ +@echo test for windows \ No newline at end of file