Skip to content

Commit 5eab51e

Browse files
committed
Initial dashboard implementation
Signed-off-by: Thomas Mäder <t.s.maeder@gmail.com>
1 parent e4d8ffe commit 5eab51e

File tree

17 files changed

+385
-11
lines changed

17 files changed

+385
-11
lines changed

package.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1937,6 +1937,25 @@
19371937
"when": "view == references-view.tree && reference-list.hasResult && reference-list.source == javaTypeHierarchy && viewItem != 'false'"
19381938
}
19391939
]
1940+
},
1941+
"viewsContainers": {
1942+
"activitybar": [
1943+
{
1944+
"id": "vscode-java-container",
1945+
"title": "Java",
1946+
"icon": "icons/icon128.png"
1947+
}
1948+
]
1949+
},
1950+
"views": {
1951+
"vscode-java-container": [
1952+
{
1953+
"type": "webview",
1954+
"id": "vscode-java-dashboard",
1955+
"name": "Dashboard",
1956+
"icon": "icons/icons128.png"
1957+
}
1958+
]
19401959
}
19411960
},
19421961
"scripts": {
@@ -1946,7 +1965,7 @@
19461965
"pretest": "npm run compile",
19471966
"test": "node ./out/test/runtest.js",
19481967
"build-server": "./node_modules/.bin/gulp build_server",
1949-
"build": "./node_modules/.bin/gulp build_or_download",
1968+
"build": "node_modules\\.bin\\gulp build_or_download",
19501969
"fast-build-server": "./node_modules/.bin/gulp dev_server",
19511970
"watch-server": "./node_modules/.bin/gulp watch_server",
19521971
"eslint": "eslint --ignore-path .eslintignore --ext .js,.ts,.tsx .",

src/dashboard/dashboard.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { prepareExecutable } from '../javaServerStarter';
2+
import { getComputedJavaConfig, getExecutable } from '../extension';
3+
import * as fs from 'fs';
4+
import * as path from 'path';
5+
import * as vscode from 'vscode';
6+
import { getNonce, getUri } from '../webviewUtils';
7+
import { InitializeMessage, JVM, SettingChangedMessage } from '../webviewProtocol/toDashboard';
8+
import { isLombokSupportEnabled } from '../lombokSupport';
9+
10+
11+
async function getJvms(): Promise<JVM[]> {
12+
const config = await getComputedJavaConfig();
13+
const jres: JVM[] = config.configuration.runtimes.map(jre => ({
14+
name: jre.name,
15+
version: jre.version,
16+
path: jre.path,
17+
}));
18+
return jres;
19+
20+
}
21+
22+
export function getWebviewContent(webview: vscode.Webview, extensionUri: vscode.Uri) {
23+
setWebviewMessageListener(webview);
24+
25+
const scriptUri = getUri(webview, extensionUri, [
26+
"dist",
27+
"dashboard.js",
28+
]);
29+
30+
const nonce = getNonce();
31+
32+
return /* html*/ `
33+
<!DOCTYPE html>
34+
<html lang="en">
35+
<head>
36+
<meta charset="utf-8">
37+
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
38+
<meta name="theme-color" content="#000000">
39+
<title>Dashboard</title>
40+
</head>
41+
<body>
42+
<div id="root"></div>
43+
<script nonce="${nonce}" src="${scriptUri}"></script>
44+
</body>
45+
</html>
46+
`;
47+
}
48+
49+
function setWebviewMessageListener(webview: vscode.Webview) {
50+
vscode.workspace.onDidChangeConfiguration(e => {
51+
if (e.affectsConfiguration('java.jdt.ls.lombokSupport.enabled')) {
52+
const msg: SettingChangedMessage = {
53+
type: "settingsChanged",
54+
lombokEnabled: isLombokSupportEnabled()
55+
}
56+
webview.postMessage(msg);
57+
}
58+
});
59+
60+
webview.onDidReceiveMessage(
61+
async (message: any) => {
62+
const command = message.command;
63+
switch (command) {
64+
case "webviewReady":
65+
const message: InitializeMessage = {
66+
type: "initialize",
67+
jvms: await getJvms(),
68+
lombokEnabled: isLombokSupportEnabled()
69+
};
70+
await webview.postMessage(message);
71+
break;
72+
}
73+
}
74+
);
75+
}

src/extension.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import * as fse from 'fs-extra';
66
import * as os from 'os';
77
import * as path from 'path';
88
import * as semver from 'semver';
9-
import { CodeActionContext, commands, CompletionItem, ConfigurationTarget, Diagnostic, env, EventEmitter, ExtensionContext, extensions, IndentAction, InputBoxOptions, languages, Location, MarkdownString, QuickPickItemKind, Range, RelativePattern, SnippetString, SnippetTextEdit, TextDocument, TextEditorRevealType, UIKind, Uri, version, ViewColumn, window, workspace, WorkspaceConfiguration, WorkspaceEdit } from 'vscode';
9+
import { CodeActionContext, commands, CompletionItem, ConfigurationTarget, Diagnostic, env, EventEmitter, ExtensionContext, extensions,
10+
IndentAction, InputBoxOptions, languages, Location, MarkdownString, QuickPickItemKind, Range, RelativePattern,
11+
SnippetString, SnippetTextEdit, TextDocument, TextEditorRevealType, UIKind, Uri, version, ViewColumn,
12+
WebviewView, WebviewViewResolveContext, window, workspace, WorkspaceConfiguration, WorkspaceEdit } from 'vscode';
1013
import { CancellationToken, CodeActionParams, CodeActionRequest, CodeActionResolveRequest, Command, CompletionRequest, DidChangeConfigurationNotification, ExecuteCommandParams, ExecuteCommandRequest, LanguageClientOptions, RevealOutputChannelOn } from 'vscode-languageclient';
11-
import { LanguageClient } from 'vscode-languageclient/node';
14+
import { Executable, LanguageClient } from 'vscode-languageclient/node';
1215
import { apiManager } from './apiManager';
1316
import { ClientErrorHandler } from './clientErrorHandler';
1417
import { Commands, CommandTitle } from './commands';
@@ -39,6 +42,8 @@ import { BuildFileSelector, PICKED_BUILD_FILES, cleanupWorkspaceState } from './
3942
import { pasteFile } from './pasteAction';
4043
import { ServerStatusKind } from './serverStatus';
4144
import { TelemetryService } from '@redhat-developer/vscode-redhat-telemetry/lib/node';
45+
import { Deferred } from './promiseUtil';
46+
import { getWebviewContent } from './dashboard/dashboard';
4247

4348
const syntaxClient: SyntaxLanguageClient = new SyntaxLanguageClient();
4449
const standardClient: StandardLanguageClient = new StandardLanguageClient();
@@ -47,6 +52,17 @@ const extensionName = 'Language Support for Java';
4752
let storagePath: string;
4853
let clientLogFile: string;
4954

55+
const excutable= new Deferred<Executable>();
56+
57+
export async function getExecutable(): Promise<Executable> {
58+
return excutable.promise;
59+
}
60+
61+
const javaConfigDeferred = new Deferred<any>();
62+
export async function getComputedJavaConfig(): Promise<any> {
63+
return javaConfigDeferred.promise;
64+
}
65+
5066
/**
5167
* Shows a message about the server crashing due to an out of memory issue
5268
*/
@@ -113,6 +129,21 @@ export function fixJdtLinksInDocumentation(oldDocumentation: MarkdownString): Ma
113129
}
114130

