Skip to content

Commit a60b5e4

Browse files
Reload the cached classfile sources when new source attachment is updated for them (#3207)
* Reload the cached classfile sources when new source attachment is updated for them Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
1 parent 080f5d9 commit a60b5e4

6 files changed

Lines changed: 150 additions & 25 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ The following settings are supported:
235235
* `java.completion.lazyResolveTextEdit.enabled`: [Experimental] Enable/disable lazily resolving text edits for code completion. Defaults to `true`.
236236
* `java.edit.validateAllOpenBuffersOnChanges`: Specifies whether to recheck all open Java files for diagnostics when editing a Java file. Defaults to `false`.
237237

238+
New in 1.21.0
239+
* `java.editor.reloadChangedSources`: Specifies whether to reload the sources of the open class files when their source jar files are changed. Defaults to `ask`.
240+
- `ask`: Ask to reload the sources of the open class files
241+
- `auto`: Automatically reload the sources of the open class files
242+
- `manual`: Manually reload the sources of the open class files
243+
238244
Semantic Highlighting
239245
===============
240246
[Semantic Highlighting](https://github.com/redhat-developer/vscode-java/wiki/Semantic-Highlighting) fixes numerous syntax highlighting issues with the default Java Textmate grammar. However, you might experience a few minor issues, particularly a delay when it kicks in, as it needs to be computed by the Java Language server, when opening a new file or when typing. Semantic highlighting can be disabled for all languages using the `editor.semanticHighlighting.enabled` setting, or for Java only using [language-specific editor settings](https://code.visualstudio.com/docs/getstarted/settings#_languagespecific-editor-settings).

package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,22 @@
11391139
"default": false,
11401140
"markdownDescription": "Specifies whether to recheck all open Java files for diagnostics when editing a Java file.",
11411141
"scope": "window"
1142+
},
1143+
"java.editor.reloadChangedSources": {
1144+
"type": "string",
1145+
"enum": [
1146+
"ask",
1147+
"auto",
1148+
"manual"
1149+
],
1150+
"enumDescriptions": [
1151+
"Ask to reload the sources of the open class files",
1152+
"Automatically reload the sources of the open class files",
1153+
"Manually reload the sources of the open class files"
1154+
],
1155+
"default": "ask",
1156+
"markdownDescription": "Specifies whether to reload the sources of the open class files when their source jar files are changed.",
1157+
"scope": "window"
11421158
}
11431159
}
11441160
},

