'use strict'; import * as OS from "os"; import { commands, window, workspace, ExtensionContext, Uri, lm } from 'vscode'; import * as commons from '@pivotal-tools/commons-vscode'; import * as liveHoverUi from './live-hover-connect-ui'; import * as rewrite from './rewrite'; import { startDebugSupport } from './debug-config-provider'; import { ApiManager } from "./apiManager"; import { ExtensionAPI } from "./api"; import {registerClasspathService} from "@pivotal-tools/commons-vscode/lib/classpath"; import {registerJavaDataService} from "@pivotal-tools/commons-vscode/lib/java-data"; import * as setLogLevelUi from './set-log-levels-ui'; import { startTestJarSupport } from "./test-jar-launch"; import { startPropertiesConversionSupport } from "./convert-props-yaml"; import { activateCopilotFeatures } from "./copilot"; import * as springBootAgent from './copilot/springBootAgent'; import { applyLspEdit } from "./copilot/guideApply"; import { isLlmApiReady } from "./copilot/util"; import CopilotRequest, { logger } from "./copilot/copilotRequest"; const PROPERTIES_LANGUAGE_ID = "spring-boot-properties"; const YAML_LANGUAGE_ID = "spring-boot-properties-yaml"; const JAVA_LANGUAGE_ID = "java"; const XML_LANGUAGE_ID = "xml"; const FACTORIES_LANGUAGE_ID = "spring-factories"; const JPA_QUERY_PROPERTIES_LANGUAGE_ID = "jpa-query-properties"; const STOP_ASKING = "Stop Asking"; /** Called when extension is activated */ export function activate(context: ExtensionContext): Thenable { // registerPipelineGenerator(context); let options : commons.ActivatorOptions = { DEBUG: false, CONNECT_TO_LS: false, extensionId: 'vscode-spring-boot', preferJdk: true, jvmHeap: '1024m', vmArgs: [ "-Dspring.config.location=classpath:/application.properties" ], checkjvm: (context: ExtensionContext, jvm: commons.JVM) => { let version = jvm.getMajorVersion(); if (version < 17) { throw Error(`Spring Tools Language Server requires Java 17 or higher to be launched. Current Java version is ${version}`); } if (!jvm.isJdk()) { window.showWarningMessage( 'JAVA_HOME or PATH environment variable seems to point to a JRE. A JDK is required, hence Boot Hints are unavailable.', STOP_ASKING).then(selection => { if (selection === STOP_ASKING) { options.workspaceOptions.update('checkJVM', false); } } ); } }, workspaceOptions: workspace.getConfiguration("spring-boot.ls"), clientOptions: { markdown: { isTrusted: true }, uriConverters: { code2Protocol: (uri) => { /* * Workaround for docUri coming from vscode-languageclient on Windows * * It comes in as "file:///c%3A/Users/ab/spring-petclinic/src/main/java/org/springframework/samples/petclinic/owner/PetRepository.java" * * While symbols index would have this uri instead: * - "file:///C:/Users/ab/spring-petclinic/src/main/java/org/springframework/samples/petclinic/owner/PetRepository.java" * * i.e. lower vs upper case drive letter and escaped drive colon */ if (OS.platform() === "win32" && uri.scheme === 'file') { let uriStr = uri.toString(); let idx = 5; // skip through `file: for (; idx < uriStr.length - 1 && uriStr.charAt(idx) === '/'; idx++) {} if (idx < uriStr.length - 1) { // replace c%3A with C: or c: with C: const replaceEscapedColon = idx < uriStr.length - 4 && uriStr.substring(idx + 1, idx + 4) === '%3A'; uriStr = `${uriStr.substring(0, idx)}${uriStr.charAt(idx).toUpperCase()}${replaceEscapedColon ? ':' : ''}${uriStr.substring(idx + (replaceEscapedColon ? 4 : 1))}` } return uriStr; } return uri.toString(); }, protocol2Code: uri => Uri.parse(uri) }, // See PT-158992999 as to why a scheme is added to the document selector // documentSelector: [ PROPERTIES_LANGUAGE_ID, YAML_LANGUAGE_ID, JAVA_LANGUAGE_ID ], documentSelector: [ { language: PROPERTIES_LANGUAGE_ID, scheme: 'file' }, { language: YAML_LANGUAGE_ID, scheme: 'file' }, { language: JAVA_LANGUAGE_ID, scheme: 'file' }, { language: JAVA_LANGUAGE_ID, scheme: 'jdt' }, { language: XML_LANGUAGE_ID, scheme: 'file' }, { language: FACTORIES_LANGUAGE_ID, scheme: 'file' }, { language: JPA_QUERY_PROPERTIES_LANGUAGE_ID, pattern: "**/jpa-named-queries.properties" } ], synchronize: { configurationSection: ['boot-java', 'spring-boot', 'http'] }, initializationOptions: () => ({ workspaceFolders: workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.toString()) : null, // Do not enable JDT classpath listeners at the startup - classpath service would enable it later if needed based on the Java extension mode // Classpath service registration requires commands to be registered and Boot LS needs to register classpath // listeners when client has callbacks for STS4 extension java related messages registered via JDT classpath and Data Service registration enableJdtClasspath: false }) }, highlightCodeLensSettingKey: 'boot-java.highlight-codelens.on' }; // Register launch config contributior to java debug launch to be able to connect to JMX context.subscriptions.push(startDebugSupport()); return commons.activate(options, context).then(client => { commands.registerCommand('vscode-spring-boot.ls.start', () => client.start().then(() => { // Boot LS is fully started registerClasspathService(client); registerJavaDataService(client); activateCopilotFeatures(context); // Force classpath listener to be enabled. Boot LS can only be launched iff classpath is available and there Spring-Boot on the classpath somewhere. commands.executeCommand('sts.vscode-spring-boot.enableClasspathListening', true); // Register TestJars launch support context.subscriptions.push(startTestJarSupport()); })); commands.registerCommand('vscode-spring-boot.ls.stop', () => client.stop()); liveHoverUi.activate(client, options, context); rewrite.activate(client, options, context); setLogLevelUi.activate(client, options, context); startPropertiesConversionSupport(context); if(isLlmApiReady) activateSpringBootParticipant(context); else window.showInformationMessage("Spring Boot chat participant is not available. Please use the vscode insiders version 1.90.0 or above and make sure all `lm` API is enabled."); registerMiscCommands(context); commands.registerCommand('vscode-spring-boot.agent.apply', applyLspEdit); return new ApiManager(client).api; }); } function registerMiscCommands(context: ExtensionContext) { context.subscriptions.push( commands.registerCommand('vscode-spring-boot.spring.modulith.metadata.refresh', async () => { const modulithProjects = await commands.executeCommand('sts/modulith/projects'); const projectNames = Object.keys(modulithProjects); if (projectNames.length === 0) { window.showErrorMessage('No Spring Modulith projects found'); } else { const projectName = projectNames.length === 1 ? projectNames[0] : await window.showQuickPick( projectNames, { placeHolder: "Select the target project." }, ); commands.executeCommand('sts/modulith/metadata/refresh', modulithProjects[projectName]); } }), commands.registerCommand('vscode-spring-boot.open.url', (openUrl) => { const openWithExternalBrowser = workspace.getConfiguration("spring.tools").get("openWith") === "external"; const browserCommand = openWithExternalBrowser ? "vscode.open" : "simpleBrowser.api.open"; return commands.executeCommand(browserCommand, Uri.parse(openUrl)); }), ); } async function activateSpringBootParticipant(context: ExtensionContext) { const model = (await lm.selectChatModels(CopilotRequest.DEFAULT_MODEL_SELECTOR))?.[0]; if (!model) { const models = await lm.selectChatModels(); logger.error(`Not a suitable model. The available models are: [${models.map(m => m.name).join(', ')}]. Please make sure you have installed the latest "GitHub Copilot Chat" (v0.16.0 or later) and all \`lm\` API is enabled.`); return; } springBootAgent.activate(context); }