Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.commands.Misc;
import org.springframework.ide.vscode.boot.java.commands.SpringIndexCommands;
import org.springframework.ide.vscode.boot.java.commands.WorkspaceBootExecutableProjects;
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeCatalogRegistry;
Expand All @@ -31,6 +32,11 @@ public class CommandsConfig {
SpringIndexCommands springIndexCommands(SimpleLanguageServer server, JavaProjectFinder projectFinder,
SpringMetamodelIndex symbolIndex, ModulithService modulithService, StereotypeCatalogRegistry stereotypeCatalogRegistry) {
return new SpringIndexCommands(server, symbolIndex, modulithService, projectFinder, stereotypeCatalogRegistry);
}

}

@Bean
Misc misc(SimpleLanguageServer server) {
return new Misc(server);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.springframework.ide.vscode.boot.java.commands;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
Expand Down Expand Up @@ -97,16 +96,13 @@ public void handleStereotype(Stereotype stereotype, NodeContext context) {
for (Object source : sources) {
if (source instanceof URL) {
URL url = (URL) source;
if (url.getProtocol().equals("jar")) {
reference = convertUrlToJdtUri((URL) source, project.getElementName());
}
else if (url.getProtocol().equals("file")) {
try {
reference = url.toURI().toASCIIString();
}
catch (URISyntaxException e) {
// something went wrong
try {
reference = url.toURI().toASCIIString();
if (reference.startsWith(Misc.JAR_URL_PROTOCOL_PREFIX)) {
reference = reference.replaceFirst(Misc.JAR_URL_PROTOCOL_PREFIX, Misc.BOOT_LS_URL_PRTOCOL_PREFIX);
}
} catch (URISyntaxException e) {
// something went wrong
}
}
else if (source instanceof Location) {
Expand All @@ -123,39 +119,7 @@ else if (source instanceof Location) {
);
}
}

private String convertUrlToJdtUri(URL url, String projectName) {
try {
URI uri = url.toURI();

// Extract the scheme-specific part (everything after "jar:")
String schemeSpecificPart = uri.getSchemeSpecificPart();

// Split on "!/" to separate jar file path from internal path
String[] parts = schemeSpecificPart.split("!/", 2);

if (parts.length != 2) {
return null;
}

String jarFilePath = parts[0];
String internalPath = parts[1];

// Remove "file:" prefix from jar file path if present
if (jarFilePath.startsWith("file:")) {
jarFilePath = jarFilePath.substring(5);
}

String jarFileName = jarFilePath.substring(jarFilePath.lastIndexOf('/') + 1);

// Construct the JDT URI with just the jar file name
return "jdt://contents/" + jarFileName + "/" + internalPath + "?=" + projectName;

} catch (URISyntaxException e) {
return null;
}
}


@Override
public void handleApplication(A application) {
this.root
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright (c) 2025 Broadcom, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.commands;

import java.io.InputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.IOUtil;

import com.google.gson.JsonElement;


public class Misc {

public static final String BOOT_LS_URL_PRTOCOL_PREFIX = "spring-boot-ls:";
public static final String JAR_URL_PROTOCOL_PREFIX = "jar:";

private static final String STS_FETCH_JAR_CONTENT = "sts/jar/fetch-content";

public Misc(SimpleLanguageServer server) {
// Fetch JAR content via a special protocol `spring-boot-ls` as we don't want to handle all JARs in VSCode
server.onCommand(STS_FETCH_JAR_CONTENT, params -> {
return server.getAsync().invoke(() -> {
if (params.getArguments().size() == 1) {
Object o = params.getArguments().get(0);
String s = o instanceof JsonElement ? ((JsonElement) o).getAsString() : o.toString();
if (s.startsWith(BOOT_LS_URL_PRTOCOL_PREFIX)) {
s = s.replaceFirst(BOOT_LS_URL_PRTOCOL_PREFIX, JAR_URL_PROTOCOL_PREFIX);
URI uri = URI.create(URLDecoder.decode(s, StandardCharsets.UTF_8));
// Java has support for JAR URLs
return IOUtil.toString((InputStream) uri.toURL().getContent());
}
}
throw new IllegalArgumentException("The command must have one valid URL parameter.");
});
});
}

}
10 changes: 9 additions & 1 deletion vscode-extensions/vscode-spring-boot/lib/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
ExtensionContext,
Uri,
lm,
TreeItemCollapsibleState
TreeItemCollapsibleState,
TextDocumentContentProvider
} from 'vscode';

import * as commons from '@pivotal-tools/commons-vscode';
Expand Down Expand Up @@ -191,6 +192,13 @@ export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {

context.subscriptions.push(commands.registerCommand('vscode-spring-boot.agent.apply', applyLspEdit));

// Register content loader for URIs of type `spring-boot-ls:...` (load JAR content via Boot LS)
context.subscriptions.push(workspace.registerTextDocumentContentProvider('spring-boot-ls', new (class implements TextDocumentContentProvider {
provideTextDocumentContent(uri: Uri) {
return commands.executeCommand<string>('sts/jar/fetch-content', uri.toString());
}
})()));

const api = new ApiManager(client).api

context.subscriptions.push(api.getSpringIndex().onSpringIndexUpdated(e => structureManager.refresh(false)));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { commands, EventEmitter, Event, ExtensionContext, Disposable, window, TreeItemCollapsibleState, TreeItem, QuickPickItem, QuickPickOptions, Memento, workspace } from "vscode";
import { commands, EventEmitter, Event, ExtensionContext, window, Memento, Uri, QuickPickItem } from "vscode";
import { SpringNode, StereotypedNode } from "./nodes";
import { ExplorerTreeProvider } from "./explorer-tree-provider";

const SPRING_STRUCTURE_CMD = "sts/spring-boot/structure";

Expand All @@ -17,8 +16,7 @@ export class StructureManager {
if (node && node.getReferenceValue) {
const reference = node.getReferenceValue();
if (reference) {
// Reference is a specific URL that should be passed to java.open.file command
commands.executeCommand('java.open.file', reference);
commands.executeCommand('vscode.open', Uri.parse(reference));
}
}
}));
Expand Down