-
Notifications
You must be signed in to change notification settings - Fork 528
Expand file tree
/
Copy pathproviderDispatcher.ts
More file actions
221 lines (192 loc) · 8.68 KB
/
providerDispatcher.ts
File metadata and controls
221 lines (192 loc) · 8.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
'use strict';
import { CancellationToken, commands, DocumentSymbol, DocumentSymbolProvider, Event, ExtensionContext, Hover, HoverProvider, languages, MarkdownString, MarkedString, Position, Range, SymbolInformation, SymbolKind, TextDocument, TextDocumentContentProvider, Uri, workspace, WorkspaceSymbolProvider } from "vscode";
import { DocumentSymbol as clientDocumentSymbol, DocumentSymbolRequest, HoverRequest, SymbolInformation as clientSymbolInformation, WorkspaceSymbolRequest } from "vscode-languageclient";
import { LanguageClient } from "vscode-languageclient/node";
import { apiManager } from "./apiManager";
import { Commands } from "./commands";
import { fixJdtLinksInDocumentation, getActiveLanguageClient } from "./extension";
import { createClientHoverProvider } from "./hoverAction";
import { ClassFileContentsRequest } from "./protocol";
import { ServerMode } from "./settings";
export interface ProviderOptions {
contentProviderEvent: Event<Uri>;
}
export interface ProviderHandle {
handles: any[];
}
export function registerClientProviders(context: ExtensionContext, options: ProviderOptions): ProviderHandle {
const hoverProvider = new ClientHoverProvider();
context.subscriptions.push(languages.registerHoverProvider('java', hoverProvider));
const symbolProvider = createDocumentSymbolProvider();
context.subscriptions.push(languages.registerDocumentSymbolProvider('java', symbolProvider));
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, classProvider]
};
}
export class ClientHoverProvider implements HoverProvider {
private delegateProvider;
async provideHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover> {
const languageClient: LanguageClient | undefined = await getActiveLanguageClient();
if (!languageClient) {
return undefined;
}
const serverMode: ServerMode = apiManager.getApiInstance().serverMode;
if (serverMode === ServerMode.standard) {
if (!this.delegateProvider) {
this.delegateProvider = createClientHoverProvider(languageClient);
}
const hover = await this.delegateProvider.provideHover(document, position, token);
return fixJdtSchemeHoverLinks(hover);
} else {
const params = {
textDocument: languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document),
position: languageClient.code2ProtocolConverter.asPosition(position)
};
const hoverResponse = await languageClient.sendRequest(HoverRequest.type, params, token);
const hover = languageClient.protocol2CodeConverter.asHover(hoverResponse);
return fixJdtSchemeHoverLinks(hover);
}
}
}
function createJDTContentProvider(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 '';
}
return languageClient.sendRequest(ClassFileContentsRequest.type, { uri: uri.toString() }, token).then((v: string): string => {
return v || '';
});
}
};
}
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 '';
}
const originalUri = uri.toString().replace(/^class/, "file");
const decompiledContent: string = await commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.GET_DECOMPILED_SOURCE, originalUri);
if (!decompiledContent) {
console.log(`Error while getting decompiled source : ${originalUri}`);
return "Error while getting decompiled source.";
} else {
return decompiledContent;
}
}
};
}
function createDocumentSymbolProvider(): DocumentSymbolProvider {
return <DocumentSymbolProvider>{
provideDocumentSymbols: async (document: TextDocument, token: CancellationToken): Promise<SymbolInformation[] | DocumentSymbol[]> => {
const languageClient: LanguageClient | undefined = await getActiveLanguageClient();
if (!languageClient) {
return [];
}
const params = {
textDocument: languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document),
};
const symbolResponse = await languageClient.sendRequest(DocumentSymbolRequest.type, params, token);
if (!symbolResponse || !symbolResponse.length) {
return [];
}
if ((<any>symbolResponse[0]).containerName) {
return languageClient.protocol2CodeConverter.asSymbolInformations(<clientSymbolInformation[]>symbolResponse);
}
return languageClient.protocol2CodeConverter.asDocumentSymbols(<clientDocumentSymbol[]>symbolResponse);
}
};
}
const START_OF_DOCUMENT = new Range(new Position(0, 0), new Position(0, 0));
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
// https://github.com/microsoft/vscode/issues/98125
const result = existingWorkspaceSymbolProvider.provideWorkspaceSymbols(query, token);
if (query.indexOf('.') > -1) { // seems like a qualified name
return new Promise<SymbolInformation[]>((resolve) => {
((result as Promise<SymbolInformation[]>)).then((symbols) => {
if (symbols === null) {
resolve(null);
} else {
resolve(symbols?.map((s) => {
s.name = `${s.containerName}.${s.name}`;
return s;
}));
}
});
});
}
return result;
},
resolveWorkspaceSymbol: async (symbol: SymbolInformation, token: CancellationToken): Promise<SymbolInformation> => {
const range = symbol.location.range;
if (range && !range.isEqual(START_OF_DOCUMENT)) {
return symbol;
}
const languageClient = await getActiveLanguageClient();
const serializableSymbol = {
name: symbol.name,
// Cannot serialize SymbolKind as number, because GSON + lsp4j.SymbolKind expect a name.
kind: SymbolKind[symbol.kind],
location: {
uri: languageClient.code2ProtocolConverter.asUri(symbol.location.uri),
range: languageClient.code2ProtocolConverter.asRange(symbol.location.range)
},
containerName: symbol.containerName
};
const response = await commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.RESOLVE_WORKSPACE_SYMBOL, JSON.stringify(serializableSymbol));
if (token.isCancellationRequested) {
return undefined;
}
return languageClient.protocol2CodeConverter.asSymbolInformation(response as clientSymbolInformation);
}
};
}
function overwriteWorkspaceSymbolProvider(context: ExtensionContext): void {
const disposable = apiManager.getApiInstance().onDidServerModeChange(async (mode) => {
if (mode === ServerMode.standard) {
const feature = (await getActiveLanguageClient()).getFeature(WorkspaceSymbolRequest.method);
const providers = feature.getProviders();
if (providers && providers.length > 0) {
feature.clear();
const workspaceSymbolProvider = createWorkspaceSymbolProvider(providers[0]);
context.subscriptions.push(languages.registerWorkspaceSymbolProvider(workspaceSymbolProvider));
disposable.dispose();
}
}
});
}
/**
* Returns the hover with all jdt:// links replaced with a command:// link that opens the jdt URI.
*
* VS Code doesn't render links with the `jdt` scheme in hover popups.
* To get around this, you can create a command:// link that invokes a command that opens the corresponding URI.
* VS Code will render command:// links in hover pop ups if they are marked as trusted.
*
* @param hover The hover to fix the jdt:// links for
* @returns the hover with all jdt:// links replaced with a command:// link that opens the jdt URI
*/
export function fixJdtSchemeHoverLinks(hover: Hover): Hover {
const newContents: (MarkedString | MarkdownString)[] = [];
for (const content of hover.contents) {
if (content instanceof MarkdownString) {
newContents.push(fixJdtLinksInDocumentation(content));
} else {
newContents.push(content);
}
}
hover.contents = newContents;
return hover;
}