Skip to content

Commit 7be69a0

Browse files
authored
Bobbrow/recursive includes (#2136)
* Make recursive includePath the default again. Refactor A/B testing stuff. * fix initialization dependency bug * bugfix
1 parent d77470f commit 7be69a0

4 files changed

Lines changed: 137 additions & 85 deletions

File tree

Extension/cpptools.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"intelliSenseEngine_default_percentage": 100
2+
"intelliSenseEngine_default_percentage": 100,
3+
"defaultIntelliSenseEngine": 100,
4+
"recursiveIncludes": 100
35
}

Extension/src/LanguageServer/configurations.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as util from '../common';
1111
import * as telemetry from '../telemetry';
1212
import { PersistentFolderState } from './persistentState';
1313
import { CppSettings } from './settings';
14+
import { ABTestSettings, getABTestSettings } from '../abTesting';
1415
const configVersion: number = 4;
1516

1617
// No properties are set in the config since we want to apply vscode settings first (if applicable).
@@ -207,7 +208,9 @@ export class CppProperties {
207208

208209
if (!settings.defaultIncludePath) {
209210
// We don't add system includes to the includePath anymore. The language server has this information.
210-
configuration.includePath = ["${workspaceFolder}"].concat(this.vcpkgIncludes);
211+
let abTestSettings: ABTestSettings = getABTestSettings();
212+
let rootFolder: string = abTestSettings.UseRecursiveIncludes ? "${workspaceFolder}/**" : "${workspaceFolder}";
213+
configuration.includePath = [rootFolder].concat(this.vcpkgIncludes);
211214
}
212215
// browse.path is not set by default anymore. When it is not set, the includePath will be used instead.
213216
if (!settings.defaultDefines) {

Extension/src/abTesting.ts

Lines changed: 115 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,88 +9,135 @@ import * as https from 'https';
99
import { ClientRequest } from 'http';
1010
import * as vscode from 'vscode';
1111
import * as fs from 'fs';
12-
import * as util from './common';
1312

13+
import * as util from './common';
1414
import * as Telemetry from './telemetry';
15+
import { PersistentState } from './LanguageServer/persistentState';
1516

1617
const userBucketMax: number = 100;
1718
const userBucketString: string = "CPP.UserBucket";
19+
const localConfigFile: string = "cpptools.json";
1820

19-
export function activate(context: vscode.ExtensionContext): void {
20-
if (context.globalState.get<number>(userBucketString, -1) === -1) {
21-
let bucket: number = Math.floor(Math.random() * userBucketMax) + 1; // Range is [1, userBucketMax].
22-
context.globalState.update(userBucketString, bucket);
23-
}
21+
interface Settings {
22+
defaultIntelliSenseEngine?: number;
23+
recursiveIncludes?: number;
24+
}
25+
26+
export class ABTestSettings {
27+
private settings: Settings;
28+
private intelliSenseEngineDefault: PersistentState<number>;
29+
private recursiveIncludesDefault: PersistentState<number>;
30+
private bucket: PersistentState<number>;
31+
32+
constructor() {
33+
this.intelliSenseEngineDefault = new PersistentState<number>("ABTest.1", 100);
34+
this.recursiveIncludesDefault = new PersistentState<number>("ABTest.2", 100);
35+
this.settings = {
36+
defaultIntelliSenseEngine: this.intelliSenseEngineDefault.Value,
37+
recursiveIncludes: this.recursiveIncludesDefault.Value
38+
};
39+
this.bucket = new PersistentState<number>(userBucketString, -1);
40+
if (this.bucket.Value === -1) {
41+
this.bucket.Value = Math.floor(Math.random() * userBucketMax) + 1; // Range is [1, userBucketMax].
42+
}
43+
44+
this.updateSettingsAsync().then(() => {
45+
// Redownload cpptools.json after initialization so it's not blocked.
46+
// It'll be used the next time the extension reloads.
47+
this.downloadCpptoolsJsonPkgAsync();
48+
});
2449

25-
setInterval(() => {
2650
// Redownload occasionally to prevent an extra reload during long sessions.
27-
downloadCpptoolsJsonPkg();
28-
}, 30 * 60 * 1000); // 30 minutes.
29-
}
51+
setInterval(() => { this.downloadCpptoolsJsonPkgAsync(); }, 30 * 60 * 1000); // 30 minutes.
52+
}
53+
54+
public get UseDefaultIntelliSenseEngine(): boolean {
55+
return this.settings.defaultIntelliSenseEngine ? this.settings.defaultIntelliSenseEngine >= this.bucket.Value : true;
56+
}
57+
58+
public get UseRecursiveIncludes(): boolean {
59+
return this.settings.recursiveIncludes ? this.settings.recursiveIncludes >= this.bucket.Value : true;
60+
}
3061

31-
// NOTE: Code is copied from DownloadPackage in packageManager.ts, but with ~75% fewer lines.
32-
function downloadCpptoolsJson(urlString): Promise<void> {
33-
return new Promise<void>((resolve, reject) => {
34-
let parsedUrl: url.Url = url.parse(urlString);
35-
let request: ClientRequest = https.request({
36-
host: parsedUrl.host,
37-
path: parsedUrl.path,
38-
agent: util.getHttpsProxyAgent(),
39-
rejectUnauthorized: vscode.workspace.getConfiguration().get("http.proxyStrictSSL", true)
40-
}, (response) => {
41-
if (response.statusCode === 301 || response.statusCode === 302) {
42-
let redirectUrl: string | string[];
43-
if (typeof response.headers.location === "string") {
44-
redirectUrl = response.headers.location;
45-
} else {
46-
redirectUrl = response.headers.location[0];
62+
private async updateSettingsAsync(): Promise<void> {
63+
const cpptoolsJsonFile: string = util.getExtensionFilePath(localConfigFile);
64+
65+
try {
66+
const exists: boolean = await util.checkFileExists(cpptoolsJsonFile);
67+
if (exists) {
68+
const fileContent: string = await util.readFileText(cpptoolsJsonFile);
69+
let newSettings: Settings = <Settings>JSON.parse(fileContent);
70+
if (newSettings.defaultIntelliSenseEngine) {
71+
this.intelliSenseEngineDefault.Value = newSettings.defaultIntelliSenseEngine;
4772
}
48-
return resolve(downloadCpptoolsJson(redirectUrl)); // Redirect - download from new location
49-
}
50-
if (response.statusCode !== 200) {
51-
return reject();
73+
if (newSettings.recursiveIncludes) {
74+
this.recursiveIncludesDefault.Value = newSettings.recursiveIncludes;
75+
}
76+
this.settings = {
77+
defaultIntelliSenseEngine: this.intelliSenseEngineDefault.Value,
78+
recursiveIncludes: this.recursiveIncludesDefault.Value
79+
};
5280
}
53-
let downloadedBytes = 0; // tslint:disable-line
54-
let cppToolsJsonFile: fs.WriteStream = fs.createWriteStream(util.getExtensionFilePath("cpptools.json"));
55-
response.on('data', (data) => { downloadedBytes += data.length; });
56-
response.on('end', () => { cppToolsJsonFile.close(); });
57-
cppToolsJsonFile.on('close', () => { resolve(); });
58-
response.on('error', (error) => { reject(); });
59-
response.pipe(cppToolsJsonFile, { end: false });
60-
});
61-
request.on('error', (error) => { reject(); });
62-
request.end();
63-
});
64-
}
81+
} catch (error) {
82+
// Ignore any cpptoolsJsonFile errors
83+
}
84+
}
6585

66-
export function downloadCpptoolsJsonPkg(): Promise<void> {
67-
let hasError: boolean = false;
68-
let telemetryProperties: { [key: string]: string } = {};
69-
return downloadCpptoolsJson("https://go.microsoft.com/fwlink/?linkid=852750")
70-
.catch((error) => {
71-
// More specific error info is not likely to be helpful, and we get detailed download data from the initial install.
72-
hasError = true;
73-
})
74-
.then(() => {
75-
telemetryProperties['success'] = (!hasError).toString();
76-
Telemetry.logDebuggerEvent("cpptoolsJsonDownload", telemetryProperties);
86+
// NOTE: Code is copied from DownloadPackage in packageManager.ts, but with ~75% fewer lines.
87+
private downloadCpptoolsJsonAsync(urlString): Promise<void> {
88+
return new Promise<void>((resolve, reject) => {
89+
let parsedUrl: url.Url = url.parse(urlString);
90+
let request: ClientRequest = https.request({
91+
host: parsedUrl.host,
92+
path: parsedUrl.path,
93+
agent: util.getHttpsProxyAgent(),
94+
rejectUnauthorized: vscode.workspace.getConfiguration().get("http.proxyStrictSSL", true)
95+
}, (response) => {
96+
if (response.statusCode === 301 || response.statusCode === 302) {
97+
let redirectUrl: string | string[];
98+
if (typeof response.headers.location === "string") {
99+
redirectUrl = response.headers.location;
100+
} else {
101+
redirectUrl = response.headers.location[0];
102+
}
103+
return resolve(this.downloadCpptoolsJsonAsync(redirectUrl)); // Redirect - download from new location
104+
}
105+
if (response.statusCode !== 200) {
106+
return reject();
107+
}
108+
let downloadedBytes = 0; // tslint:disable-line
109+
let cppToolsJsonFile: fs.WriteStream = fs.createWriteStream(util.getExtensionFilePath(localConfigFile));
110+
response.on('data', (data) => { downloadedBytes += data.length; });
111+
response.on('end', () => { cppToolsJsonFile.close(); });
112+
cppToolsJsonFile.on('close', () => { resolve(); this.updateSettingsAsync(); });
113+
response.on('error', (error) => { reject(); });
114+
response.pipe(cppToolsJsonFile, { end: false });
115+
});
116+
request.on('error', (error) => { reject(); });
117+
request.end();
77118
});
119+
}
120+
121+
private downloadCpptoolsJsonPkgAsync(): Promise<void> {
122+
let hasError: boolean = false;
123+
let telemetryProperties: { [key: string]: string } = {};
124+
return this.downloadCpptoolsJsonAsync("https://go.microsoft.com/fwlink/?linkid=852750")
125+
.catch((error) => {
126+
// More specific error info is not likely to be helpful, and we get detailed download data from the initial install.
127+
hasError = true;
128+
})
129+
.then(() => {
130+
telemetryProperties['success'] = (!hasError).toString();
131+
Telemetry.logDebuggerEvent("cpptoolsJsonDownload", telemetryProperties);
132+
});
133+
}
78134
}
79135

80-
export function processCpptoolsJson(cpptoolsString: string): Promise<void> {
81-
let cpptoolsObject: any = JSON.parse(cpptoolsString);
82-
let intelliSenseEnginePercentage: number = cpptoolsObject.intelliSenseEngine_default_percentage;
83-
let packageJson: any = util.getRawPackageJson();
136+
let settings: ABTestSettings;
84137

85-
if (!packageJson.extensionFolderPath.includes(".vscode-insiders")) {
86-
let prevIntelliSenseEngineDefault: any = packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default;
87-
if (util.extensionContext.globalState.get<number>(userBucketString, userBucketMax + 1) <= intelliSenseEnginePercentage) {
88-
packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default = "Default";
89-
} else {
90-
packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default = "Tag Parser";
91-
}
92-
if (prevIntelliSenseEngineDefault !== packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default) {
93-
return util.writeFileText(util.getPackageJsonPath(), util.stringifyPackageJson(packageJson));
94-
}
138+
export function getABTestSettings(): ABTestSettings {
139+
if (!settings) {
140+
settings = new ABTestSettings();
95141
}
96-
}
142+
return settings;
143+
}

Extension/src/main.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<CppToo
3030
util.setExtensionContext(context);
3131
Telemetry.activate();
3232
util.setProgress(0);
33-
cpptoolsJsonUtils.activate(context);
3433
initializeInstallationInformation();
3534

3635
// Initialize the DebuggerExtension and register the related commands and providers.
@@ -275,23 +274,24 @@ async function postInstall(info: PlatformInformation): Promise<void> {
275274
}
276275

277276
async function finalizeExtensionActivation(): Promise<void> {
278-
const cpptoolsJsonFile: string = util.getExtensionFilePath("cpptools.json");
277+
getTemporaryCommandRegistrarInstance().activateLanguageServer();
279278

280-
try {
281-
const exists: boolean = await util.checkFileExists(cpptoolsJsonFile);
282-
if (exists) {
283-
const cpptoolsString: string = await util.readFileText(cpptoolsJsonFile);
284-
await cpptoolsJsonUtils.processCpptoolsJson(cpptoolsString);
279+
// Update default for C_Cpp.intelliSenseEngine based on A/B testing settings.
280+
// (this may result in rewriting the package.json file)
281+
282+
let abTestSettings: cpptoolsJsonUtils.ABTestSettings = cpptoolsJsonUtils.getABTestSettings();
283+
let packageJson: any = util.getRawPackageJson();
284+
if (!packageJson.extensionFolderPath.includes(".vscode-insiders")) {
285+
let prevIntelliSenseEngineDefault: any = packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default;
286+
if (abTestSettings.UseDefaultIntelliSenseEngine) {
287+
packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default = "Default";
288+
} else {
289+
packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default = "Tag Parser";
290+
}
291+
if (prevIntelliSenseEngineDefault !== packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default) {
292+
return util.writeFileText(util.getPackageJsonPath(), util.stringifyPackageJson(packageJson));
285293
}
286-
} catch (error) {
287-
// Ignore any cpptoolsJsonFile errors
288294
}
289-
290-
getTemporaryCommandRegistrarInstance().activateLanguageServer();
291-
292-
// Redownload cpptools.json after activation so it's not blocked.
293-
// It'll be used after the extension reloads.
294-
cpptoolsJsonUtils.downloadCpptoolsJsonPkg();
295295
}
296296

297297
function rewriteManifest(): Promise<void> {

0 commit comments

Comments
 (0)