Skip to content

Commit 1cb29d2

Browse files
authored
Show server status via language status item (#2363)
* apply language status item Signed-off-by: Shi Chen <[email protected]>
1 parent e032c20 commit 1cb29d2

File tree

4 files changed

+331
-63
lines changed

4 files changed

+331
-63
lines changed

src/extension.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
185185

186186
cleanJavaWorkspaceStorage();
187187

188+
serverStatusBarProvider.initialize();
189+
188190
return requirements.resolveRequirements(context).catch(error => {
189191
// show error
190192
window.showErrorMessage(error.message, error.label).then((selection) => {
@@ -424,7 +426,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
424426
if (event === ServerMode.STANDARD) {
425427
syntaxClient.stop();
426428
fileEventHandler.setServerStatus(true);
427-
runtimeStatusBarProvider.initialize(context.storagePath);
429+
runtimeStatusBarProvider.initialize(context);
428430
}
429431
commands.executeCommand('setContext', 'java:serverMode', event);
430432
});
@@ -544,7 +546,7 @@ async function ensureNoBuildToolConflicts(context: ExtensionContext, clientOptio
544546
return true;
545547
}
546548

547-
async function hasBuildToolConflicts(): Promise<boolean> {
549+
export async function hasBuildToolConflicts(): Promise<boolean> {
548550
const projectConfigurationUris: Uri[] = await getBuildFilesInWorkspace();
549551
const projectConfigurationFsPaths: string[] = projectConfigurationUris.map((uri) => uri.fsPath);
550552
const eclipseDirectories = getDirectoriesByBuildFile(projectConfigurationFsPaths, [], ".project");

src/languageStatusItemFactory.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
'use strict';
2+
3+
import * as path from "path";
4+
import * as vscode from "vscode";
5+
import { Commands } from "./commands";
6+
import { StatusIcon } from "./serverStatusBarProvider";
7+
8+
const languageServerDocumentSelector = [
9+
{ scheme: 'file', language: 'java' },
10+
{ scheme: 'jdt', language: 'java' },
11+
{ scheme: 'untitled', language: 'java' },
12+
{ pattern: '**/pom.xml' },
13+
{ pattern: '**/{build,settings}.gradle'},
14+
{ pattern: '**/{build,settings}.gradle.kts'}
15+
];
16+
17+
export function supportsLanguageStatus(): boolean {
18+
return !!vscode.languages.createLanguageStatusItem;
19+
}
20+
21+
export namespace StatusCommands {
22+
export const switchToStandardCommand = {
23+
title: "Load Projects",
24+
command: Commands.SWITCH_SERVER_MODE,
25+
arguments: ["Standard", true],
26+
tooltip: "LightWeight mode only provides limited features, please load projects to get full feature set"
27+
};
28+
29+
export const showServerStatusCommand = {
30+
title: "Show Build Status",
31+
command: Commands.SHOW_SERVER_TASK_STATUS,
32+
tooltip: "Show Build Status"
33+
};
34+
35+
export const configureJavaRuntimeCommand = {
36+
title: "Configure Java Runtime",
37+
command: "workbench.action.openSettings",
38+
arguments: ["java.configuration.runtimes"],
39+
tooltip: "Configure Java Runtime"
40+
};
41+
}
42+
43+
export namespace ServerStatusItemFactory {
44+
export function create(): any {
45+
if (supportsLanguageStatus()) {
46+
const item = vscode.languages.createLanguageStatusItem("JavaServerStatusItem", languageServerDocumentSelector);
47+
item.name = "Java Language Server Status";
48+
return item;
49+
}
50+
return undefined;
51+
}
52+
53+
export function showLightWeightStatus(item: any): void {
54+
item.severity = vscode.LanguageStatusSeverity?.Warning;
55+
item.text = StatusIcon.LightWeight;
56+
item.detail = "Lightweight Mode";
57+
item.command = StatusCommands.switchToStandardCommand;
58+
}
59+
60+
export function showStandardStatus(item: any): void {
61+
item.severity = vscode.LanguageStatusSeverity?.Information;
62+
item.command = StatusCommands.showServerStatusCommand;
63+
}
64+
65+
export function setBusy(item: any): void {
66+
item.text = "Building";
67+
item.busy = true;
68+
}
69+
70+
export function setError(item: any): void {
71+
item.busy = false;
72+
item.severity = vscode.LanguageStatusSeverity?.Error;
73+
item.command = {
74+
title: "Open logs",
75+
command: Commands.OPEN_LOGS
76+
};
77+
item.text = StatusIcon.Error;
78+
item.detail = "Errors occurred in initializing language server";
79+
}
80+
81+
export function setWarning(item: any): void {
82+
item.busy = false;
83+
item.severity = vscode.LanguageStatusSeverity?.Error;
84+
item.command = {
85+
title: "Show PROBLEMS Panel",
86+
command: "workbench.panel.markers.view.focus",
87+
tooltip: "Errors occurred in project configurations, click to show the PROBLEMS panel"
88+
};
89+
item.text = StatusIcon.Warning;
90+
item.detail = "Project Configuration Error";
91+
}
92+
93+
export function setReady(item: any): void {
94+
item.busy = false;
95+
item.severity = vscode.LanguageStatusSeverity?.Information;
96+
item.command = StatusCommands.showServerStatusCommand;
97+
item.text = StatusIcon.Ready;
98+
item.detail = "";
99+
}
100+
}
101+
102+
export namespace RuntimeStatusItemFactory {
103+
export function create(text: string, vmInstallPath: string): any {
104+
if (supportsLanguageStatus()) {
105+
const item = vscode.languages.createLanguageStatusItem("javaRuntimeStatusItem", languageServerDocumentSelector);
106+
item.severity = vscode.LanguageStatusSeverity?.Information;
107+
item.name = "Java Runtime";
108+
item.text = text;
109+
item.command = StatusCommands.configureJavaRuntimeCommand;
110+
if (vmInstallPath) {
111+
item.command.tooltip = `Language Level: ${text} <${vmInstallPath}>`;
112+
}
113+
return item;
114+
}
115+
return undefined;
116+
}
117+
118+
export function update(item: any, text: string, vmInstallPath: string): void {
119+
item.text = text;
120+
item.command.tooltip = vmInstallPath ? `Language Level: ${text} <${vmInstallPath}>` : "Configure Java Runtime";
121+
}
122+
}
123+
124+
export namespace BuildFileStatusItemFactory {
125+
export function create(buildFilePath: string): any {
126+
if (supportsLanguageStatus()) {
127+
const fileName = path.basename(buildFilePath);
128+
const item = vscode.languages.createLanguageStatusItem("javaBuildFileStatusItem", languageServerDocumentSelector);
129+
item.severity = vscode.LanguageStatusSeverity?.Information;
130+
item.name = "Java Build File";
131+
item.text = fileName;
132+
item.command = getOpenBuildFileCommand(buildFilePath);
133+
return item;
134+
}
135+
return undefined;
136+
}
137+
138+
export function update(item: any, buildFilePath: string): void {
139+
const fileName = path.basename(buildFilePath);
140+
item.text = fileName;
141+
item.command = getOpenBuildFileCommand(buildFilePath);
142+
}
143+
144+
function getOpenBuildFileCommand(buildFilePath: string): vscode.Command {
145+
const relativePath = vscode.workspace.asRelativePath(buildFilePath);
146+
return {
147+
title: `Open Config File`,
148+
command: Commands.OPEN_BROWSER,
149+
arguments: [vscode.Uri.file(buildFilePath)],
150+
tooltip: `Open ${relativePath}`
151+
};
152+
}
153+
}

src/runtimeStatusBarProvider.ts

Lines changed: 110 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
'use strict';
22

3-
import { StatusBarItem, window, StatusBarAlignment, TextEditor, Uri, commands, workspace, version } from "vscode";
3+
import * as fse from "fs-extra";
4+
import { StatusBarItem, window, StatusBarAlignment, TextEditor, Uri, commands, workspace, version, languages, Command, ExtensionContext } from "vscode";
45
import { Commands } from "./commands";
56
import { Disposable } from "vscode-languageclient";
67
import * as path from "path";
78
import { apiManager } from "./apiManager";
89
import * as semver from "semver";
10+
import { ACTIVE_BUILD_TOOL_STATE } from "./settings";
11+
import { BuildFileStatusItemFactory, RuntimeStatusItemFactory, StatusCommands, supportsLanguageStatus } from "./languageStatusItemFactory";
12+
import { getJavaConfiguration } from "./utils";
13+
import { hasBuildToolConflicts } from "./extension";
914

1015
class RuntimeStatusBarProvider implements Disposable {
1116
private statusBarItem: StatusBarItem;
17+
private runtimeStatusItem: any;
18+
private buildFileStatusItem: any;
1219
private javaProjects: Map<string, IProjectInfo>;
1320
private fileProjectMapping: Map<string, string>;
1421
private storagePath: string | undefined;
@@ -24,17 +31,20 @@ class RuntimeStatusBarProvider implements Disposable {
2431
this.isAdvancedStatusBarItem = semver.gte(version, "1.57.0");
2532
}
2633

27-
public async initialize(storagePath?: string): Promise<void> {
34+
public async initialize(context: ExtensionContext): Promise<void> {
2835
// ignore the hash part to make it compatible in debug mode.
36+
const storagePath = context.storagePath;
2937
if (storagePath) {
3038
this.storagePath = Uri.file(path.join(storagePath, "..", "..")).fsPath;
3139
}
3240

33-
if (this.isAdvancedStatusBarItem) {
34-
this.statusBarItem = (window.createStatusBarItem as any)("java.runtimeStatus", StatusBarAlignment.Right, 0);
35-
(this.statusBarItem as any).name = "Java Runtime Configuration";
36-
} else {
37-
this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, 0);
41+
if (!supportsLanguageStatus()) {
42+
if (this.isAdvancedStatusBarItem) {
43+
this.statusBarItem = (window.createStatusBarItem as any)("java.runtimeStatus", StatusBarAlignment.Right, 0);
44+
(this.statusBarItem as any).name = "Java Runtime Configuration";
45+
} else {
46+
this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, 0);
47+
}
3848
}
3949

4050
let projectUriStrings: string[];
@@ -48,38 +58,48 @@ class RuntimeStatusBarProvider implements Disposable {
4858
this.javaProjects.set(Uri.parse(uri).fsPath, undefined);
4959
}
5060

51-
this.statusBarItem.command = {
52-
title: "Configure Java Runtime",
53-
command: "workbench.action.openSettings",
54-
arguments: ["java.configuration.runtimes"],
55-
};
61+
if (!supportsLanguageStatus()) {
62+
this.statusBarItem.command = StatusCommands.configureJavaRuntimeCommand;
63+
}
5664

5765
this.disposables.push(window.onDidChangeActiveTextEditor((textEditor) => {
58-
this.updateItem(textEditor);
66+
this.updateItem(context, textEditor);
5967
}));
6068

6169
this.disposables.push(apiManager.getApiInstance().onDidProjectsImport(async (uris: Uri[]) => {
6270
for (const uri of uris) {
6371
this.javaProjects.set(uri.fsPath, this.javaProjects.get(uri.fsPath));
6472
}
65-
await this.updateItem(window.activeTextEditor);
73+
await this.updateItem(context, window.activeTextEditor);
6674
}));
6775

6876
this.disposables.push(apiManager.getApiInstance().onDidClasspathUpdate(async (e: Uri) => {
6977
for (const projectPath of this.javaProjects.keys()) {
7078
if (path.relative(projectPath, e.fsPath) === '') {
7179
this.javaProjects.set(projectPath, undefined);
72-
await this.updateItem(window.activeTextEditor);
80+
await this.updateItem(context, window.activeTextEditor);
7381
return;
7482
}
7583
}
7684
}));
7785

78-
await this.updateItem(window.activeTextEditor);
86+
await this.updateItem(context, window.activeTextEditor);
87+
}
88+
89+
private hideRuntimeStatusItem(): void {
90+
this.runtimeStatusItem?.dispose();
91+
this.runtimeStatusItem = undefined;
92+
}
93+
94+
private hideBuildFileStatusItem(): void {
95+
this.buildFileStatusItem?.dispose();
96+
this.buildFileStatusItem = undefined;
7997
}
8098

8199
public dispose(): void {
82-
this.statusBarItem.dispose();
100+
this.statusBarItem?.dispose();
101+
this.runtimeStatusItem?.dispose();
102+
this.buildFileStatusItem?.dispose();
83103
for (const disposable of this.disposables) {
84104
disposable.dispose();
85105
}
@@ -140,28 +160,54 @@ class RuntimeStatusBarProvider implements Disposable {
140160
return undefined;
141161
}
142162

143-
private async updateItem(textEditor: TextEditor): Promise<void> {
144-
if (!textEditor || path.extname(textEditor.document.fileName) !== ".java") {
145-
this.statusBarItem.hide();
163+
private async updateItem(context: ExtensionContext, textEditor: TextEditor): Promise<void> {
164+
if (!textEditor || path.extname(textEditor.document.fileName) !== ".java" && !supportsLanguageStatus()) {
165+
this.statusBarItem?.hide();
146166
return;
147167
}
148168

149169
const uri: Uri = textEditor.document.uri;
150170
const projectPath: string = this.findOwnerProject(uri);
151171
if (!projectPath) {
152-
this.statusBarItem.hide();
172+
if (supportsLanguageStatus()) {
173+
this.hideRuntimeStatusItem();
174+
this.hideBuildFileStatusItem();
175+
} else {
176+
this.statusBarItem?.hide();
177+
}
153178
return;
154179
}
155180

156181
const projectInfo: IProjectInfo = await this.getProjectInfo(projectPath);
157182
if (!projectInfo) {
158-
this.statusBarItem.hide();
183+
if (supportsLanguageStatus()) {
184+
this.hideRuntimeStatusItem();
185+
this.hideBuildFileStatusItem();
186+
} else {
187+
this.statusBarItem?.hide();
188+
}
159189
return;
160190
}
161191

162-
this.statusBarItem.text = this.getJavaRuntimeFromVersion(projectInfo.sourceLevel);
163-
this.statusBarItem.tooltip = projectInfo.vmInstallPath ? `Language Level: ${this.statusBarItem.text} <${projectInfo.vmInstallPath}>` : "Configure Java Runtime";
164-
this.statusBarItem.show();
192+
const text = this.getJavaRuntimeFromVersion(projectInfo.sourceLevel);
193+
if (supportsLanguageStatus()) {
194+
const buildFilePath = await this.getBuildFilePath(context, projectPath);
195+
if (!this.runtimeStatusItem) {
196+
this.runtimeStatusItem = RuntimeStatusItemFactory.create(text, projectInfo.vmInstallPath);
197+
if (buildFilePath) {
198+
this.buildFileStatusItem = BuildFileStatusItemFactory.create(buildFilePath);
199+
}
200+
} else {
201+
RuntimeStatusItemFactory.update(this.runtimeStatusItem, text, projectInfo.vmInstallPath);
202+
if (buildFilePath) {
203+
BuildFileStatusItemFactory.update(this.buildFileStatusItem, buildFilePath);
204+
}
205+
}
206+
} else {
207+
this.statusBarItem.text = text;
208+
this.statusBarItem.tooltip = projectInfo.vmInstallPath ? `Language Level: ${this.statusBarItem.text} <${projectInfo.vmInstallPath}>` : "Configure Java Runtime";
209+
this.statusBarItem.show();
210+
}
165211
}
166212

167213
private isDefaultProjectPath(fsPath: string) {
@@ -181,6 +227,45 @@ class RuntimeStatusBarProvider implements Disposable {
181227

182228
return `JavaSE-${ver}`;
183229
}
230+
231+
private async getBuildFilePath(context: ExtensionContext, projectPath: string): Promise<string | undefined> {
232+
const isMavenEnabled: boolean = getJavaConfiguration().get<boolean>("import.maven.enabled");
233+
const isGradleEnabled: boolean = getJavaConfiguration().get<boolean>("import.gradle.enabled");
234+
if (isMavenEnabled && isGradleEnabled) {
235+
let buildFilePath: string | undefined;
236+
const activeBuildTool: string | undefined = context.workspaceState.get(ACTIVE_BUILD_TOOL_STATE);
237+
if (!activeBuildTool) {
238+
if (!hasBuildToolConflicts()) {
239+
// only one build tool exists in the project
240+
buildFilePath = await this.getBuildFilePathFromNames(projectPath, ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"]);
241+
} else {
242+
// the user has not resolved build conflicts yet
243+
return undefined;
244+
}
245+
} else if (activeBuildTool.toLocaleLowerCase().includes("maven")) {
246+
buildFilePath = await this.getBuildFilePathFromNames(projectPath, ["pom.xml"]);
247+
} else if (activeBuildTool.toLocaleLowerCase().includes("gradle")) {
248+
buildFilePath = await this.getBuildFilePathFromNames(projectPath, ["build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"]);
249+
}
250+
return buildFilePath;
251+
} else if (isMavenEnabled) {
252+
return this.getBuildFilePathFromNames(projectPath, ["pom.xml"]);
253+
} else if (isGradleEnabled) {
254+
return this.getBuildFilePathFromNames(projectPath, ["build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"]);
255+
} else {
256+
return undefined;
257+
}
258+
}
259+
260+
private async getBuildFilePathFromNames(projectPath: string, buildFileNames: string[]): Promise<string> {
261+
for (const buildFileName of buildFileNames) {
262+
const buildFilePath = path.join(projectPath, buildFileName);
263+
if (await fse.pathExists(buildFilePath)) {
264+
return buildFilePath;
265+
}
266+
}
267+
return undefined;
268+
}
184269
}
185270

186271
interface IProjectInfo {

0 commit comments

Comments
 (0)