src/apiManager.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
import { ExtensionAPI, ClasspathQueryOptions, ClasspathResult, extensionApiVersion, ClientStatus } from "./extension.api";
3+
import { ExtensionAPI, ClasspathQueryOptions, ClasspathResult, extensionApiVersion, ClientStatus, SourceInvalidatedEvent } from "./extension.api";
44
import { RequirementsData } from "./requirements";
55
import { GetDocumentSymbolsCommand, getDocumentSymbolsProvider } from "./documentSymbols";
66
import { GoToDefinitionCommand, goToDefinitionProvider } from "./goToDefinition";
@@ -18,6 +18,7 @@ class ApiManager {
1818
private onDidServerModeChangeEmitter: Emitter<ServerMode> = new Emitter<ServerMode>();
1919
private onDidProjectsImportEmitter: Emitter<Uri[]> = new Emitter<Uri[]>();
2020
private traceEventEmitter: Emitter<any> = new Emitter<any>();
21+
private sourceInvalidatedEventEmitter: Emitter<SourceInvalidatedEvent> = new Emitter<SourceInvalidatedEvent>();
2122
private serverReadyPromiseResolve: (result: boolean) => void;
2223

2324
public initialize(requirements: RequirementsData, serverMode: ServerMode): void {
@@ -65,6 +66,7 @@ class ApiManager {
6566
serverReady,
6667
onDidRequestEnd,
6768
trackEvent: traceEvent,
69+
onDidSourceInvalidate: this.sourceInvalidatedEventEmitter.event,
6870
};
6971
}
7072

@@ -92,6 +94,10 @@ class ApiManager {
9294
this.traceEventEmitter.fire(event);
9395
}
9496

97+
public fireSourceInvalidatedEvent(event: SourceInvalidatedEvent): void {
98+
this.sourceInvalidatedEventEmitter.fire(event);
99+
}
100+
95101
public updateServerMode(mode: ServerMode): void {
96102
this.api.serverMode = mode;
97103
}

src/extension.api.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,18 @@ export interface TraceEvent {
9292
resultLength?: number | undefined;
9393
}
9494

95-
export const extensionApiVersion = '0.9';
95+
export interface SourceInvalidatedEvent {
96+
/**
97+
* The paths of the jar files that are linked to new source attachments.
98+
*/
99+
affectedRootPaths: string[];
100+
/**
101+
* The opened editors with updated source.
102+
*/
103+
affectedEditorDocuments?: Uri[];
104+
}
105+
106+
export const extensionApiVersion = '0.10';
96107

97108
export interface ExtensionAPI {
98109
readonly apiVersion: string;
@@ -152,4 +163,14 @@ export interface ExtensionAPI {
152163
* @since extension version 1.20.0
153164
*/
154165
readonly trackEvent: Event<any>;
166+
167+
/**
168+
* An event that occurs when the package fragment roots have updated source attachments.
169+
* The client should refresh the new source if it has previously requested the source
170+
* from them.
171+
*
172+
* @since API version 0.10
173+
* @since extension version 1.21.0
174+
*/
175+
readonly onDidSourceInvalidate: Event<SourceInvalidatedEvent>;
155176
}

src/protocol.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export enum EventType {
5959
classpathUpdated = 100,
6060
projectsImported = 200,
6161
incompatibleGradleJdkIssue = 300,
62-
upgradeGradleWrapper = 400,
62+
upgradeGradleWrapper = 400,
63+
sourceInvalidated = 500,
6364
}
6465

6566
export enum CompileWorkspaceStatus {
@@ -76,25 +77,25 @@ export enum AccessorKind {
7677
}
7778

7879
export interface StatusReport {
79-
message: string;
80-
type: string;
80+
message: string;
81+
type: string;
8182
}
8283

8384
export interface ProgressReport {
84-
id: string;
85-
task: string;
86-
subTask: string;
87-
status: string;
88-
workDone: number;
89-
totalWork: number;
90-
complete: boolean;
85+
id: string;
86+
task: string;
87+
subTask: string;
88+
status: string;
89+
workDone: number;
90+
totalWork: number;
91+
complete: boolean;
9192
}
9293

9394
export interface ActionableMessage {
94-
severity: MessageType;
95-
message: string;
96-
data?: any;
97-
commands?: Command[];
95+
severity: MessageType;
96+
message: string;
97+
data?: any;
98+
commands?: Command[];
9899
}
99100

100101
export interface EventNotification {
@@ -103,11 +104,11 @@ export interface EventNotification {
103104
}
104105

105106
export namespace StatusNotification {
106-
export const type = new NotificationType<StatusReport>('language/status');
107+
export const type = new NotificationType<StatusReport>('language/status');
107108
}
108109

109110
export namespace ProgressReportNotification {
110-
export const type = new NotificationType<ProgressReport>('language/progressReport');
111+
export const type = new NotificationType<ProgressReport>('language/progressReport');
111112
}
112113

113114
export namespace ClassFileContentsRequest {
@@ -179,8 +180,8 @@ export interface OverridableMethod {
179180
}
180181

181182
export interface OverridableMethodsResponse {
182-
type: string;
183-
methods: OverridableMethod[];
183+
type: string;
184+
methods: OverridableMethod[];
184185
}
185186

186187
export namespace ListOverridableMethodsRequest {
@@ -443,9 +444,9 @@ export interface GradleCompatibilityInfo {
443444
}
444445

445446
export interface UpgradeGradleWrapperInfo {
446-
projectUri: string;
447-
message: string;
448-
recommendedGradleVersion: string;
447+
projectUri: string;
448+
message: string;
449+
recommendedGradleVersion: string;
449450
}
450451

451452
export interface Member {
@@ -470,5 +471,14 @@ export interface ValidateDocumentParams {
470471
}
471472

472473
export namespace ValidateDocumentNotification {
473-
export const type = new NotificationType<ValidateDocumentParams>('java/validateDocument');
474+
export const type = new NotificationType<ValidateDocumentParams>('java/validateDocument');
475+
}
476+
477+
export interface SourceInvalidatedEvent {
478+
/**
479+
* The package fragment roots that get new source attachments.
480+
* The key is its root path, the value means if its source is
481+
* automatically downloaded.
482+
*/
483+
affectedRootPaths: { [key: string]: boolean };
474484
}

src/standardLanguageClient.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import * as pasteAction from './pasteAction';
2222
import { registerPasteEventHandler } from './pasteEventHandler';
2323
import { collectBuildFilePattern, onExtensionChange } from "./plugin";
2424
import { pomCodeActionMetadata, PomCodeActionProvider } from "./pom/pomCodeActionProvider";
25-
import { ActionableNotification, BuildProjectParams, BuildProjectRequest, CompileWorkspaceRequest, CompileWorkspaceStatus, EventNotification, EventType, ExecuteClientCommandRequest, FeatureStatus, FindLinks, GradleCompatibilityInfo, LinkLocation, ProgressReportNotification, ServerNotification, SourceAttachmentAttribute, SourceAttachmentRequest, SourceAttachmentResult, StatusNotification, UpgradeGradleWrapperInfo } from "./protocol";
25+
import { ActionableNotification, BuildProjectParams, BuildProjectRequest, CompileWorkspaceRequest, CompileWorkspaceStatus, EventNotification, EventType, ExecuteClientCommandRequest, FeatureStatus, FindLinks, GradleCompatibilityInfo, LinkLocation, ProgressReportNotification, ServerNotification, SourceAttachmentAttribute, SourceAttachmentRequest, SourceAttachmentResult, SourceInvalidatedEvent, StatusNotification, UpgradeGradleWrapperInfo } from "./protocol";
2626
import * as refactorAction from './refactorAction';
2727
import { getJdkUrl, RequirementsData, sortJdksBySource, sortJdksByVersion } from "./requirements";
2828
import { serverStatus, ServerStatusKind } from "./serverStatus";
@@ -237,6 +237,23 @@ export class StandardLanguageClient {
237237
});
238238
}
239239
break;
240+
case EventType.sourceInvalidated:
241+
const result = notification.data as SourceInvalidatedEvent;
242+
const triggeredByUser: string[] = [];
243+
const triggeredByAutoDownloadedSource: string[] = [];
244+
Object.entries(result.affectedRootPaths || {})?.forEach(([key, value]) => {
245+
if (value) {
246+
triggeredByAutoDownloadedSource.push(key);
247+
} else {
248+
triggeredByUser.push(key);
249+
}
250+
});
251+
if (triggeredByUser?.length) {
252+
this.handleSourceInvalidatedEvent(triggeredByUser, false, jdtEventEmitter);
253+
}
254+
if (triggeredByAutoDownloadedSource?.length) {
255+
this.handleSourceInvalidatedEvent(triggeredByAutoDownloadedSource, true, jdtEventEmitter);
256+
}
240257
default:
241258
break;
242259
}
@@ -652,6 +669,55 @@ export class StandardLanguageClient {
652669
public getClientStatus(): ClientStatus {
653670
return this.status;
654671
}
672+
673+
private async handleSourceInvalidatedEvent(jars: string[], isAutoDownloadSource: boolean, jdtContentProviderEventEmitter: EventEmitter<Uri>): Promise<void> {
674+
const changedJarNames: Set<string> = new Set();
675+
for (const jar of jars) {
676+
const path = jar.split(/\/|\\/);
677+
if (path?.length) {
678+
changedJarNames.add(path[path.length - 1]);
679+
}
680+
}
681+
682+
const affectedDocumentUris: Uri[] = [];
683+
const affectedDocumentNames: string[] = [];
684+
workspace.textDocuments.forEach(document => {
685+
// Here is a sample jdt uri for classfile:
686+
// jdt://contents/rt.jar/java.lang/System.class?...
687+
if (document.uri.scheme === "jdt") {
688+
const paths = document.uri.path?.split(/\/|\\/);
689+
if (paths?.[1] && changedJarNames.has(paths[1])) {
690+
affectedDocumentNames.push(paths[paths.length - 1]);
691+
affectedDocumentUris.push(document.uri);
692+
}
693+
}
694+
});
695+
if (affectedDocumentUris.length) {
696+
if (isAutoDownloadSource) {
697+
const reloadSources = workspace.getConfiguration().get("java.editor.reloadChangedSources");
698+
if (reloadSources === "manual") {
699+
return;
700+
}
701+
702+
if (reloadSources === "ask") {
703+
const choice = await window.showWarningMessage(`The following class(es): ${affectedDocumentNames.map(name => `'${name}'`).join(", ")} ` +
704+
"have new source jar available on the local Maven repository. Do you want to reload them?", "Yes", "Always", "No");
705+
if (choice === "Always") {
706+
workspace.getConfiguration().update("java.editor.reloadChangedSources", "auto", ConfigurationTarget.Global);
707+
} else if (choice !== "Yes") {
708+
return;
709+
}
710+
}
711+
}
712+
affectedDocumentUris.forEach(classFileUri => {
713+
jdtContentProviderEventEmitter.fire(classFileUri);
714+
});
715+
}
716+
apiManager.fireSourceInvalidatedEvent({
717+
affectedRootPaths: jars,
718+
affectedEditorDocuments: affectedDocumentUris,
719+
});
720+
}
655721
}
656722

657723
async function showImportFinishNotification(context: ExtensionContext) {

0 commit comments

Comments
 (0)