Skip to content

Commit

Permalink
Add support for .class files
Browse files Browse the repository at this point in the history
Signed-off-by: Jessica He <[email protected]>
  • Loading branch information
JessicaJHee committed Mar 15, 2023
1 parent de77dc8 commit 80f9e86
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 27 deletions.
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@
],
"main": "./dist/extension",
"contributes": {
"customEditors": [
{
"viewType": "decompiled.javaClass",
"displayName": "Decompiled Java Class File",
"selector": [
{
"scheme": "file",
"filenamePattern": "*.class"
}
]
}
],
"javaBuildFilePatterns": [
"^pom\\.xml$",
".*\\.gradle(\\.kts)?$"
Expand Down
40 changes: 23 additions & 17 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ export namespace Commands {
*/
export const EXECUTE_WORKSPACE_COMMAND = 'java.execute.workspaceCommand';

/**
* Execute Workspace build (compilation)
*/
/**
* Execute Workspace build (compilation)
*/
export const COMPILE_WORKSPACE = 'java.workspace.compile';

/**
Expand Down Expand Up @@ -119,18 +119,18 @@ export namespace Commands {
*/
export const OPEN_FORMATTER = 'java.open.formatter.settings';

/**
* Open a file given the URI
*/
export const OPEN_FILE = 'java.open.file';
/**
* Open a file given the URI
*/
export const OPEN_FILE = 'java.open.file';

/**
* Clean the Java language server workspace
*/
export const CLEAN_WORKSPACE = 'java.clean.workspace';
/**
* Update the source attachment for the selected class file
* client-side & server-side commands
* client-side & server-side commands
*/
export const UPDATE_SOURCE_ATTACHMENT_CMD = 'java.project.updateSourceAttachment.command';
export const UPDATE_SOURCE_ATTACHMENT = 'java.project.updateSourceAttachment';
Expand All @@ -140,25 +140,25 @@ export namespace Commands {
export const RESOLVE_SOURCE_ATTACHMENT = 'java.project.resolveSourceAttachment';
/**
* Mark the folder as the source root of the closest project.
* client-side & server-side commands
* client-side & server-side commands
*/
export const ADD_TO_SOURCEPATH_CMD = 'java.project.addToSourcePath.command';
export const ADD_TO_SOURCEPATH = 'java.project.addToSourcePath';
/**
* Unmark the folder as the source root of the project.
* client-side & server-side commands
* client-side & server-side commands
*/
export const REMOVE_FROM_SOURCEPATH_CMD = 'java.project.removeFromSourcePath.command';
export const REMOVE_FROM_SOURCEPATH = 'java.project.removeFromSourcePath';
/**
* List all recognized source roots in the workspace.
* client-side & server-side commands
* client-side & server-side commands
*/
export const LIST_SOURCEPATHS_CMD = 'java.project.listSourcePaths.command';
export const LIST_SOURCEPATHS = 'java.project.listSourcePaths';
/**
* Import new projects
* client-side & server-side commands
* client-side & server-side commands
*/
export const IMPORT_PROJECTS_CMD = 'java.project.import.command';
export const IMPORT_PROJECTS = 'java.project.import';
Expand All @@ -170,7 +170,7 @@ export namespace Commands {
* Generate hashCode() and equals().
*/
export const HASHCODE_EQUALS_PROMPT = 'java.action.hashCodeEqualsPrompt';
/**
/**
* Open settings.json
*/
export const OPEN_JSON_SETTINGS = 'workbench.action.openSettingsJson';
Expand All @@ -182,10 +182,10 @@ export namespace Commands {
* Organize imports silently.
*/
export const ORGANIZE_IMPORTS_SILENTLY = "java.edit.organizeImports";
/**
* Handle a paste event.
*/
export const HANDLE_PASTE_EVENT = "java.edit.handlePasteEvent";
/**
* Handle a paste event.
*/
export const HANDLE_PASTE_EVENT = "java.edit.handlePasteEvent";
/**
* Custom paste action (triggers auto-import)
*/
Expand Down Expand Up @@ -320,4 +320,10 @@ export namespace Commands {
* Clean everything in the shared index directory.
*/
export const CLEAN_SHARED_INDEXES = "java.clean.sharedIndexes";

/**
* Get the uri of the decompiled class file.
*/
export const GET_DECOMPILED_SOURCE = "java.decompile";

}
12 changes: 7 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { runtimeStatusBarProvider } from './runtimeStatusBarProvider';
import { serverStatusBarProvider } from './serverStatusBarProvider';
import { ACTIVE_BUILD_TOOL_STATE, cleanWorkspaceFileName, getJavaServerMode, handleTextBlockClosing, onConfigurationChange, ServerMode } from './settings';
import { snippetCompletionProvider } from './snippetCompletionProvider';
import { javaClassEditorProvider } from './javaClassEditor';
import { StandardLanguageClient } from './standardLanguageClient';
import { SyntaxLanguageClient } from './syntaxLanguageClient';
import { convertToGlob, deleteDirectory, ensureExists, getBuildFilePatterns, getExclusionBlob, getInclusionPatternsFromNegatedExclusion, getJavaConfig, getJavaConfiguration, hasBuildToolConflicts } from './utils';
Expand Down Expand Up @@ -191,7 +192,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
range: client.code2ProtocolConverter.asRange(range),
context: await client.code2ProtocolConverter.asCodeActionContext(context)
};
const showAt = getJavaConfiguration().get<string>("quickfix.showAt");
const showAt = getJavaConfiguration().get<string>("quickfix.showAt");
if (showAt === 'line' && range.start.line === range.end.line && range.start.character === range.end.character) {
const textLine = document.lineAt(params.range.start.line);
if (textLine !== null) {
Expand Down Expand Up @@ -361,6 +362,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
context.subscriptions.push(snippetCompletionProvider.initialize());
context.subscriptions.push(serverStatusBarProvider);
context.subscriptions.push(runtimeStatusBarProvider);
context.subscriptions.push(javaClassEditorProvider.register(context));

registerClientProviders(context, { contentProviderEvent: jdtEventEmitter.event });

Expand Down Expand Up @@ -448,7 +450,7 @@ async function workspaceContainsBuildFiles(): Promise<boolean> {
const inclusionPatterns: string[] = getBuildFilePatterns();
const inclusionPatternsFromNegatedExclusion: string[] = getInclusionPatternsFromNegatedExclusion();
if (inclusionPatterns.length > 0 && inclusionPatternsFromNegatedExclusion.length > 0 &&
(await workspace.findFiles(convertToGlob(inclusionPatterns, inclusionPatternsFromNegatedExclusion), null, 1 /* maxResults */)).length > 0) {
(await workspace.findFiles(convertToGlob(inclusionPatterns, inclusionPatternsFromNegatedExclusion), null, 1 /* maxResults */)).length > 0) {
return true;
}

Expand Down Expand Up @@ -485,7 +487,7 @@ async function ensureNoBuildToolConflicts(context: ExtensionContext, clientOptio
clientOptions.initializationOptions.settings.java.import.maven.enabled = false;
context.workspaceState.update(ACTIVE_BUILD_TOOL_STATE, "gradle");
} else {
throw new Error (`Unknown build tool: ${activeBuildTool}`); // unreachable
throw new Error(`Unknown build tool: ${activeBuildTool}`); // unreachable
}
}

Expand Down Expand Up @@ -699,7 +701,7 @@ function openLogFile(logFile, openingFailureWarning: string, column: ViewColumn
if (!doc) {
return false;
}
return window.showTextDocument(doc, {viewColumn: column, preview: false})
return window.showTextDocument(doc, { viewColumn: column, preview: false })
.then(editor => !!editor);
}, () => false)
.then(didOpen => {
Expand Down Expand Up @@ -937,7 +939,7 @@ async function cleanJavaWorkspaceStorage() {
}
}
});
}
}
}

function registerOutOfMemoryDetection(storagePath: string) {
Expand Down
80 changes: 80 additions & 0 deletions src/javaClassEditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import path = require('path');
import * as vscode from 'vscode';
import { Disposable, Uri, window, ExtensionContext} from "vscode";

class JavaClassDocument implements vscode.CustomDocument {
constructor(uri: Uri) { this.uri = uri; }
uri: Uri;
dispose(): void { }
}

export class JavaClassEditorProvider implements vscode.CustomReadonlyEditorProvider {

private context: ExtensionContext;

openCustomDocument(uri: Uri, openContext: vscode.CustomDocumentOpenContext, token: vscode.CancellationToken): JavaClassDocument {
return new JavaClassDocument(uri);
}

public register(context: ExtensionContext): Disposable {
this.context = context;
const providerRegistration = vscode.window.registerCustomEditorProvider(JavaClassEditorProvider.viewType, javaClassEditorProvider);
return providerRegistration;
}

private static readonly viewType = 'decompiled.javaClass';

async resolveCustomEditor(document: vscode.CustomDocument, webviewPanel: vscode.WebviewPanel, token: vscode.CancellationToken): Promise<void> {
const nonce: string = this.getNonce();
webviewPanel.webview.options = {
enableScripts: true,
localResourceRoots: [Uri.joinPath(Uri.parse(this.context.extensionPath), 'webview-resources')]
};
const classUri = Uri.parse((document.uri.toString()).replace(/^file/, "class"));
const styleUri = Uri.file(
path.join(this.context.extensionPath, 'webview-resources', 'button.css')
);
const style: string = `<link rel="stylesheet" type="text/css" href="${webviewPanel.webview.asWebviewUri(styleUri).toString()}">`;
webviewPanel.webview.html = `
<html lang="en">
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${nonce}'; style-src ${webviewPanel.webview.cspSource};">
${style}
</head>
<body>
<div class="center">
<p>This file is not displayed in the text editor because it is a Java class file. Click here to decompile and open.</p>
<button id="btn"><center>Decompile Class File</center></button>
<div>
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
document.getElementById("btn").addEventListener("click", decompiled);
function decompiled() {
vscode.postMessage({ command: 'decompiled' });
}
</script>
</body>
</html>
`;
webviewPanel.webview.onDidReceiveMessage(message => {
switch (message.command) {
case 'decompiled':
webviewPanel.dispose();
window.showTextDocument(classUri, { preview: false });
return;
}
}, undefined, this.context.subscriptions);
}

private getNonce(): string {
let text = "";
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

}

export const javaClassEditorProvider: JavaClassEditorProvider = new JavaClassEditorProvider();
36 changes: 31 additions & 5 deletions src/providerDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ export function registerClientProviders(context: ExtensionContext, options: Prov
const jdtProvider = createJDTContentProvider(options);
context.subscriptions.push(workspace.registerTextDocumentContentProvider('jdt', jdtProvider));

const classProvider = createClassContentProvider(options);
context.subscriptions.push(workspace.registerTextDocumentContentProvider('class', classProvider));

overwriteWorkspaceSymbolProvider(context);

return {
handles: [hoverProvider, symbolProvider, jdtProvider]
handles: [hoverProvider, symbolProvider, jdtProvider, classProvider]
};
}

Expand Down Expand Up @@ -81,6 +84,29 @@ function createJDTContentProvider(options: ProviderOptions): TextDocumentContent
};
}

function createClassContentProvider(options: ProviderOptions): TextDocumentContentProvider {
return <TextDocumentContentProvider>{
onDidChange: options.contentProviderEvent,
provideTextDocumentContent: async (uri: Uri, token: CancellationToken): Promise<string> => {
const languageClient: LanguageClient | undefined = await getActiveLanguageClient();

if (!languageClient) {
return '';
}

let decompiledContent: string;
try {
const originalUri = (uri.toString()).replace(/^class/, "file").replace(/_decompiled.class/, ".class");
decompiledContent = await commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.GET_DECOMPILED_SOURCE, originalUri);
return decompiledContent;
} catch (error) {
console.log(`Error while getting decompiled source : ${error}`);
return "Error while getting decompiled source.";
}
}
};
}

function createDocumentSymbolProvider(): DocumentSymbolProvider {
return <DocumentSymbolProvider>{
provideDocumentSymbols: async (document: TextDocument, token: CancellationToken): Promise<SymbolInformation[] | DocumentSymbol[]> => {
Expand Down Expand Up @@ -109,7 +135,7 @@ function createDocumentSymbolProvider(): DocumentSymbolProvider {

const START_OF_DOCUMENT = new Range(new Position(0, 0), new Position(0, 0));

function createWorkspaceSymbolProvider(existingWorkspaceSymbolProvider: WorkspaceSymbolProvider): WorkspaceSymbolProvider {
function createWorkspaceSymbolProvider(existingWorkspaceSymbolProvider: WorkspaceSymbolProvider): WorkspaceSymbolProvider {
return {
provideWorkspaceSymbols: async (query: string, token: CancellationToken) => {
// This is a workaround until vscode add support for qualified symbol search which is tracked by
Expand Down Expand Up @@ -159,9 +185,9 @@ function createWorkspaceSymbolProvider(existingWorkspaceSymbolProvider: Workspac
}

function overwriteWorkspaceSymbolProvider(context: ExtensionContext): void {
const disposable = apiManager.getApiInstance().onDidServerModeChange( async (mode) => {
const disposable = apiManager.getApiInstance().onDidServerModeChange(async (mode) => {
if (mode === ServerMode.standard) {
const feature = (await getActiveLanguageClient()).getFeature(WorkspaceSymbolRequest.method);
const feature = (await getActiveLanguageClient()).getFeature(WorkspaceSymbolRequest.method);
const providers = feature.getProviders();
if (providers && providers.length > 0) {
feature.dispose();
Expand All @@ -186,7 +212,7 @@ const REPLACE_JDT_LINKS_PATTERN: RegExp = /(\[(?:[^\]])+\]\()(jdt:\/\/(?:(?:(?:\
* @returns the hover with all jdt:// links replaced with a command:// link that opens the jdt URI
*/
function fixJdtSchemeHoverLinks(hover: Hover): Hover {
const newContents: (MarkedString|MarkdownString)[] = [];
const newContents: (MarkedString | MarkdownString)[] = [];
for (const content of hover.contents) {
if (content instanceof MarkdownString) {
const newContent: string = (<MarkdownString>content).value.replace(REPLACE_JDT_LINKS_PATTERN, (_substring, group1, group2) => {
Expand Down
34 changes: 34 additions & 0 deletions webview-resources/button.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* https://css-tricks.com/overriding-default-button-styles/ */

button {
display: inline-block;
border: none;
padding: 12px 16px;
margin: 0;
text-decoration: none;
background: var(--vscode-button-background);
color: #ffffff;
font-family: sans-serif;
font-size: 14px;
cursor: pointer;
text-align: center;
}

button:hover,
button:focus {
background: var(--vscode-button-hoverBackground);
}
button:focus {
outline: 1px solid var(--vscode-button-hoverBackground);
/* outline-offset: -4px; */
}

.center {
text-align: center;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}

0 comments on commit 80f9e86

Please sign in to comment.