Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/json-language-features/client/src/node/jsonClientMain.ts
3322 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Disposable, ExtensionContext, LogOutputChannel, window, l10n, env, LogLevel } from 'vscode';
7
import { startClient, LanguageClientConstructor, SchemaRequestService, languageServerDescription, AsyncDisposable } from '../jsonClient';
8
import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node';
9
10
import { promises as fs } from 'fs';
11
import * as path from 'path';
12
import { xhr, XHRResponse, getErrorStatusDescription, Headers } from 'request-light';
13
14
import TelemetryReporter from '@vscode/extension-telemetry';
15
import { JSONSchemaCache } from './schemaCache';
16
17
let client: AsyncDisposable | undefined;
18
19
// this method is called when vs code is activated
20
export async function activate(context: ExtensionContext) {
21
const clientPackageJSON = await getPackageInfo(context);
22
const telemetry = new TelemetryReporter(clientPackageJSON.aiKey);
23
context.subscriptions.push(telemetry);
24
25
const logOutputChannel = window.createOutputChannel(languageServerDescription, { log: true });
26
context.subscriptions.push(logOutputChannel);
27
28
const serverMain = `./server/${clientPackageJSON.main.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/jsonServerMain`;
29
const serverModule = context.asAbsolutePath(serverMain);
30
31
// The debug options for the server
32
const debugOptions = { execArgv: ['--nolazy', '--inspect=' + (6000 + Math.round(Math.random() * 999))] };
33
34
// If the extension is launch in debug mode the debug server options are use
35
// Otherwise the run options are used
36
const serverOptions: ServerOptions = {
37
run: { module: serverModule, transport: TransportKind.ipc },
38
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
39
};
40
41
const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => {
42
return new LanguageClient(id, name, serverOptions, clientOptions);
43
};
44
45
const timer = {
46
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable {
47
const handle = setTimeout(callback, ms, ...args);
48
return { dispose: () => clearTimeout(handle) };
49
}
50
};
51
52
// pass the location of the localization bundle to the server
53
process.env['VSCODE_L10N_BUNDLE_LOCATION'] = l10n.uri?.toString() ?? '';
54
55
const schemaRequests = await getSchemaRequestService(context, logOutputChannel);
56
57
client = await startClient(context, newLanguageClient, { schemaRequests, telemetry, timer, logOutputChannel });
58
}
59
60
export async function deactivate(): Promise<any> {
61
if (client) {
62
await client.dispose();
63
client = undefined;
64
}
65
}
66
67
interface IPackageInfo {
68
name: string;
69
version: string;
70
aiKey: string;
71
main: string;
72
}
73
74
async function getPackageInfo(context: ExtensionContext): Promise<IPackageInfo> {
75
const location = context.asAbsolutePath('./package.json');
76
try {
77
return JSON.parse((await fs.readFile(location)).toString());
78
} catch (e) {
79
console.log(`Problems reading ${location}: ${e}`);
80
return { name: '', version: '', aiKey: '', main: '' };
81
}
82
}
83
84
const retryTimeoutInHours = 2 * 24; // 2 days
85
86
async function getSchemaRequestService(context: ExtensionContext, log: LogOutputChannel): Promise<SchemaRequestService> {
87
let cache: JSONSchemaCache | undefined = undefined;
88
const globalStorage = context.globalStorageUri;
89
90
let clearCache: (() => Promise<string[]>) | undefined;
91
if (globalStorage.scheme === 'file') {
92
const schemaCacheLocation = path.join(globalStorage.fsPath, 'json-schema-cache');
93
await fs.mkdir(schemaCacheLocation, { recursive: true });
94
95
const schemaCache = new JSONSchemaCache(schemaCacheLocation, context.globalState);
96
log.trace(`[json schema cache] initial state: ${JSON.stringify(schemaCache.getCacheInfo(), null, ' ')}`);
97
cache = schemaCache;
98
clearCache = async () => {
99
const cachedSchemas = await schemaCache.clearCache();
100
log.trace(`[json schema cache] cache cleared. Previously cached schemas: ${cachedSchemas.join(', ')}`);
101
return cachedSchemas;
102
};
103
}
104
105
106
const isXHRResponse = (error: any): error is XHRResponse => typeof error?.status === 'number';
107
108
const request = async (uri: string, etag?: string): Promise<string> => {
109
const headers: Headers = {
110
'Accept-Encoding': 'gzip, deflate',
111
'User-Agent': `${env.appName} (${env.appHost})`
112
};
113
if (etag) {
114
headers['If-None-Match'] = etag;
115
}
116
try {
117
log.trace(`[json schema cache] Requesting schema ${uri} etag ${etag}...`);
118
119
const response = await xhr({ url: uri, followRedirects: 5, headers });
120
if (cache) {
121
const etag = response.headers['etag'];
122
if (typeof etag === 'string') {
123
log.trace(`[json schema cache] Storing schema ${uri} etag ${etag} in cache`);
124
await cache.putSchema(uri, etag, response.responseText);
125
} else {
126
log.trace(`[json schema cache] Response: schema ${uri} no etag`);
127
}
128
}
129
return response.responseText;
130
} catch (error: unknown) {
131
if (isXHRResponse(error)) {
132
if (error.status === 304 && etag && cache) {
133
134
log.trace(`[json schema cache] Response: schema ${uri} unchanged etag ${etag}`);
135
136
const content = await cache.getSchema(uri, etag, true);
137
if (content) {
138
log.trace(`[json schema cache] Get schema ${uri} etag ${etag} from cache`);
139
return content;
140
}
141
return request(uri);
142
}
143
144
let status = getErrorStatusDescription(error.status);
145
if (status && error.responseText) {
146
status = `${status}\n${error.responseText.substring(0, 200)}`;
147
}
148
if (!status) {
149
status = error.toString();
150
}
151
log.trace(`[json schema cache] Respond schema ${uri} error ${status}`);
152
153
throw status;
154
}
155
throw error;
156
}
157
};
158
159
return {
160
getContent: async (uri: string) => {
161
if (cache && /^https?:\/\/(json|www)\.schemastore\.org\//.test(uri)) {
162
const content = await cache.getSchemaIfUpdatedSince(uri, retryTimeoutInHours);
163
if (content) {
164
if (log.logLevel === LogLevel.Trace) {
165
log.trace(`[json schema cache] Schema ${uri} from cache without request (last accessed ${cache.getLastUpdatedInHours(uri)} hours ago)`);
166
}
167
168
return content;
169
}
170
}
171
return request(uri, cache?.getETag(uri));
172
},
173
clearCache
174
};
175
}
176
177