115131
export async function activate(context: ExtensionContext): Promise<ExtensionAPI> {
132+
console.log('registering webview provider');
133+
context.subscriptions.push(window.registerWebviewViewProvider('vscode-java-dashboard', {
134+
resolveWebviewView: async function (webviewView: WebviewView, webviewContext: WebviewViewResolveContext, token: CancellationToken): Promise<void> {
135+
136+
webviewView.webview.options = {
137+
enableScripts: true,
138+
enableCommandUris: true,
139+
localResourceRoots: [context.extensionUri]
140+
};
141+
142+
webviewView.webview.html = await getWebviewContent(webviewView.webview, context.extensionUri);
143+
}
144+
}));
145+
console.log('registered webview provider');
146+
116147
await loadSupportedJreNames(context);
117148
context.subscriptions.push(commands.registerCommand(Commands.FILESEXPLORER_ONPASTE, async () => {
118149
const originalClipboard = await env.clipboard.readText();
@@ -196,6 +227,9 @@ export async function activate(context: ExtensionContext): Promise<ExtensionAPI>
196227
let requireStandardServer = (serverMode !== ServerMode.lightWeight) && (!isDebugModeByClientPort || !!process.env['JDTLS_CLIENT_PORT']);
197228
let initFailureReported: boolean = false;
198229

230+
const javaConfig = await getJavaConfig(requirements.java_home);
231+
javaConfigDeferred.resolve(javaConfig);
232+
199233
// Options to control the language client
200234
const clientOptions: LanguageClientOptions = {
201235
// Register the server for java
@@ -210,7 +244,7 @@ export async function activate(context: ExtensionContext): Promise<ExtensionAPI>
210244
initializationOptions: {
211245
bundles: collectJavaExtensions(extensions.all),
212246
workspaceFolders: workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.toString()) : null,
213-
settings: { java: await getJavaConfig(requirements.java_home) },
247+
settings: { java: javaConfig },
214248
extendedClientCapabilities: {
215249
classFileContentsSupport: true,
216250
overrideMethodsPromptSupport: true,
@@ -394,6 +428,7 @@ export async function activate(context: ExtensionContext): Promise<ExtensionAPI>
394428
// no need to pass `resolve` into any code past this point,
395429
// since `resolve` is a no-op from now on
396430
const serverOptions = prepareExecutable(requirements, syntaxServerWorkspacePath, context, true);
431+
excutable.resolve(serverOptions);
397432
if (requireSyntaxServer) {
398433
if (process.env['SYNTAXLS_CLIENT_PORT']) {
399434
syntaxClient.initialize(requirements, clientOptions);

src/promiseUtil.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export class Deferred<T> {
2+
readonly promise: Promise<T>;
3+
4+
resolve: (result: T) => void;
5+
reject: (error: any) => void;
6+
7+
constructor() {
8+
this.promise= new Promise<T>((resolve, reject) => {
9+
this.resolve = resolve;
10+
this.reject = reject;
11+
});
12+
}
13+
}

src/webview/dashboard/dashboard.css

Whitespace-only changes.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* eslint-disable @typescript-eslint/naming-convention */
2+
/* eslint-disable @typescript-eslint/prefer-for-of */
3+
import React from "react";
4+
import { vscode } from "../vscodeApiWrapper";
5+
import { Jvms } from "./jvms";
6+
import { InitializeMessage, JVM, ToDashboardMessage, ToDashboardMessageType } from "../../webviewProtocol/toDashboard";
7+
import './dashboard.css';
8+
9+
type State = {
10+
jvms: JVM[];
11+
lombokEnabled: boolean;
12+
};
13+
14+
export interface AppProps {
15+
}
16+
17+
function openSettingsLink(settingId: string): string {
18+
const args = {
19+
query: settingId
20+
};
21+
return `command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify(args))}`;
22+
}
23+
24+
export class Dashboard extends React.Component<AppProps, State> {
25+
26+
constructor(props: AppProps) {
27+
super(props);
28+
this.state = {
29+
jvms: [],
30+
lombokEnabled: false,
31+
};
32+
}
33+
34+
handleMessage = (event: MessageEvent) => {
35+
const message = event.data as ToDashboardMessage;
36+
switch (message.type) {
37+
case "initialize": {
38+
const initializeMessage = message as InitializeMessage;
39+
this.setState({ jvms: initializeMessage.jvms, lombokEnabled: initializeMessage.lombokEnabled });
40+
break;
41+
} case "settingsChanged":
42+
this.setState({ lombokEnabled: (message as InitializeMessage).lombokEnabled });
43+
break;
44+
}
45+
};
46+
47+
componentDidMount(): void {
48+
window.addEventListener("message", this.handleMessage);
49+
vscode.postMessage({
50+
command: "webviewReady"
51+
});
52+
}
53+
54+
55+
render = () => {
56+
57+
return (
58+
<main>
59+
<h3>Detected Java Installations</h3>
60+
<Jvms jvms={this.state.jvms}></Jvms>
61+
<a href={openSettingsLink('java.configuration.runtimes')}>configure...</a>
62+
<br />
63+
<h3>Lombok Support</h3>
64+
<span>enabled</span><input type="checkbox" readOnly={true} checked={this.state.lombokEnabled}/>
65+
<a href = {openSettingsLink('java.jdt.ls.lombokSupport.enabled')}>configure...</a>
66+
</main >
67+
);
68+
};
69+
}

src/webview/dashboard/index.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom";
3+
import { Dashboard } from "./dashboard";
4+
5+
6+
ReactDOM.render(
7+
<React.StrictMode>
8+
<Dashboard />
9+
</React.StrictMode>,
10+
document.getElementById('root')
11+
);

src/webview/dashboard/jvms.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.jvm-table {
2+
text-align: left;
3+
width: 100%;
4+
border-collapse: collapse;
5+
border: 1px solid var(--vscode-panel-border);
6+
}
7+
8+
.jvm-table td{
9+
border-left: 1px solid var(--vscode-panel-border);
10+
}
11+
12+
.jvm-table th{
13+
border-bottom: 1px solid var(--vscode-panel-border);
14+
}
15+
16+
.jvm-table th>td {
17+
background-color: var(--vscode-sideBarSectionHeader-background);
18+
}
19+
20+
.jvm-table tbody tr.odd {
21+
background-color: var(--vscode-tree-tableOddRowsBackground);
22+
}

src/webview/dashboard/jvms.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from "react";
2+
import { JVM } from "../../webviewProtocol/toDashboard";
3+
import path from "path";
4+
import './jvms.css';
5+
6+
export interface JvmsProps {
7+
jvms: JVM[];
8+
}
9+
10+
export class Jvms extends React.Component<JvmsProps, {}> {
11+
constructor(props: any) {
12+
super(props);
13+
this.state = {
14+
};
15+
}
16+
17+
render = () => {
18+
return (
19+
<table className="jvm-table">
20+
<thead>
21+
<tr>
22+
<th>Name</th>
23+
<th>Path</th>
24+
</tr>
25+
</thead>
26+
<tbody>
27+
{this.props.jvms.map((jvm, index) => (
28+
<tr className={index % 2 === 0 ? "even" : "odd"} key={jvm.name}>
29+
<td>{jvm.name}</td>
30+
<td>{jvm.path}</td>
31+
</tr>
32+
))}
33+
</tbody>
34+
</table>
35+
);
36+
};
37+
}

src/webviewProtocol/common.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface WebviewMessage<T> {
2+
type: T;
3+
}
4+
5+
export type FromWebviewMessageType = "webviewReady";
6+
export type ToWebviewMessageType = "initialize" | "settingsChanged";

0 commit comments

Comments
 (0)