Skip to content

Commit 5d1c872

Browse files
authored
Allow rebuild selected projects (#2526)
- Add a new extended LSP request: BuildProjectRequest. - The 'isFullBuild' is set to true by default in the request param. Signed-off-by: sheche <[email protected]>
1 parent f866687 commit 5d1c872

File tree

7 files changed

+134
-39
lines changed

7 files changed

+134
-39
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ The following commands are available:
9797
- `Java: Open Java Extension Log File`: opens the Java extension log file, useful for troubleshooting problems.
9898
- `Java: Open All Log Files`: opens both the Java Language Server log file and the Java extension log file.
9999
- `Java: Force Java Compilation` (`Shift+Alt+B`): manually triggers compilation of the workspace.
100+
- `Java: Rebuild Projects`: manually triggers a full build of the selected projects.
100101
- `Java: Open Java Formatter Settings`: opens the Eclipse formatter settings. Creates a new settings file if none exists.
101102
- `Java: Clean Java Language Server Workspace`: cleans the Java language server workspace.
102103
- `Java: Attach Source`: attaches a jar/zip source to the currently opened binary class file. This command is only available in the editor context menu.

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,11 @@
950950
"title": "%java.workspace.compile%",
951951
"category": "Java"
952952
},
953+
{
954+
"command": "java.project.build",
955+
"title": "%java.project.build%",
956+
"category": "Java"
957+
},
953958
{
954959
"command": "java.open.formatter.settings",
955960
"title": "%java.open.formatter.settings%",
@@ -1104,6 +1109,10 @@
11041109
"command": "java.workspace.compile",
11051110
"when": "javaLSReady"
11061111
},
1112+
{
1113+
"command": "java.project.build",
1114+
"when": "javaLSReady"
1115+
},
11071116
{
11081117
"command": "java.project.listSourcePaths.command",
11091118
"when": "javaLSReady"

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"java.open.clientLog": "Open Java Extension Log File",
99
"java.open.logs": "Open All Log Files",
1010
"java.workspace.compile": "Force Java Compilation",
11+
"java.project.build": "Rebuild Projects",
1112
"java.open.formatter.settings": "Open Java Formatter Settings",
1213
"java.clean.workspace": "Clean Java Language Server Workspace",
1314
"java.project.updateSourceAttachment": "Attach Source",

package.nls.zh.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"java.open.clientLog": "打开 Java 插件日志文件",
99
"java.open.logs": "打开所有日志文件",
1010
"java.workspace.compile": "强制编译",
11+
"java.project.build": "重新构建项目",
1112
"java.open.formatter.settings": "打开 Java 格式设置",
1213
"java.clean.workspace": "清理 Java 语言服务器工作区",
1314
"java.project.updateSourceAttachment": "附加源代码",

src/commands.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ export namespace Commands {
7979
*/
8080
export const COMPILE_WORKSPACE = 'java.workspace.compile';
8181

82+
/**
83+
* Execute build for projects
84+
*/
85+
export const BUILD_PROJECT = 'java.project.build';
86+
8287
/**
8388
* Open Java Language Server Log file
8489
*/

src/protocol.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ export namespace CompileWorkspaceRequest {
135135
export const type = new RequestType<boolean, CompileWorkspaceStatus, void>('java/buildWorkspace');
136136
}
137137

138+
export namespace BuildProjectRequest {
139+
export const type = new RequestType<BuildProjectParams, CompileWorkspaceStatus, void>('java/buildProjects');
140+
}
141+
142+
export interface BuildProjectParams {
143+
identifiers: TextDocumentIdentifier[];
144+
isFullBuild: boolean;
145+
}
146+
138147
export namespace ExecuteClientCommandRequest {
139148
export const type = new RequestType<ExecuteCommandParams, any, void>('workspace/executeClientCommand');
140149
}

src/standardLanguageClient.ts

Lines changed: 108 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
'use strict';
22

3-
import { ExtensionContext, window, workspace, commands, Uri, ProgressLocation, ViewColumn, EventEmitter, extensions, Location, languages, CodeActionKind, TextEditor, CancellationToken, ConfigurationTarget, Range, Position, QuickPickItem } from "vscode";
3+
import { ExtensionContext, window, workspace, commands, Uri, ProgressLocation, ViewColumn, EventEmitter, extensions, Location, languages, CodeActionKind, TextEditor, CancellationToken, ConfigurationTarget, Range, Position, QuickPickItem, QuickPickItemKind } from "vscode";
44
import { Commands } from "./commands";
55
import { serverStatus, ServerStatusKind } from "./serverStatus";
66
import { prepareExecutable, awaitServerConnection } from "./javaServerStarter";
77
import { getJavaConfig, applyWorkspaceEdit } from "./extension";
88
import { LanguageClientOptions, Position as LSPosition, Location as LSLocation, MessageType, TextDocumentPositionParams, ConfigurationRequest, ConfigurationParams } from "vscode-languageclient";
99
import { LanguageClient, StreamInfo } from "vscode-languageclient/node";
10-
import { CompileWorkspaceRequest, CompileWorkspaceStatus, SourceAttachmentRequest, SourceAttachmentResult, SourceAttachmentAttribute, ProjectConfigurationUpdateRequest, FeatureStatus, StatusNotification, ProgressReportNotification, ActionableNotification, ExecuteClientCommandRequest, ServerNotification, EventNotification, EventType, LinkLocation, FindLinks, GradleCompatibilityInfo, UpgradeGradleWrapperInfo } from "./protocol";
10+
import { CompileWorkspaceRequest, CompileWorkspaceStatus, SourceAttachmentRequest, SourceAttachmentResult, SourceAttachmentAttribute, ProjectConfigurationUpdateRequest, FeatureStatus, StatusNotification, ProgressReportNotification, ActionableNotification, ExecuteClientCommandRequest, ServerNotification, EventNotification, EventType, LinkLocation, FindLinks, GradleCompatibilityInfo, UpgradeGradleWrapperInfo, BuildProjectRequest, BuildProjectParams } from "./protocol";
1111
import { setGradleWrapperChecksum, excludeProjectSettingsFiles, ServerMode } from "./settings";
1212
import { onExtensionChange, collectBuildFilePattern } from "./plugin";
1313
import { activationProgressNotification, serverTaskPresenter } from "./serverTaskPresenter";
@@ -395,6 +395,62 @@ export class StandardLanguageClient {
395395
typeHierarchyTree.changeBaseItem(item);
396396
}));
397397

398+
context.subscriptions.push(commands.registerCommand(Commands.BUILD_PROJECT, async (uris: Uri[] | Uri, isFullBuild: boolean, token: CancellationToken) => {
399+
let resources: Uri[] = [];
400+
if (uris instanceof Uri) {
401+
resources.push(uris);
402+
} else if (Array.isArray(uris)) {
403+
for (const uri of uris) {
404+
if (uri instanceof Uri) {
405+
resources.push(uri);
406+
}
407+
}
408+
}
409+
410+
if (!resources.length) {
411+
resources = await askForProjects(
412+
window.activeTextEditor?.document.uri,
413+
"Please select the project(s) to rebuild.",
414+
);
415+
if (!resources?.length) {
416+
return;
417+
}
418+
}
419+
420+
const params: BuildProjectParams = {
421+
identifiers: resources.map((u => {
422+
return { uri: u.toString() };
423+
})),
424+
// we can consider expose 'isFullBuild' according to users' feedback,
425+
// currently set it to true by default.
426+
isFullBuild: isFullBuild === undefined ? true : isFullBuild,
427+
};
428+
429+
return window.withProgress({ location: ProgressLocation.Window }, async p => {
430+
p.report({ message: 'Rebuilding projects...' });
431+
return new Promise(async (resolve, reject) => {
432+
const start = new Date().getTime();
433+
434+
let res: CompileWorkspaceStatus;
435+
try {
436+
res = token ? await this.languageClient.sendRequest(BuildProjectRequest.type, params, token) :
437+
await this.languageClient.sendRequest(BuildProjectRequest.type, params);
438+
} catch (error) {
439+
if (error && error.code === -32800) { // Check if the request is cancelled.
440+
res = CompileWorkspaceStatus.CANCELLED;
441+
}
442+
reject(error);
443+
}
444+
445+
const elapsed = new Date().getTime() - start;
446+
const humanVisibleDelay = elapsed < 1000 ? 1000 : 0;
447+
setTimeout(() => { // set a timeout so user would still see the message when build time is short
448+
resolve(res);
449+
}, humanVisibleDelay);
450+
});
451+
});
452+
}));
453+
398454
context.subscriptions.push(commands.registerCommand(Commands.COMPILE_WORKSPACE, (isFullCompile: boolean, token?: CancellationToken) => {
399455
return window.withProgress({ location: ProgressLocation.Window }, async p => {
400456
if (typeof isFullCompile !== 'boolean') {
@@ -588,7 +644,13 @@ function setIncompleteClasspathSeverity(severity: string) {
588644
async function projectConfigurationUpdate(languageClient: LanguageClient, uris?: Uri | Uri[]) {
589645
let resources = [];
590646
if (!uris) {
591-
resources = await askForProjectToUpdate();
647+
const activeFileUri: Uri | undefined = window.activeTextEditor?.document.uri;
648+
649+
if (activeFileUri && isJavaConfigFile(activeFileUri.fsPath)) {
650+
resources = [activeFileUri];
651+
} else {
652+
resources = await askForProjects(activeFileUri, "Please select the project(s) to update.");
653+
}
592654
} else if (uris instanceof Uri) {
593655
resources.push(uris);
594656
} else if (Array.isArray(uris)) {
@@ -611,21 +673,44 @@ async function projectConfigurationUpdate(languageClient: LanguageClient, uris?:
611673
}
612674
}
613675

614-
async function askForProjectToUpdate(): Promise<Uri[]> {
615-
let uriCandidate: Uri;
616-
if (window.activeTextEditor) {
617-
uriCandidate = window.activeTextEditor.document.uri;
618-
}
676+
/**
677+
* Ask user to select projects and return the selected projects' uris.
678+
* @param activeFileUri the uri of the active file.
679+
* @param placeHolder message to be shown in quick pick.
680+
*/
681+
async function askForProjects(activeFileUri: Uri | undefined, placeHolder: string): Promise<Uri[]> {
682+
const projectPicks: QuickPickItem[] = await generateProjectPicks(activeFileUri);
683+
if (!projectPicks?.length) {
684+
return [];
685+
} else if (projectPicks.length === 1) {
686+
return [Uri.file(projectPicks[0].detail)];
687+
} else {
688+
const choices: QuickPickItem[] | undefined = await window.showQuickPick(projectPicks, {
689+
matchOnDetail: true,
690+
placeHolder: placeHolder,
691+
ignoreFocusOut: true,
692+
canPickMany: true,
693+
});
619694

620-
if (uriCandidate && isJavaConfigFile(uriCandidate.fsPath)) {
621-
return [uriCandidate];
695+
if (choices?.length) {
696+
return choices.map(c => Uri.file(c.detail));
697+
}
622698
}
623699

700+
return [];
701+
}
702+
703+
/**
704+
* Generate the quick picks for projects selection. An `undefined` value will be return if
705+
* it's failed to generate picks.
706+
* @param activeFileUri the uri of the active document.
707+
*/
708+
async function generateProjectPicks(activeFileUri: Uri | undefined): Promise<QuickPickItem[] | undefined> {
624709
let projectUriStrings: string[];
625710
try {
626711
projectUriStrings = await commands.executeCommand<string[]>(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.GET_ALL_JAVA_PROJECTS);
627712
} catch (e) {
628-
return uriCandidate ? [uriCandidate] : [];
713+
return undefined;
629714
}
630715

631716
const projectPicks: QuickPickItem[] = projectUriStrings.map(uriString => {
@@ -640,40 +725,24 @@ async function askForProjectToUpdate(): Promise<Uri[]> {
640725
};
641726
}).filter(Boolean);
642727

643-
if (projectPicks.length === 0) {
644-
return [];
645-
} else if (projectPicks.length === 1) {
646-
return [Uri.file(projectPicks[0].detail)];
647-
} else {
648-
// pre-select an active project based on the uri candidate.
649-
if (uriCandidate) {
650-
const candidatePath = uriCandidate.fsPath;
651-
let belongingIndex = -1;
652-
for (let i = 0; i < projectPicks.length; i++) {
653-
if (candidatePath.startsWith(projectPicks[i].detail)) {
654-
if (belongingIndex < 0
655-
|| projectPicks[i].detail.length > projectPicks[belongingIndex].detail.length) {
656-
belongingIndex = i;
657-
}
728+
// pre-select an active project based on the uri candidate.
729+
if (activeFileUri?.scheme === "file") {
730+
const candidatePath = activeFileUri.fsPath;
731+
let belongingIndex = -1;
732+
for (let i = 0; i < projectPicks.length; i++) {
733+
if (candidatePath.startsWith(projectPicks[i].detail)) {
734+
if (belongingIndex < 0
735+
|| projectPicks[i].detail.length > projectPicks[belongingIndex].detail.length) {
736+
belongingIndex = i;
658737
}
659738
}
660-
if (belongingIndex >= 0) {
661-
projectPicks[belongingIndex].picked = true;
662-
}
663739
}
664-
665-
const choices: QuickPickItem[] | undefined = await window.showQuickPick(projectPicks, {
666-
matchOnDetail: true,
667-
placeHolder: "Please select the project(s) to update.",
668-
ignoreFocusOut: true,
669-
canPickMany: true,
670-
});
671-
if (choices && choices.length) {
672-
return choices.map(c => Uri.file(c.detail));
740+
if (belongingIndex >= 0) {
741+
projectPicks[belongingIndex].picked = true;
673742
}
674743
}
675744

676-
return [];
745+
return projectPicks;
677746
}
678747

679748
function isJavaConfigFile(filePath: string) {

0 commit comments

Comments
 (0)