mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
Merge branch 'main' into ai-codefixes
This commit is contained in:
commit
ba511b36f1
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
|
@ -151,4 +151,12 @@
|
|||
"application.experimental.rendererProfiling": true,
|
||||
"editor.experimental.asyncTokenization": true,
|
||||
"editor.experimental.asyncTokenizationVerification": true,
|
||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||
"xterm",
|
||||
"xterm-headless",
|
||||
"node-pty",
|
||||
"vscode-notebook-renderer",
|
||||
"src/vs/workbench/workbench.web.main.ts",
|
||||
"src/vs/workbench/api/common/extHostTypes.ts"
|
||||
]
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -197,15 +197,19 @@ async function main(): Promise<void> {
|
|||
|
||||
const uploadPromises: Promise<void>[] = [];
|
||||
|
||||
if (await blobClient.exists()) {
|
||||
uploadPromises.push(Promise.reject(new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`)));
|
||||
} else {
|
||||
uploadPromises.push(retry(async (attempt) => {
|
||||
console.log(`Uploading blobs to Azure storage (attempt ${attempt})...`);
|
||||
await blobClient.uploadFile(filePath, blobOptions);
|
||||
console.log('Blob successfully uploaded to Azure storage.');
|
||||
}));
|
||||
}
|
||||
uploadPromises.push((async () => {
|
||||
console.log(`Checking for blob in Azure...`);
|
||||
|
||||
if (await retry(() => blobClient.exists())) {
|
||||
throw new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`);
|
||||
} else {
|
||||
await retry(async (attempt) => {
|
||||
console.log(`Uploading blobs to Azure storage (attempt ${attempt})...`);
|
||||
await blobClient.uploadFile(filePath, blobOptions);
|
||||
console.log('Blob successfully uploaded to Azure storage.');
|
||||
});
|
||||
}
|
||||
})());
|
||||
|
||||
const shouldUploadToMooncake = /true/i.test(process.env['VSCODE_PUBLISH_TO_MOONCAKE'] ?? 'true');
|
||||
|
||||
|
@ -215,15 +219,19 @@ async function main(): Promise<void> {
|
|||
const mooncakeContainerClient = mooncakeBlobServiceClient.getContainerClient(quality);
|
||||
const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName);
|
||||
|
||||
if (await mooncakeBlobClient.exists()) {
|
||||
uploadPromises.push(Promise.reject(new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`)));
|
||||
} else {
|
||||
uploadPromises.push(retry(async (attempt) => {
|
||||
console.log(`Uploading blobs to Mooncake Azure storage (attempt ${attempt})...`);
|
||||
await mooncakeBlobClient.uploadFile(filePath, blobOptions);
|
||||
console.log('Blob successfully uploaded to Mooncake Azure storage.');
|
||||
}));
|
||||
}
|
||||
uploadPromises.push((async () => {
|
||||
console.log(`Checking for blob in Mooncake Azure...`);
|
||||
|
||||
if (await retry(() => mooncakeBlobClient.exists())) {
|
||||
throw new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`);
|
||||
} else {
|
||||
await retry(async (attempt) => {
|
||||
console.log(`Uploading blobs to Mooncake Azure storage (attempt ${attempt})...`);
|
||||
await mooncakeBlobClient.uploadFile(filePath, blobOptions);
|
||||
console.log('Blob successfully uploaded to Mooncake Azure storage.');
|
||||
});
|
||||
}
|
||||
})());
|
||||
}
|
||||
|
||||
const promiseResults = await Promise.allSettled(uploadPromises);
|
||||
|
|
|
@ -32,8 +32,8 @@ async function main(buildDir) {
|
|||
'Credits.rtf',
|
||||
'CodeResources',
|
||||
'fsevents.node',
|
||||
'Info.plist',
|
||||
'MainMenu.nib',
|
||||
'Info.plist', // TODO@deepak1556: regressed with 11.4.2 internal builds
|
||||
'MainMenu.nib', // Generated sequence is not deterministic with Xcode 13
|
||||
'.npmrc'
|
||||
],
|
||||
outAppPath,
|
||||
|
@ -57,4 +57,4 @@ if (require.main === module) {
|
|||
process.exit(1);
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLXVuaXZlcnNhbC1hcHAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGUtdW5pdmVyc2FsLWFwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLDZCQUE2QjtBQUM3Qix5QkFBeUI7QUFDekIsdUVBQTREO0FBQzVELHFFQUFvRDtBQUVwRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQUVuRCxLQUFLLFVBQVUsSUFBSSxDQUFDLFFBQWlCO0lBQ3BDLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUM7SUFFeEMsSUFBSSxDQUFDLFFBQVEsRUFBRTtRQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztLQUMxQztJQUVELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxjQUFjLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQ3JGLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxRQUFRLEdBQUcsTUFBTSxDQUFDO0lBQzFDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLG1CQUFtQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3JFLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLHFCQUFxQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3pFLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLG1CQUFtQixDQUFDLENBQUM7SUFDL0YsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsbUJBQW1CLENBQUMsQ0FBQztJQUNuRyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxpQkFBaUIsSUFBSSxFQUFFLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDekUsTUFBTSxlQUFlLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsY0FBYyxDQUFDLENBQUM7SUFFakcsTUFBTSxJQUFBLDJDQUFnQixFQUFDO1FBQ3RCLFVBQVU7UUFDVixZQUFZO1FBQ1osV0FBVztRQUNYLGFBQWE7UUFDYixXQUFXLEVBQUU7WUFDWixjQUFjO1lBQ2QsYUFBYTtZQUNiLGVBQWU7WUFDZixlQUFlO1lBQ2YsWUFBWTtZQUNaLGNBQWM7WUFDZCxRQUFRO1NBQ1I7UUFDRCxVQUFVO1FBQ1YsS0FBSyxFQUFFLElBQUk7S0FDWCxDQUFDLENBQUM7SUFFSCxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsZUFBZSxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7SUFDekUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxXQUFXLEVBQUU7UUFDMUIsc0JBQXNCLEVBQUUsa0JBQWtCO0tBQzFDLENBQUMsQ0FBQztJQUNILEVBQUUsQ0FBQyxhQUFhLENBQUMsZUFBZSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsV0FBVyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBRTNFLGtEQUFrRDtJQUNsRCxNQUFNLFVBQVUsR0FBRyxNQUFNLElBQUEsMkJBQUssRUFBQyxNQUFNLEVBQUUsQ0FBQyxVQUFVLEVBQUUsT0FBTyxFQUFFLGVBQWUsQ0FBQyxDQUFDLENBQUM7SUFDL0UsTUFBTSxVQUFVLEdBQUcsTUFBTSxJQUFBLDJCQUFLLEVBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNsRixJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxLQUFLLGNBQWMsRUFBRTtRQUNyRCxNQUFNLElBQUksS0FBSyxDQUFDLHVCQUF1QixVQUFVLEVBQUUsQ0FBQyxDQUFDO0tBQ3JEO0FBQ0YsQ0FBQztBQUVELElBQUksT0FBTyxDQUFDLElBQUksS0FBSyxNQUFNLEVBQUU7SUFDNUIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUU7UUFDakMsT0FBTyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNuQixPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2pCLENBQUMsQ0FBQyxDQUFDO0NBQ0gifQ==
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLXVuaXZlcnNhbC1hcHAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGUtdW5pdmVyc2FsLWFwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLDZCQUE2QjtBQUM3Qix5QkFBeUI7QUFDekIsdUVBQTREO0FBQzVELHFFQUFvRDtBQUVwRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQUVuRCxLQUFLLFVBQVUsSUFBSSxDQUFDLFFBQWlCO0lBQ3BDLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUM7SUFFeEMsSUFBSSxDQUFDLFFBQVEsRUFBRTtRQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztLQUMxQztJQUVELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxjQUFjLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQ3JGLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxRQUFRLEdBQUcsTUFBTSxDQUFDO0lBQzFDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLG1CQUFtQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3JFLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLHFCQUFxQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3pFLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLG1CQUFtQixDQUFDLENBQUM7SUFDL0YsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsbUJBQW1CLENBQUMsQ0FBQztJQUNuRyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxpQkFBaUIsSUFBSSxFQUFFLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDekUsTUFBTSxlQUFlLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsY0FBYyxDQUFDLENBQUM7SUFFakcsTUFBTSxJQUFBLDJDQUFnQixFQUFDO1FBQ3RCLFVBQVU7UUFDVixZQUFZO1FBQ1osV0FBVztRQUNYLGFBQWE7UUFDYixXQUFXLEVBQUU7WUFDWixjQUFjO1lBQ2QsYUFBYTtZQUNiLGVBQWU7WUFDZixlQUFlO1lBQ2YsWUFBWSxFQUFFLHlEQUF5RDtZQUN2RSxjQUFjLEVBQUUsd0RBQXdEO1lBQ3hFLFFBQVE7U0FDUjtRQUNELFVBQVU7UUFDVixLQUFLLEVBQUUsSUFBSTtLQUNYLENBQUMsQ0FBQztJQUVILE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxlQUFlLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztJQUN6RSxNQUFNLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRTtRQUMxQixzQkFBc0IsRUFBRSxrQkFBa0I7S0FDMUMsQ0FBQyxDQUFDO0lBQ0gsRUFBRSxDQUFDLGFBQWEsQ0FBQyxlQUFlLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxXQUFXLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7SUFFM0Usa0RBQWtEO0lBQ2xELE1BQU0sVUFBVSxHQUFHLE1BQU0sSUFBQSwyQkFBSyxFQUFDLE1BQU0sRUFBRSxDQUFDLFVBQVUsRUFBRSxPQUFPLEVBQUUsZUFBZSxDQUFDLENBQUMsQ0FBQztJQUMvRSxNQUFNLFVBQVUsR0FBRyxNQUFNLElBQUEsMkJBQUssRUFBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUUsVUFBVSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2xGLElBQUksVUFBVSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLEtBQUssY0FBYyxFQUFFO1FBQ3JELE1BQU0sSUFBSSxLQUFLLENBQUMsdUJBQXVCLFVBQVUsRUFBRSxDQUFDLENBQUM7S0FDckQ7QUFDRixDQUFDO0FBRUQsSUFBSSxPQUFPLENBQUMsSUFBSSxLQUFLLE1BQU0sRUFBRTtJQUM1QixJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRTtRQUNqQyxPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ25CLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDakIsQ0FBQyxDQUFDLENBQUM7Q0FDSCJ9
|
File diff suppressed because one or more lines are too long
|
@ -8,11 +8,11 @@ exports.referenceGeneratedDepsByArch = exports.recommendedDeps = exports.additio
|
|||
// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps
|
||||
// Additional dependencies not in the dpkg-shlibdeps output.
|
||||
exports.additionalDeps = [
|
||||
'ca-certificates',
|
||||
'ca-certificates', // Make sure users have SSL certificates.
|
||||
'libgtk-3-0 (>= 3.9.10) | libgtk-4-1',
|
||||
'libnss3 (>= 3.26)',
|
||||
'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3',
|
||||
'xdg-utils (>= 1.0.2)',
|
||||
'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', // For Breakpad crash reports.
|
||||
'xdg-utils (>= 1.0.2)', // OS integration
|
||||
'libgssapi-krb5-2',
|
||||
'libkrb5-3',
|
||||
];
|
||||
|
@ -140,4 +140,4 @@ exports.referenceGeneratedDepsByArch = {
|
|||
'xdg-utils (>= 1.0.2)'
|
||||
]
|
||||
};
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLGtIQUFrSDtBQUNsSCw0REFBNEQ7QUFDL0MsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCO0lBQ2pCLHFDQUFxQztJQUNyQyxtQkFBbUI7SUFDbkIsc0RBQXNEO0lBQ3RELHNCQUFzQjtJQUN0QixrQkFBa0I7SUFDbEIsV0FBVztDQUNYLENBQUM7QUFFRixvSEFBb0g7QUFDcEgsMENBQTBDO0FBQzFDLDhEQUE4RDtBQUNqRCxRQUFBLGVBQWUsR0FBRztJQUM5QixZQUFZLENBQUMseUVBQXlFO0NBQ3RGLENBQUM7QUFFVyxRQUFBLDRCQUE0QixHQUFHO0lBQzNDLE9BQU8sRUFBRTtRQUNSLGlCQUFpQjtRQUNqQix3QkFBd0I7UUFDeEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QiwyQkFBMkI7UUFDM0IsaUJBQWlCO1FBQ2pCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsa0JBQWtCO1FBQ2xCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQixrQkFBa0I7UUFDbEIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyxXQUFXO1FBQ1gsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsNEJBQTRCO1FBQzVCLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsaUJBQWlCO1FBQ2pCLGdCQUFnQjtRQUNoQixnQkFBZ0I7UUFDaEIsZ0JBQWdCO1FBQ2hCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQixrQkFBa0I7UUFDbEIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyxXQUFXO1FBQ1gsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsNEJBQTRCO1FBQzVCLG1CQUFtQjtRQUNuQixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixzQkFBc0I7UUFDdEIsc0RBQXNEO1FBQ3RELHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsc0JBQXNCO1FBQ3RCLHlCQUF5QjtRQUN6QiwwQkFBMEI7UUFDMUIsa0JBQWtCO1FBQ2xCLHdCQUF3QjtRQUN4QixxQ0FBcUM7UUFDckMsV0FBVztRQUNYLHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLDRCQUE0QjtRQUM1QixtQkFBbUI7UUFDbkIscUJBQXFCO1FBQ3JCLG1CQUFtQjtRQUNuQixVQUFVO1FBQ1YsMEJBQTBCO1FBQzFCLG9CQUFvQjtRQUNwQiwrQkFBK0I7UUFDL0Isd0JBQXdCO1FBQ3hCLFVBQVU7UUFDVixZQUFZO1FBQ1osMEJBQTBCO1FBQzFCLGFBQWE7UUFDYixZQUFZO1FBQ1osc0JBQXNCO0tBQ3RCO0NBQ0QsQ0FBQyJ9
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLGtIQUFrSDtBQUNsSCw0REFBNEQ7QUFDL0MsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCLEVBQUUseUNBQXlDO0lBQzVELHFDQUFxQztJQUNyQyxtQkFBbUI7SUFDbkIsc0RBQXNELEVBQUUsOEJBQThCO0lBQ3RGLHNCQUFzQixFQUFFLGlCQUFpQjtJQUN6QyxrQkFBa0I7SUFDbEIsV0FBVztDQUNYLENBQUM7QUFFRixvSEFBb0g7QUFDcEgsMENBQTBDO0FBQzFDLDhEQUE4RDtBQUNqRCxRQUFBLGVBQWUsR0FBRztJQUM5QixZQUFZLENBQUMseUVBQXlFO0NBQ3RGLENBQUM7QUFFVyxRQUFBLDRCQUE0QixHQUFHO0lBQzNDLE9BQU8sRUFBRTtRQUNSLGlCQUFpQjtRQUNqQix3QkFBd0I7UUFDeEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QiwyQkFBMkI7UUFDM0IsaUJBQWlCO1FBQ2pCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsa0JBQWtCO1FBQ2xCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQixrQkFBa0I7UUFDbEIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyxXQUFXO1FBQ1gsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsNEJBQTRCO1FBQzVCLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsaUJBQWlCO1FBQ2pCLGdCQUFnQjtRQUNoQixnQkFBZ0I7UUFDaEIsZ0JBQWdCO1FBQ2hCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQixrQkFBa0I7UUFDbEIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyxXQUFXO1FBQ1gsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsNEJBQTRCO1FBQzVCLG1CQUFtQjtRQUNuQixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixzQkFBc0I7UUFDdEIsc0RBQXNEO1FBQ3RELHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsc0JBQXNCO1FBQ3RCLHlCQUF5QjtRQUN6QiwwQkFBMEI7UUFDMUIsa0JBQWtCO1FBQ2xCLHdCQUF3QjtRQUN4QixxQ0FBcUM7UUFDckMsV0FBVztRQUNYLHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLDRCQUE0QjtRQUM1QixtQkFBbUI7UUFDbkIscUJBQXFCO1FBQ3JCLG1CQUFtQjtRQUNuQixVQUFVO1FBQ1YsMEJBQTBCO1FBQzFCLG9CQUFvQjtRQUNwQiwrQkFBK0I7UUFDL0Isd0JBQXdCO1FBQ3hCLFVBQVU7UUFDVixZQUFZO1FBQ1osMEJBQTBCO1FBQzFCLGFBQWE7UUFDYixZQUFZO1FBQ1osc0JBQXNCO0tBQ3RCO0NBQ0QsQ0FBQyJ9
|
File diff suppressed because one or more lines are too long
|
@ -6,7 +6,7 @@
|
|||
"git": {
|
||||
"name": "microsoft/vscode-css",
|
||||
"repositoryUrl": "https://github.com/microsoft/vscode-css",
|
||||
"commitHash": "3bd00206f6b0d16eb2eba53fb886462eb8c58baa"
|
||||
"commitHash": "c216f777497265700ff336f739328e5197e012cd"
|
||||
}
|
||||
},
|
||||
"licenseDetail": [
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -18,6 +18,7 @@
|
|||
"editSessionIdentityProvider",
|
||||
"quickDiffProvider",
|
||||
"scmActionButton",
|
||||
"scmHistoryProvider",
|
||||
"scmSelectedProvider",
|
||||
"scmValidation",
|
||||
"tabInputTextMerge",
|
||||
|
|
|
@ -20,24 +20,24 @@ interface ActionButtonState {
|
|||
readonly repositoryHasChangesToCommit: boolean;
|
||||
}
|
||||
|
||||
export class ActionButtonCommand {
|
||||
private _onDidChange = new EventEmitter<void>();
|
||||
abstract class AbstractActionButton {
|
||||
protected _onDidChange = new EventEmitter<void>();
|
||||
get onDidChange(): Event<void> { return this._onDidChange.event; }
|
||||
|
||||
private _state: ActionButtonState;
|
||||
private get state() { return this._state; }
|
||||
private set state(state: ActionButtonState) {
|
||||
protected get state() { return this._state; }
|
||||
protected set state(state: ActionButtonState) {
|
||||
if (JSON.stringify(this._state) !== JSON.stringify(state)) {
|
||||
this._state = state;
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
private disposables: Disposable[] = [];
|
||||
abstract get button(): SourceControlActionButton | undefined;
|
||||
|
||||
constructor(
|
||||
readonly repository: Repository,
|
||||
readonly postCommitCommandCenter: CommitCommandsCenter) {
|
||||
protected disposables: Disposable[] = [];
|
||||
|
||||
constructor(readonly repository: Repository) {
|
||||
this._state = {
|
||||
HEAD: undefined,
|
||||
isCheckoutInProgress: false,
|
||||
|
@ -50,6 +50,126 @@ export class ActionButtonCommand {
|
|||
|
||||
repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables);
|
||||
repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables);
|
||||
}
|
||||
|
||||
protected getPublishBranchActionButton(): SourceControlActionButton | undefined {
|
||||
const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(cloud-upload)';
|
||||
|
||||
return {
|
||||
command: {
|
||||
command: 'git.publish',
|
||||
title: l10n.t({ message: '{0} Publish Branch', args: [icon], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }),
|
||||
tooltip: this.state.isSyncInProgress ?
|
||||
(this.state.HEAD?.name ?
|
||||
l10n.t({ message: 'Publishing Branch "{0}"...', args: [this.state.HEAD.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) :
|
||||
l10n.t({ message: 'Publishing Branch...', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })) :
|
||||
(this.repository.HEAD?.name ?
|
||||
l10n.t({ message: 'Publish Branch "{0}"', args: [this.state.HEAD?.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) :
|
||||
l10n.t({ message: 'Publish Branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })),
|
||||
arguments: [this.repository.sourceControl],
|
||||
},
|
||||
enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress && !this.state.isCommitInProgress && !this.state.isMergeInProgress && !this.state.isRebaseInProgress
|
||||
};
|
||||
}
|
||||
|
||||
protected getSyncChangesActionButton(): SourceControlActionButton | undefined {
|
||||
const branchIsAheadOrBehind = (this.state.HEAD?.behind ?? 0) > 0 || (this.state.HEAD?.ahead ?? 0) > 0;
|
||||
|
||||
const ahead = this.state.HEAD?.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : '';
|
||||
const behind = this.state.HEAD?.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : '';
|
||||
const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)';
|
||||
|
||||
return {
|
||||
command: {
|
||||
command: 'git.sync',
|
||||
title: l10n.t('{0} Sync Changes{1}{2}', icon, behind, ahead),
|
||||
tooltip: this.state.isSyncInProgress ?
|
||||
l10n.t('Synchronizing Changes...')
|
||||
: this.repository.syncTooltip,
|
||||
arguments: [this.repository.sourceControl],
|
||||
},
|
||||
description: `${icon}${behind}${ahead}`,
|
||||
enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress && !this.state.isCommitInProgress && !this.state.isMergeInProgress && !this.state.isRebaseInProgress && branchIsAheadOrBehind
|
||||
};
|
||||
}
|
||||
|
||||
private onDidChangeOperations(): void {
|
||||
const isCheckoutInProgress
|
||||
= this.repository.operations.isRunning(OperationKind.Checkout) ||
|
||||
this.repository.operations.isRunning(OperationKind.CheckoutTracking);
|
||||
|
||||
const isCommitInProgress =
|
||||
this.repository.operations.isRunning(OperationKind.Commit) ||
|
||||
this.repository.operations.isRunning(OperationKind.PostCommitCommand) ||
|
||||
this.repository.operations.isRunning(OperationKind.RebaseContinue);
|
||||
|
||||
const isSyncInProgress =
|
||||
this.repository.operations.isRunning(OperationKind.Sync) ||
|
||||
this.repository.operations.isRunning(OperationKind.Push) ||
|
||||
this.repository.operations.isRunning(OperationKind.Pull);
|
||||
|
||||
this.state = { ...this.state, isCheckoutInProgress, isCommitInProgress, isSyncInProgress };
|
||||
}
|
||||
|
||||
private onDidRunGitStatus(): void {
|
||||
this.state = {
|
||||
...this.state,
|
||||
HEAD: this.repository.HEAD,
|
||||
isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0,
|
||||
isRebaseInProgress: !!this.repository.rebaseCommit,
|
||||
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
|
||||
};
|
||||
}
|
||||
|
||||
protected repositoryHasChangesToCommit(): boolean {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
|
||||
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
|
||||
const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges', 'all');
|
||||
|
||||
const resources = [...this.repository.indexGroup.resourceStates];
|
||||
|
||||
if (
|
||||
// Smart commit enabled (all)
|
||||
(enableSmartCommit && smartCommitChanges === 'all') ||
|
||||
// Smart commit disabled, smart suggestion enabled
|
||||
(!enableSmartCommit && suggestSmartCommit)
|
||||
) {
|
||||
resources.push(...this.repository.workingTreeGroup.resourceStates);
|
||||
}
|
||||
|
||||
// Smart commit enabled (tracked only)
|
||||
if (enableSmartCommit && smartCommitChanges === 'tracked') {
|
||||
resources.push(...this.repository.workingTreeGroup.resourceStates.filter(r => r.type !== Status.UNTRACKED));
|
||||
}
|
||||
|
||||
return resources.length !== 0;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export class CommitActionButton extends AbstractActionButton {
|
||||
override get button(): SourceControlActionButton | undefined {
|
||||
if (!this.state.HEAD) { return undefined; }
|
||||
|
||||
let actionButton: SourceControlActionButton | undefined;
|
||||
|
||||
if (this.state.repositoryHasChangesToCommit) {
|
||||
// Commit Changes (enabled)
|
||||
actionButton = this.getCommitActionButton();
|
||||
}
|
||||
|
||||
// Commit Changes (enabled) -> Publish Branch -> Sync Changes -> Commit Changes (disabled)
|
||||
return actionButton ?? this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton() ?? this.getCommitActionButton();
|
||||
}
|
||||
|
||||
constructor(
|
||||
repository: Repository,
|
||||
readonly postCommitCommandCenter: CommitCommandsCenter) {
|
||||
super(repository);
|
||||
|
||||
this.disposables.push(repository.onDidChangeBranchProtection(() => this._onDidChange.fire()));
|
||||
this.disposables.push(postCommitCommandCenter.onDidChange(() => this._onDidChange.fire()));
|
||||
|
@ -62,7 +182,8 @@ export class ActionButtonCommand {
|
|||
this.onDidChangeSmartCommitSettings();
|
||||
}
|
||||
|
||||
if (e.affectsConfiguration('git.branchProtectionPrompt', root) ||
|
||||
if (e.affectsConfiguration('scm.experimental.showSyncView') ||
|
||||
e.affectsConfiguration('git.branchProtectionPrompt', root) ||
|
||||
e.affectsConfiguration('git.postCommitCommand', root) ||
|
||||
e.affectsConfiguration('git.rememberPostCommitCommand', root) ||
|
||||
e.affectsConfiguration('git.showActionButton', root)) {
|
||||
|
@ -71,20 +192,6 @@ export class ActionButtonCommand {
|
|||
}));
|
||||
}
|
||||
|
||||
get button(): SourceControlActionButton | undefined {
|
||||
if (!this.state.HEAD) { return undefined; }
|
||||
|
||||
let actionButton: SourceControlActionButton | undefined;
|
||||
|
||||
if (this.state.repositoryHasChangesToCommit) {
|
||||
// Commit Changes (enabled)
|
||||
actionButton = this.getCommitActionButton();
|
||||
}
|
||||
|
||||
// Commit Changes (enabled) -> Publish Branch -> Sync Changes -> Commit Changes (disabled)
|
||||
return actionButton ?? this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton() ?? this.getCommitActionButton();
|
||||
}
|
||||
|
||||
private getCommitActionButton(): SourceControlActionButton | undefined {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const showActionButton = config.get<{ commit: boolean }>('showActionButton', { commit: true });
|
||||
|
@ -133,34 +240,27 @@ export class ActionButtonCommand {
|
|||
return commandGroups;
|
||||
}
|
||||
|
||||
private getPublishBranchActionButton(): SourceControlActionButton | undefined {
|
||||
protected override getPublishBranchActionButton(): SourceControlActionButton | undefined {
|
||||
const scmConfig = workspace.getConfiguration('scm');
|
||||
if (scmConfig.get<boolean>('experimental.showSyncView', false)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true });
|
||||
|
||||
// Not a branch (tag, detached), branch does have an upstream, commit/merge/rebase is in progress, or the button is disabled
|
||||
if (this.state.HEAD?.type === RefType.Tag || !this.state.HEAD?.name || this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.publish) { return undefined; }
|
||||
|
||||
// Button icon
|
||||
const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(cloud-upload)';
|
||||
|
||||
return {
|
||||
command: {
|
||||
command: 'git.publish',
|
||||
title: l10n.t({ message: '{0} Publish Branch', args: [icon], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }),
|
||||
tooltip: this.state.isSyncInProgress ?
|
||||
(this.state.HEAD?.name ?
|
||||
l10n.t({ message: 'Publishing Branch "{0}"...', args: [this.state.HEAD.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) :
|
||||
l10n.t({ message: 'Publishing Branch...', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })) :
|
||||
(this.repository.HEAD?.name ?
|
||||
l10n.t({ message: 'Publish Branch "{0}"', args: [this.state.HEAD?.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) :
|
||||
l10n.t({ message: 'Publish Branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })),
|
||||
arguments: [this.repository.sourceControl],
|
||||
},
|
||||
enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress
|
||||
};
|
||||
return super.getPublishBranchActionButton();
|
||||
}
|
||||
|
||||
private getSyncChangesActionButton(): SourceControlActionButton | undefined {
|
||||
protected override getSyncChangesActionButton(): SourceControlActionButton | undefined {
|
||||
const scmConfig = workspace.getConfiguration('scm');
|
||||
if (scmConfig.get<boolean>('experimental.showSyncView', false)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true });
|
||||
const branchIsAheadOrBehind = (this.state.HEAD?.behind ?? 0) > 0 || (this.state.HEAD?.ahead ?? 0) > 0;
|
||||
|
@ -168,40 +268,7 @@ export class ActionButtonCommand {
|
|||
// Branch does not have an upstream, branch is not ahead/behind the remote branch, commit/merge/rebase is in progress, or the button is disabled
|
||||
if (!this.state.HEAD?.upstream || !branchIsAheadOrBehind || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.sync) { return undefined; }
|
||||
|
||||
const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : '';
|
||||
const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : '';
|
||||
const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)';
|
||||
|
||||
return {
|
||||
command: {
|
||||
command: 'git.sync',
|
||||
title: l10n.t('{0} Sync Changes{1}{2}', icon, behind, ahead),
|
||||
tooltip: this.state.isSyncInProgress ?
|
||||
l10n.t('Synchronizing Changes...')
|
||||
: this.repository.syncTooltip,
|
||||
arguments: [this.repository.sourceControl],
|
||||
},
|
||||
description: `${icon}${behind}${ahead}`,
|
||||
enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress
|
||||
};
|
||||
}
|
||||
|
||||
private onDidChangeOperations(): void {
|
||||
const isCheckoutInProgress
|
||||
= this.repository.operations.isRunning(OperationKind.Checkout) ||
|
||||
this.repository.operations.isRunning(OperationKind.CheckoutTracking);
|
||||
|
||||
const isCommitInProgress =
|
||||
this.repository.operations.isRunning(OperationKind.Commit) ||
|
||||
this.repository.operations.isRunning(OperationKind.PostCommitCommand) ||
|
||||
this.repository.operations.isRunning(OperationKind.RebaseContinue);
|
||||
|
||||
const isSyncInProgress =
|
||||
this.repository.operations.isRunning(OperationKind.Sync) ||
|
||||
this.repository.operations.isRunning(OperationKind.Push) ||
|
||||
this.repository.operations.isRunning(OperationKind.Pull);
|
||||
|
||||
this.state = { ...this.state, isCheckoutInProgress, isCommitInProgress, isSyncInProgress };
|
||||
return super.getSyncChangesActionButton();
|
||||
}
|
||||
|
||||
private onDidChangeSmartCommitSettings(): void {
|
||||
|
@ -210,43 +277,30 @@ export class ActionButtonCommand {
|
|||
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private onDidRunGitStatus(): void {
|
||||
this.state = {
|
||||
...this.state,
|
||||
HEAD: this.repository.HEAD,
|
||||
isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0,
|
||||
isRebaseInProgress: !!this.repository.rebaseCommit,
|
||||
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
|
||||
};
|
||||
export class SyncActionButton extends AbstractActionButton {
|
||||
override get button(): SourceControlActionButton | undefined {
|
||||
if (!this.state.HEAD) { return undefined; }
|
||||
|
||||
// Publish Branch -> Sync Changes
|
||||
return this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton();
|
||||
}
|
||||
|
||||
private repositoryHasChangesToCommit(): boolean {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
|
||||
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
|
||||
const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges', 'all');
|
||||
constructor(repository: Repository) {
|
||||
super(repository);
|
||||
|
||||
const resources = [...this.repository.indexGroup.resourceStates];
|
||||
|
||||
if (
|
||||
// Smart commit enabled (all)
|
||||
(enableSmartCommit && smartCommitChanges === 'all') ||
|
||||
// Smart commit disabled, smart suggestion enabled
|
||||
(!enableSmartCommit && suggestSmartCommit)
|
||||
) {
|
||||
resources.push(...this.repository.workingTreeGroup.resourceStates);
|
||||
}
|
||||
|
||||
// Smart commit enabled (tracked only)
|
||||
if (enableSmartCommit && smartCommitChanges === 'tracked') {
|
||||
resources.push(...this.repository.workingTreeGroup.resourceStates.filter(r => r.type !== Status.UNTRACKED));
|
||||
}
|
||||
|
||||
return resources.length !== 0;
|
||||
this.disposables.push(workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('scm.experimental.showSyncView')) {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
protected override getPublishBranchActionButton(): SourceControlActionButton | undefined {
|
||||
// Not a branch (tag, detached), branch does have an upstream
|
||||
if (this.state.HEAD?.type === RefType.Tag || this.state.HEAD?.upstream) { return undefined; }
|
||||
|
||||
return super.getPublishBranchActionButton();
|
||||
}
|
||||
}
|
||||
|
|
2
extensions/git/src/api/git.d.ts
vendored
2
extensions/git/src/api/git.d.ts
vendored
|
@ -131,6 +131,8 @@ export interface LogOptions {
|
|||
readonly path?: string;
|
||||
/** A commit range, such as "0a47c67f0fb52dd11562af48658bc1dff1d75a38..0bb4bdea78e1db44d728fd6894720071e303304f" */
|
||||
readonly range?: string;
|
||||
readonly reverse?: boolean;
|
||||
readonly sortByAuthorDate?: boolean;
|
||||
}
|
||||
|
||||
export interface CommitOptions {
|
||||
|
|
|
@ -1023,17 +1023,24 @@ export class Repository {
|
|||
}
|
||||
|
||||
async log(options?: LogOptions): Promise<Commit[]> {
|
||||
const maxEntries = options?.maxEntries ?? 32;
|
||||
const args = ['log', `-n${maxEntries}`, `--format=${COMMIT_FORMAT}`, '-z'];
|
||||
const args = ['log', `--format=${COMMIT_FORMAT}`, '-z'];
|
||||
|
||||
if (options?.reverse) {
|
||||
args.push('--reverse', '--ancestry-path');
|
||||
}
|
||||
|
||||
if (options?.sortByAuthorDate) {
|
||||
args.push('--author-date-order');
|
||||
}
|
||||
|
||||
if (options?.range) {
|
||||
args.push(options.range);
|
||||
} else {
|
||||
args.push(`-n${options?.maxEntries ?? 32}`);
|
||||
}
|
||||
|
||||
args.push('--');
|
||||
|
||||
if (options?.path) {
|
||||
args.push(options.path);
|
||||
args.push('--', options.path);
|
||||
}
|
||||
|
||||
const result = await this.exec(args);
|
||||
|
@ -1258,7 +1265,7 @@ export class Repository {
|
|||
|
||||
diffIndexWithHEAD(): Promise<Change[]>;
|
||||
diffIndexWithHEAD(path: string): Promise<string>;
|
||||
diffIndexWithHEAD(path?: string | undefined): Promise<string | Change[]>;
|
||||
diffIndexWithHEAD(path?: string | undefined): Promise<Change[]>;
|
||||
async diffIndexWithHEAD(path?: string): Promise<string | Change[]> {
|
||||
if (!path) {
|
||||
return await this.diffFiles(true);
|
||||
|
@ -1303,6 +1310,17 @@ export class Repository {
|
|||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
async diffBetweenShortStat(ref1: string, ref2: string): Promise<string> {
|
||||
const args = ['diff', '--shortstat', `${ref1}...${ref2}`];
|
||||
|
||||
const result = await this.exec(args);
|
||||
if (result.exitCode) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
private async diffFiles(cached: boolean, ref?: string): Promise<Change[]> {
|
||||
const args = ['diff', '--name-status', '-z', '--diff-filter=ADMR'];
|
||||
if (cached) {
|
||||
|
@ -2450,6 +2468,15 @@ export class Repository {
|
|||
return Promise.reject<Branch>(new Error('No such branch'));
|
||||
}
|
||||
|
||||
async getDefaultBranch(): Promise<Branch> {
|
||||
const result = await this.exec(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']);
|
||||
if (!result.stdout) {
|
||||
throw new Error('No default branch');
|
||||
}
|
||||
|
||||
return this.getBranch(result.stdout.trim());
|
||||
}
|
||||
|
||||
// TODO: Support core.commentChar
|
||||
stripCommitMessageComments(message: string): string {
|
||||
return message.replace(/^\s*#.*$\n?/gm, '').trim();
|
||||
|
@ -2510,6 +2537,13 @@ export class Repository {
|
|||
return commits[0];
|
||||
}
|
||||
|
||||
async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> {
|
||||
const result = await this.exec(['rev-list', '--count', '--left-right', range]);
|
||||
const [ahead, behind] = result.stdout.trim().split('\t');
|
||||
|
||||
return { ahead: Number(ahead) || 0, behind: Number(behind) || 0 };
|
||||
}
|
||||
|
||||
async updateSubmodules(paths: string[]): Promise<void> {
|
||||
const args = ['submodule', 'update'];
|
||||
|
||||
|
|
183
extensions/git/src/historyProvider.ts
Normal file
183
extensions/git/src/historyProvider.ts
Normal file
|
@ -0,0 +1,183 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlActionButton, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, l10n } from 'vscode';
|
||||
import { Repository, Resource } from './repository';
|
||||
import { IDisposable } from './util';
|
||||
import { toGitUri } from './uri';
|
||||
import { SyncActionButton } from './actionButton';
|
||||
import { Status } from './api/git';
|
||||
|
||||
export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable {
|
||||
|
||||
private readonly _onDidChangeActionButton = new EventEmitter<void>();
|
||||
readonly onDidChangeActionButton: Event<void> = this._onDidChangeActionButton.event;
|
||||
|
||||
private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter<void>();
|
||||
readonly onDidChangeCurrentHistoryItemGroup: Event<void> = this._onDidChangeCurrentHistoryItemGroup.event;
|
||||
|
||||
private readonly _onDidChangeDecorations = new EventEmitter<Uri[]>();
|
||||
readonly onDidChangeFileDecorations: Event<Uri[]> = this._onDidChangeDecorations.event;
|
||||
|
||||
private _actionButton: SourceControlActionButton | undefined;
|
||||
get actionButton(): SourceControlActionButton | undefined { return this._actionButton; }
|
||||
set actionButton(button: SourceControlActionButton | undefined) {
|
||||
this._actionButton = button;
|
||||
this._onDidChangeActionButton.fire();
|
||||
}
|
||||
|
||||
private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined;
|
||||
|
||||
get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; }
|
||||
set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) {
|
||||
this._currentHistoryItemGroup = value;
|
||||
this._onDidChangeCurrentHistoryItemGroup.fire();
|
||||
}
|
||||
|
||||
private historyItemDecorations = new Map<string, FileDecoration>();
|
||||
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(protected readonly repository: Repository) {
|
||||
const actionButton = new SyncActionButton(repository);
|
||||
this.actionButton = actionButton.button;
|
||||
this.disposables.push(actionButton);
|
||||
|
||||
this.disposables.push(repository.onDidRunGitStatus(this.onDidRunGitStatus, this));
|
||||
this.disposables.push(actionButton.onDidChange(() => this.actionButton = actionButton.button));
|
||||
|
||||
this.disposables.push(window.registerFileDecorationProvider(this));
|
||||
}
|
||||
|
||||
private async onDidRunGitStatus(): Promise<void> {
|
||||
if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit) { return; }
|
||||
|
||||
this.currentHistoryItemGroup = {
|
||||
id: `refs/heads/${this.repository.HEAD.name}`,
|
||||
label: this.repository.HEAD.name,
|
||||
upstream: this.repository.HEAD.upstream ?
|
||||
{
|
||||
id: `refs/remotes/${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`,
|
||||
label: `${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`,
|
||||
} : undefined
|
||||
};
|
||||
}
|
||||
|
||||
async provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions): Promise<SourceControlHistoryItem[]> {
|
||||
//TODO@lszomoru - support limit and cursor
|
||||
if (typeof options.limit === 'number') {
|
||||
throw new Error('Unsupported options.');
|
||||
}
|
||||
if (typeof options.limit?.id !== 'string') {
|
||||
throw new Error('Unsupported options.');
|
||||
}
|
||||
|
||||
const optionsRef = options.limit.id;
|
||||
const [commits, summary] = await Promise.all([
|
||||
this.repository.log({ range: `${optionsRef}..${historyItemGroupId}`, sortByAuthorDate: true }),
|
||||
this.getSummaryHistoryItem(optionsRef, historyItemGroupId)
|
||||
]);
|
||||
|
||||
const historyItems = commits.length === 0 ? [] : [summary];
|
||||
historyItems.push(...commits.map(commit => {
|
||||
const newLineIndex = commit.message.indexOf('\n');
|
||||
const subject = newLineIndex !== -1 ? commit.message.substring(0, newLineIndex) : commit.message;
|
||||
|
||||
return {
|
||||
id: commit.hash,
|
||||
parentIds: commit.parents,
|
||||
label: subject,
|
||||
description: commit.authorName,
|
||||
icon: new ThemeIcon('account'),
|
||||
timestamp: commit.authorDate?.getTime()
|
||||
};
|
||||
}));
|
||||
|
||||
return historyItems;
|
||||
}
|
||||
|
||||
async provideHistoryItemChanges(historyItemId: string): Promise<SourceControlHistoryItemChange[]> {
|
||||
const [ref1, ref2] = historyItemId.includes('..')
|
||||
? historyItemId.split('..')
|
||||
: [`${historyItemId}^`, historyItemId];
|
||||
|
||||
const historyItemChangesUri: Uri[] = [];
|
||||
const historyItemChanges: SourceControlHistoryItemChange[] = [];
|
||||
const changes = await this.repository.diffBetween(ref1, ref2);
|
||||
|
||||
for (const change of changes) {
|
||||
const historyItemUri = change.uri.with({
|
||||
query: `ref=${historyItemId}`
|
||||
});
|
||||
|
||||
// History item change
|
||||
historyItemChanges.push({
|
||||
uri: historyItemUri,
|
||||
originalUri: toGitUri(change.originalUri, ref1),
|
||||
modifiedUri: toGitUri(change.originalUri, ref2),
|
||||
renameUri: change.renameUri,
|
||||
});
|
||||
|
||||
// History item change decoration
|
||||
const fileDecoration = this.historyItemChangeFileDecoration(change.status);
|
||||
this.historyItemDecorations.set(historyItemUri.toString(), fileDecoration);
|
||||
|
||||
historyItemChangesUri.push(historyItemUri);
|
||||
}
|
||||
|
||||
this._onDidChangeDecorations.fire(historyItemChangesUri);
|
||||
return historyItemChanges;
|
||||
}
|
||||
|
||||
async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise<SourceControlHistoryItemGroup | undefined> {
|
||||
// TODO - support for all history item groups
|
||||
if (historyItemGroupId !== this.currentHistoryItemGroup?.id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this.currentHistoryItemGroup?.upstream) {
|
||||
return this.currentHistoryItemGroup.upstream;
|
||||
}
|
||||
|
||||
// Default branch
|
||||
const defaultBranch = await this.repository.getDefaultBranch();
|
||||
return defaultBranch.name ? { id: `refs/heads/${defaultBranch.name}`, label: defaultBranch.name } : undefined;
|
||||
}
|
||||
|
||||
async resolveHistoryItemGroupCommonAncestor(refId1: string, refId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> {
|
||||
const ancestor = await this.repository.getMergeBase(refId1, refId2);
|
||||
if (ancestor === '') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const commitCount = await this.repository.getCommitCount(`${refId1}...${refId2}`);
|
||||
return { id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind };
|
||||
}
|
||||
|
||||
provideFileDecoration(uri: Uri): FileDecoration | undefined {
|
||||
return this.historyItemDecorations.get(uri.toString());
|
||||
}
|
||||
|
||||
private historyItemChangeFileDecoration(status: Status): FileDecoration {
|
||||
const letter = Resource.getStatusLetter(status);
|
||||
const tooltip = Resource.getStatusText(status);
|
||||
const color = Resource.getStatusColor(status);
|
||||
|
||||
const fileDecoration = new FileDecoration(letter, tooltip, color);
|
||||
fileDecoration.propagate = status !== Status.DELETED && status !== Status.INDEX_DELETED;
|
||||
|
||||
return fileDecoration;
|
||||
}
|
||||
|
||||
private async getSummaryHistoryItem(ref1: string, ref2: string): Promise<SourceControlHistoryItem> {
|
||||
const diffShortStat = await this.repository.diffBetweenShortStat(ref1, ref2);
|
||||
return { id: `${ref1}..${ref2}`, parentIds: [], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), description: diffShortStat };
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
|
@ -53,6 +53,7 @@ export const enum OperationKind {
|
|||
RebaseContinue = 'RebaseContinue',
|
||||
RevertFiles = 'RevertFiles',
|
||||
RevertFilesNoProgress = 'RevertFilesNoProgress',
|
||||
RevList = 'RevList',
|
||||
SetBranchUpstream = 'SetBranchUpstream',
|
||||
Show = 'Show',
|
||||
Stage = 'Stage',
|
||||
|
@ -69,8 +70,9 @@ export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchO
|
|||
GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation |
|
||||
HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation |
|
||||
MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | RemoveOperation |
|
||||
ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | SetBranchUpstreamOperation |
|
||||
ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation | TagOperation;
|
||||
ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | RevListOperation |
|
||||
SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation |
|
||||
TagOperation;
|
||||
|
||||
type BaseOperation = { kind: OperationKind; blocking: boolean; readOnly: boolean; remote: boolean; retry: boolean; showProgress: boolean };
|
||||
export type AddOperation = BaseOperation & { kind: OperationKind.Add };
|
||||
|
@ -116,6 +118,7 @@ export type RebaseOperation = BaseOperation & { kind: OperationKind.Rebase };
|
|||
export type RebaseAbortOperation = BaseOperation & { kind: OperationKind.RebaseAbort };
|
||||
export type RebaseContinueOperation = BaseOperation & { kind: OperationKind.RebaseContinue };
|
||||
export type RevertFilesOperation = BaseOperation & { kind: OperationKind.RevertFiles };
|
||||
export type RevListOperation = BaseOperation & { kind: OperationKind.RevList };
|
||||
export type SetBranchUpstreamOperation = BaseOperation & { kind: OperationKind.SetBranchUpstream };
|
||||
export type ShowOperation = BaseOperation & { kind: OperationKind.Show };
|
||||
export type StageOperation = BaseOperation & { kind: OperationKind.Stage };
|
||||
|
@ -169,6 +172,7 @@ export const Operation = {
|
|||
RebaseAbort: { kind: OperationKind.RebaseAbort, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseAbortOperation,
|
||||
RebaseContinue: { kind: OperationKind.RebaseContinue, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseContinueOperation,
|
||||
RevertFiles: (showProgress: boolean) => ({ kind: OperationKind.RevertFiles, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as RevertFilesOperation),
|
||||
RevList: { kind: OperationKind.RevList, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as RevListOperation,
|
||||
SetBranchUpstream: { kind: OperationKind.SetBranchUpstream, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as SetBranchUpstreamOperation,
|
||||
Show: { kind: OperationKind.Show, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as ShowOperation,
|
||||
Stage: { kind: OperationKind.Stage, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as StageOperation,
|
||||
|
|
|
@ -19,10 +19,11 @@ import { IFileWatcher, watch } from './watch';
|
|||
import { IPushErrorHandlerRegistry } from './pushError';
|
||||
import { ApiRepository } from './api/api1';
|
||||
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
|
||||
import { ActionButtonCommand } from './actionButton';
|
||||
import { CommitActionButton } from './actionButton';
|
||||
import { IPostCommitCommandsProviderRegistry, CommitCommandsCenter } from './postCommitCommands';
|
||||
import { Operation, OperationKind, OperationManager, OperationResult } from './operation';
|
||||
import { GitBranchProtectionProvider, IBranchProtectionProviderRegistry } from './branchProtection';
|
||||
import { GitHistoryProvider } from './historyProvider';
|
||||
|
||||
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));
|
||||
|
||||
|
@ -46,6 +47,43 @@ export const enum ResourceGroupType {
|
|||
|
||||
export class Resource implements SourceControlResourceState {
|
||||
|
||||
static getStatusLetter(type: Status): string {
|
||||
switch (type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
case Status.MODIFIED:
|
||||
return 'M';
|
||||
case Status.INDEX_ADDED:
|
||||
case Status.INTENT_TO_ADD:
|
||||
return 'A';
|
||||
case Status.INDEX_DELETED:
|
||||
case Status.DELETED:
|
||||
return 'D';
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INTENT_TO_RENAME:
|
||||
return 'R';
|
||||
case Status.TYPE_CHANGED:
|
||||
return 'T';
|
||||
case Status.UNTRACKED:
|
||||
return 'U';
|
||||
case Status.IGNORED:
|
||||
return 'I';
|
||||
case Status.DELETED_BY_THEM:
|
||||
return 'D';
|
||||
case Status.DELETED_BY_US:
|
||||
return 'D';
|
||||
case Status.INDEX_COPIED:
|
||||
return 'C';
|
||||
case Status.BOTH_DELETED:
|
||||
case Status.ADDED_BY_US:
|
||||
case Status.ADDED_BY_THEM:
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return '!'; // Using ! instead of ⚠, because the latter looks really bad on windows
|
||||
default:
|
||||
throw new Error('Unknown git status: ' + type);
|
||||
}
|
||||
}
|
||||
|
||||
static getStatusText(type: Status) {
|
||||
switch (type) {
|
||||
case Status.INDEX_MODIFIED: return l10n.t('Index Modified');
|
||||
|
@ -71,6 +109,41 @@ export class Resource implements SourceControlResourceState {
|
|||
}
|
||||
}
|
||||
|
||||
static getStatusColor(type: Status): ThemeColor {
|
||||
switch (type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
return new ThemeColor('gitDecoration.stageModifiedResourceForeground');
|
||||
case Status.MODIFIED:
|
||||
case Status.TYPE_CHANGED:
|
||||
return new ThemeColor('gitDecoration.modifiedResourceForeground');
|
||||
case Status.INDEX_DELETED:
|
||||
return new ThemeColor('gitDecoration.stageDeletedResourceForeground');
|
||||
case Status.DELETED:
|
||||
return new ThemeColor('gitDecoration.deletedResourceForeground');
|
||||
case Status.INDEX_ADDED:
|
||||
case Status.INTENT_TO_ADD:
|
||||
return new ThemeColor('gitDecoration.addedResourceForeground');
|
||||
case Status.INDEX_COPIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INTENT_TO_RENAME:
|
||||
return new ThemeColor('gitDecoration.renamedResourceForeground');
|
||||
case Status.UNTRACKED:
|
||||
return new ThemeColor('gitDecoration.untrackedResourceForeground');
|
||||
case Status.IGNORED:
|
||||
return new ThemeColor('gitDecoration.ignoredResourceForeground');
|
||||
case Status.BOTH_DELETED:
|
||||
case Status.ADDED_BY_US:
|
||||
case Status.DELETED_BY_THEM:
|
||||
case Status.ADDED_BY_THEM:
|
||||
case Status.DELETED_BY_US:
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return new ThemeColor('gitDecoration.conflictingResourceForeground');
|
||||
default:
|
||||
throw new Error('Unknown git status: ' + type);
|
||||
}
|
||||
}
|
||||
|
||||
@memoize
|
||||
get resourceUri(): Uri {
|
||||
if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED || this._type === Status.INTENT_TO_RENAME)) {
|
||||
|
@ -188,75 +261,11 @@ export class Resource implements SourceControlResourceState {
|
|||
}
|
||||
|
||||
get letter(): string {
|
||||
switch (this.type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
case Status.MODIFIED:
|
||||
return 'M';
|
||||
case Status.INDEX_ADDED:
|
||||
case Status.INTENT_TO_ADD:
|
||||
return 'A';
|
||||
case Status.INDEX_DELETED:
|
||||
case Status.DELETED:
|
||||
return 'D';
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INTENT_TO_RENAME:
|
||||
return 'R';
|
||||
case Status.TYPE_CHANGED:
|
||||
return 'T';
|
||||
case Status.UNTRACKED:
|
||||
return 'U';
|
||||
case Status.IGNORED:
|
||||
return 'I';
|
||||
case Status.DELETED_BY_THEM:
|
||||
return 'D';
|
||||
case Status.DELETED_BY_US:
|
||||
return 'D';
|
||||
case Status.INDEX_COPIED:
|
||||
return 'C';
|
||||
case Status.BOTH_DELETED:
|
||||
case Status.ADDED_BY_US:
|
||||
case Status.ADDED_BY_THEM:
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return '!'; // Using ! instead of ⚠, because the latter looks really bad on windows
|
||||
default:
|
||||
throw new Error('Unknown git status: ' + this.type);
|
||||
}
|
||||
return Resource.getStatusLetter(this.type);
|
||||
}
|
||||
|
||||
get color(): ThemeColor {
|
||||
switch (this.type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
return new ThemeColor('gitDecoration.stageModifiedResourceForeground');
|
||||
case Status.MODIFIED:
|
||||
case Status.TYPE_CHANGED:
|
||||
return new ThemeColor('gitDecoration.modifiedResourceForeground');
|
||||
case Status.INDEX_DELETED:
|
||||
return new ThemeColor('gitDecoration.stageDeletedResourceForeground');
|
||||
case Status.DELETED:
|
||||
return new ThemeColor('gitDecoration.deletedResourceForeground');
|
||||
case Status.INDEX_ADDED:
|
||||
case Status.INTENT_TO_ADD:
|
||||
return new ThemeColor('gitDecoration.addedResourceForeground');
|
||||
case Status.INDEX_COPIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INTENT_TO_RENAME:
|
||||
return new ThemeColor('gitDecoration.renamedResourceForeground');
|
||||
case Status.UNTRACKED:
|
||||
return new ThemeColor('gitDecoration.untrackedResourceForeground');
|
||||
case Status.IGNORED:
|
||||
return new ThemeColor('gitDecoration.ignoredResourceForeground');
|
||||
case Status.BOTH_DELETED:
|
||||
case Status.ADDED_BY_US:
|
||||
case Status.DELETED_BY_THEM:
|
||||
case Status.ADDED_BY_THEM:
|
||||
case Status.DELETED_BY_US:
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return new ThemeColor('gitDecoration.conflictingResourceForeground');
|
||||
default:
|
||||
throw new Error('Unknown git status: ' + this.type);
|
||||
}
|
||||
return Resource.getStatusColor(this.type);
|
||||
}
|
||||
|
||||
get priority(): number {
|
||||
|
@ -833,8 +842,13 @@ export class Repository implements Disposable {
|
|||
const root = Uri.file(repository.root);
|
||||
this._sourceControl = scm.createSourceControl('git', 'Git', root);
|
||||
|
||||
this._sourceControl.acceptInputCommand = { command: 'git.commit', title: l10n.t('Commit'), arguments: [this._sourceControl] };
|
||||
this._sourceControl.quickDiffProvider = this;
|
||||
|
||||
const historyProvider = new GitHistoryProvider(this);
|
||||
this._sourceControl.historyProvider = historyProvider;
|
||||
this.disposables.push(historyProvider);
|
||||
|
||||
this._sourceControl.acceptInputCommand = { command: 'git.commit', title: l10n.t('Commit'), arguments: [this._sourceControl] };
|
||||
this._sourceControl.inputBox.validateInput = this.validateInput.bind(this);
|
||||
this.disposables.push(this._sourceControl);
|
||||
|
||||
|
@ -921,10 +935,10 @@ export class Repository implements Disposable {
|
|||
this.commitCommandCenter = new CommitCommandsCenter(globalState, this, postCommitCommandsProviderRegistry);
|
||||
this.disposables.push(this.commitCommandCenter);
|
||||
|
||||
const actionButton = new ActionButtonCommand(this, this.commitCommandCenter);
|
||||
this.disposables.push(actionButton);
|
||||
actionButton.onDidChange(() => this._sourceControl.actionButton = actionButton.button);
|
||||
this._sourceControl.actionButton = actionButton.button;
|
||||
const commitActionButton = new CommitActionButton(this, this.commitCommandCenter);
|
||||
this.disposables.push(commitActionButton);
|
||||
commitActionButton.onDidChange(() => this._sourceControl.actionButton = commitActionButton.button);
|
||||
this._sourceControl.actionButton = commitActionButton.button;
|
||||
|
||||
const progressManager = new ProgressManager(this);
|
||||
this.disposables.push(progressManager);
|
||||
|
@ -1115,6 +1129,10 @@ export class Repository implements Disposable {
|
|||
return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path));
|
||||
}
|
||||
|
||||
diffBetweenShortStat(ref1: string, ref2: string): Promise<string> {
|
||||
return this.run(Operation.Diff, () => this.repository.diffBetweenShortStat(ref1, ref2));
|
||||
}
|
||||
|
||||
getMergeBase(ref1: string, ref2: string): Promise<string> {
|
||||
return this.run(Operation.MergeBase, () => this.repository.getMergeBase(ref1, ref2));
|
||||
}
|
||||
|
@ -1421,6 +1439,10 @@ export class Repository implements Disposable {
|
|||
await this.run(Operation.Move, () => this.repository.move(from, to));
|
||||
}
|
||||
|
||||
async getDefaultBranch(): Promise<Branch> {
|
||||
return await this.run(Operation.GetBranch, () => this.repository.getDefaultBranch());
|
||||
}
|
||||
|
||||
async getBranch(name: string): Promise<Branch> {
|
||||
return await this.run(Operation.GetBranch, () => this.repository.getBranch(name));
|
||||
}
|
||||
|
@ -1506,6 +1528,10 @@ export class Repository implements Disposable {
|
|||
return await this.repository.getCommit(ref);
|
||||
}
|
||||
|
||||
async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> {
|
||||
return await this.run(Operation.RevList, () => this.repository.getCommitCount(range));
|
||||
}
|
||||
|
||||
async reset(treeish: string, hard?: boolean): Promise<void> {
|
||||
await this.run(Operation.Reset, () => this.repository.reset(treeish, hard));
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.diffCommand.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.scmValidation.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts",
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"description": "Extension to add Grunt capabilities to VS Code.",
|
||||
"displayName": "Grunt support for VS Code",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"icon": "images/grunt.png",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"publisher": "vscode",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"version": "1.0.1",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "0.10.x"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"git": {
|
||||
"name": "jeff-hykin/better-shell-syntax",
|
||||
"repositoryUrl": "https://github.com/jeff-hykin/better-shell-syntax",
|
||||
"commitHash": "ce62ea59e8e522f8a07d8d8a2d1f992c6c600b91"
|
||||
"commitHash": "a3de7b32f1537194a83ee848838402fbf4b67424"
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
|
||||
"Once accepted there, we are happy to receive an update request."
|
||||
],
|
||||
"version": "https://github.com/jeff-hykin/better-shell-syntax/commit/ce62ea59e8e522f8a07d8d8a2d1f992c6c600b91",
|
||||
"version": "https://github.com/jeff-hykin/better-shell-syntax/commit/a3de7b32f1537194a83ee848838402fbf4b67424",
|
||||
"name": "Shell Script",
|
||||
"scopeName": "source.shell",
|
||||
"patterns": [
|
||||
|
@ -268,7 +268,7 @@
|
|||
"patterns": [
|
||||
{
|
||||
"begin": "(?=\\S)",
|
||||
"end": ";;",
|
||||
"end": ";;&?|;&",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.terminator.case-clause.shell"
|
||||
|
@ -311,7 +311,7 @@
|
|||
},
|
||||
{
|
||||
"begin": "(?<=\\))",
|
||||
"end": "(?=;;)",
|
||||
"end": "(?=;;&?|;&)",
|
||||
"name": "meta.scope.case-clause-body.shell",
|
||||
"patterns": [
|
||||
{
|
||||
|
|
|
@ -206,7 +206,7 @@ class DiagnosticsTelemetryManager extends Disposable {
|
|||
this._diagnosticCodesMap.clear();
|
||||
/* __GDPR__
|
||||
"typescript.diagnostics" : {
|
||||
"owner": "@aiday-mar",
|
||||
"owner": "aiday-mar",
|
||||
"diagnosticCodes" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
|
||||
"${include}": [
|
||||
"${TypeScriptCommonProperties}"
|
||||
|
@ -214,7 +214,7 @@ class DiagnosticsTelemetryManager extends Disposable {
|
|||
}
|
||||
*/
|
||||
this._telemetryReporter.logTelemetry('typescript.diagnostics', {
|
||||
diagnoticCodes: diagnosticCodes
|
||||
diagnosticCodes: diagnosticCodes
|
||||
});
|
||||
}
|
||||
}, 5 * 60 * 1000); // 5 minutes
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.83.0",
|
||||
"distro": "c13fa037c20b672a48c9e7990df10998974196f9",
|
||||
"distro": "5a794f00ac87e37b6abea46ccf030aeb4b323aeb",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
|
@ -70,7 +70,7 @@
|
|||
"@parcel/watcher": "2.1.0",
|
||||
"@vscode/iconv-lite-umd": "0.7.0",
|
||||
"@vscode/policy-watcher": "^1.1.4",
|
||||
"@vscode/proxy-agent": "^0.17.2",
|
||||
"@vscode/proxy-agent": "^0.17.4",
|
||||
"@vscode/ripgrep": "^1.15.5",
|
||||
"@vscode/spdlog": "^0.13.11",
|
||||
"@vscode/sqlite3": "5.1.6-vscode",
|
||||
|
@ -210,7 +210,7 @@
|
|||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsec": "0.2.7",
|
||||
"typescript": "^5.3.0-dev.20230911",
|
||||
"typescript": "^5.3.0-dev.20230919",
|
||||
"typescript-formatter": "7.1.0",
|
||||
"underscore": "^1.12.1",
|
||||
"util": "^0.12.4",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@parcel/watcher": "2.1.0",
|
||||
"@vscode/iconv-lite-umd": "0.7.0",
|
||||
"@vscode/proxy-agent": "^0.17.2",
|
||||
"@vscode/proxy-agent": "^0.17.4",
|
||||
"@vscode/ripgrep": "^1.15.5",
|
||||
"@vscode/spdlog": "^0.13.11",
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
|
|
|
@ -58,10 +58,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48"
|
||||
integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==
|
||||
|
||||
"@vscode/proxy-agent@^0.17.2":
|
||||
version "0.17.2"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.2.tgz#0e0dac24478e2d71a4fd1b2bb5f84dc61add79e2"
|
||||
integrity sha512-aKRo1YfUCsgEjHvr2HXfM6dwHhieyO6G+WHly7jewyyTJ1nANWEocS3JRnRbC4KjlajKhSUEOx838cdnY/vRtA==
|
||||
"@vscode/proxy-agent@^0.17.4":
|
||||
version "0.17.4"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.4.tgz#e3ffb63357353a428436f15a69de3453a5061f0c"
|
||||
integrity sha512-tX8eidofoJlZFRWzdiiW3wyu26hgIRk8HvM/RoP1wVSu3U/As36EgGIZYG6pPnqiythRqTcsddniVNA5M39g4w==
|
||||
dependencies:
|
||||
"@tootallnate/once" "^3.0.0"
|
||||
agent-base "^7.0.1"
|
||||
|
|
|
@ -17,6 +17,29 @@ import { FileAccess, RemoteAuthorities, Schemas } from 'vs/base/common/network';
|
|||
import * as platform from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export const { registerWindow, getWindows, onDidCreateWindow } = (function () {
|
||||
const windows: Window[] = [];
|
||||
const onDidCreateWindow = new event.Emitter<{ window: Window; disposableStore: DisposableStore }>();
|
||||
return {
|
||||
onDidCreateWindow: onDidCreateWindow.event,
|
||||
registerWindow(window: Window): IDisposable {
|
||||
windows.push(window);
|
||||
const disposableStore = new DisposableStore();
|
||||
disposableStore.add(toDisposable(() => {
|
||||
const index = windows.indexOf(window);
|
||||
if (index !== -1) {
|
||||
windows.splice(index, 1);
|
||||
}
|
||||
}));
|
||||
onDidCreateWindow.fire({ window, disposableStore });
|
||||
return disposableStore;
|
||||
},
|
||||
getWindows(): Window[] {
|
||||
return windows;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
export function clearNode(node: HTMLElement): void {
|
||||
while (node.firstChild) {
|
||||
node.firstChild.remove();
|
||||
|
@ -282,34 +305,37 @@ export function addDisposableThrottledListener<R, E extends Event = Event>(node:
|
|||
}
|
||||
|
||||
export function getComputedStyle(el: HTMLElement): CSSStyleDeclaration {
|
||||
return document.defaultView!.getComputedStyle(el, null);
|
||||
return el.ownerDocument.defaultView!.getComputedStyle(el, null);
|
||||
}
|
||||
|
||||
export function getClientArea(element: HTMLElement): Dimension {
|
||||
|
||||
const elDocument = element.ownerDocument;
|
||||
const elWindow = elDocument.defaultView?.window;
|
||||
|
||||
// Try with DOM clientWidth / clientHeight
|
||||
if (element !== document.body) {
|
||||
if (element !== elDocument.body) {
|
||||
return new Dimension(element.clientWidth, element.clientHeight);
|
||||
}
|
||||
|
||||
// If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight
|
||||
if (platform.isIOS && window.visualViewport) {
|
||||
return new Dimension(window.visualViewport.width, window.visualViewport.height);
|
||||
if (platform.isIOS && elWindow?.visualViewport) {
|
||||
return new Dimension(elWindow.visualViewport.width, elWindow.visualViewport.height);
|
||||
}
|
||||
|
||||
// Try innerWidth / innerHeight
|
||||
if (window.innerWidth && window.innerHeight) {
|
||||
return new Dimension(window.innerWidth, window.innerHeight);
|
||||
if (elWindow?.innerWidth && elWindow.innerHeight) {
|
||||
return new Dimension(elWindow.innerWidth, elWindow.innerHeight);
|
||||
}
|
||||
|
||||
// Try with document.body.clientWidth / document.body.clientHeight
|
||||
if (document.body && document.body.clientWidth && document.body.clientHeight) {
|
||||
return new Dimension(document.body.clientWidth, document.body.clientHeight);
|
||||
if (elDocument.body && elDocument.body.clientWidth && elDocument.body.clientHeight) {
|
||||
return new Dimension(elDocument.body.clientWidth, elDocument.body.clientHeight);
|
||||
}
|
||||
|
||||
// Try with document.documentElement.clientWidth / document.documentElement.clientHeight
|
||||
if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientHeight) {
|
||||
return new Dimension(document.documentElement.clientWidth, document.documentElement.clientHeight);
|
||||
if (elDocument.documentElement && elDocument.documentElement.clientWidth && elDocument.documentElement.clientHeight) {
|
||||
return new Dimension(elDocument.documentElement.clientWidth, elDocument.documentElement.clientHeight);
|
||||
}
|
||||
|
||||
throw new Error('Unable to figure out browser width and height');
|
||||
|
@ -431,8 +457,8 @@ export function getTopLeftOffset(element: HTMLElement): IDomPosition {
|
|||
|
||||
while (
|
||||
(element = <HTMLElement>element.parentNode) !== null
|
||||
&& element !== document.body
|
||||
&& element !== document.documentElement
|
||||
&& element !== element.ownerDocument.body
|
||||
&& element !== element.ownerDocument.documentElement
|
||||
) {
|
||||
top -= element.scrollTop;
|
||||
const c = isShadowRoot(element) ? null : getComputedStyle(element);
|
||||
|
@ -498,8 +524,8 @@ export function position(element: HTMLElement, top: number, right?: number, bott
|
|||
export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePosition {
|
||||
const bb = domNode.getBoundingClientRect();
|
||||
return {
|
||||
left: bb.left + window.scrollX,
|
||||
top: bb.top + window.scrollY,
|
||||
left: bb.left + (domNode.ownerDocument.defaultView?.scrollX ?? 0),
|
||||
top: bb.top + (domNode.ownerDocument.defaultView?.scrollY ?? 0),
|
||||
width: bb.width,
|
||||
height: bb.height
|
||||
};
|
||||
|
@ -518,7 +544,7 @@ export function getDomNodeZoomLevel(domNode: HTMLElement): number {
|
|||
}
|
||||
|
||||
testElement = testElement.parentElement;
|
||||
} while (testElement !== null && testElement !== document.documentElement);
|
||||
} while (testElement !== null && testElement !== testElement.ownerDocument.documentElement);
|
||||
|
||||
return zoom;
|
||||
}
|
||||
|
@ -602,7 +628,7 @@ export function setParentFlowTo(fromChildElement: HTMLElement, toParentElement:
|
|||
function getParentFlowToElement(node: HTMLElement): HTMLElement | null {
|
||||
const flowToParentId = node.dataset[parentFlowToDataKey];
|
||||
if (typeof flowToParentId === 'string') {
|
||||
return document.getElementById(flowToParentId);
|
||||
return node.ownerDocument.getElementById(flowToParentId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -671,7 +697,7 @@ export function isInShadowDOM(domNode: Node): boolean {
|
|||
|
||||
export function getShadowRoot(domNode: Node): ShadowRoot | null {
|
||||
while (domNode.parentNode) {
|
||||
if (domNode === document.body) {
|
||||
if (domNode === domNode.ownerDocument?.body) {
|
||||
// reached the body
|
||||
return null;
|
||||
}
|
||||
|
@ -680,8 +706,12 @@ export function getShadowRoot(domNode: Node): ShadowRoot | null {
|
|||
return isShadowRoot(domNode) ? domNode : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active element across all child windows.
|
||||
* Use this instead of `document.activeElement` to handle multiple windows.
|
||||
*/
|
||||
export function getActiveElement(): Element | null {
|
||||
let result = document.activeElement;
|
||||
let result = getActiveDocument().activeElement;
|
||||
|
||||
while (result?.shadowRoot) {
|
||||
result = result.shadowRoot.activeElement;
|
||||
|
@ -690,6 +720,15 @@ export function getActiveElement(): Element | null {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active document across all child windows.
|
||||
* Use this instead of `document` when reacting to dom events to handle multiple windows.
|
||||
*/
|
||||
export function getActiveDocument(): Document {
|
||||
const documents = [document, ...getWindows().map(w => w.document)];
|
||||
return documents.find(doc => doc.hasFocus()) ?? document;
|
||||
}
|
||||
|
||||
export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0], beforeAppend?: (style: HTMLStyleElement) => void): HTMLStyleElement {
|
||||
const style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
|
@ -875,15 +914,19 @@ class FocusTracker extends Disposable implements IFocusTracker {
|
|||
|
||||
private _refreshStateHandler: () => void;
|
||||
|
||||
private static hasFocusWithin(element: HTMLElement): boolean {
|
||||
const shadowRoot = getShadowRoot(element);
|
||||
const activeElement = (shadowRoot ? shadowRoot.activeElement : document.activeElement);
|
||||
return isAncestor(activeElement, element);
|
||||
private static hasFocusWithin(element: HTMLElement | Window): boolean {
|
||||
if (isHTMLElement(element)) {
|
||||
const shadowRoot = getShadowRoot(element);
|
||||
const activeElement = (shadowRoot ? shadowRoot.activeElement : element.ownerDocument.activeElement);
|
||||
return isAncestor(activeElement, element);
|
||||
} else {
|
||||
return isAncestor(window.document.activeElement, window.document);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(element: HTMLElement | Window) {
|
||||
super();
|
||||
let hasFocus = FocusTracker.hasFocusWithin(<HTMLElement>element);
|
||||
let hasFocus = FocusTracker.hasFocusWithin(element);
|
||||
let loosingFocus = false;
|
||||
|
||||
const onFocus = () => {
|
||||
|
@ -1092,7 +1135,7 @@ export function removeTabIndexAndUpdateFocus(node: HTMLElement): void {
|
|||
// standard DOM behavior is to move focus to the <body> element. We
|
||||
// typically never want that, rather put focus to the closest element
|
||||
// in the hierarchy of the parent DOM nodes.
|
||||
if (document.activeElement === node) {
|
||||
if (node.ownerDocument.activeElement === node) {
|
||||
const parentFocusable = findParentWithAttribute(node.parentElement, 'tabIndex');
|
||||
parentFocusable?.focus();
|
||||
}
|
||||
|
|
|
@ -69,8 +69,8 @@ export class StandardMouseEvent implements IMouseEvent {
|
|||
this.posy = e.pageY;
|
||||
} else {
|
||||
// Probably hit by MSGestureEvent
|
||||
this.posx = e.clientX + document.body.scrollLeft + document.documentElement!.scrollLeft;
|
||||
this.posy = e.clientY + document.body.scrollTop + document.documentElement!.scrollTop;
|
||||
this.posx = e.clientX + this.target.ownerDocument.body.scrollLeft + this.target.ownerDocument.documentElement.scrollLeft;
|
||||
this.posy = e.clientY + this.target.ownerDocument.body.scrollTop + this.target.ownerDocument.documentElement.scrollTop;
|
||||
}
|
||||
|
||||
// Find the position of the iframe this code is executing in relative to the iframe where the event was captured.
|
||||
|
|
|
@ -778,7 +778,7 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
|
|||
private _onDidViewChange: Event<number | undefined>;
|
||||
readonly onDidChange: Event<number | undefined>;
|
||||
|
||||
private disposables = new DisposableStore();
|
||||
private readonly disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
readonly view: IView,
|
||||
|
|
38
src/vs/base/browser/ui/icons/iconSelectBox.css
Normal file
38
src/vs/base/browser/ui/icons/iconSelectBox.css
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.icon-select-box > .icon-select-box-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon-select-box .icon-select-icons-container {
|
||||
height: 100%;
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
.icon-select-box .icon-select-icons-container > .icon-container {
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.icon-select-box .icon-select-icons-container > .icon-container.focused {
|
||||
outline: 1px dashed var(--vscode-toolbar-hoverOutline);
|
||||
outline-offset: -1px;
|
||||
background-color: var(--vscode-toolbar-hoverBackground);
|
||||
}
|
||||
|
||||
.icon-select-box .icon-select-id-container .icon-select-id-label {
|
||||
height: 24px;
|
||||
padding: 10px;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.icon-select-box .icon-select-id-container .icon-select-id-label .highlight {
|
||||
color: var(--vscode-list-highlightForeground);
|
||||
}
|
287
src/vs/base/browser/ui/icons/iconSelectBox.ts
Normal file
287
src/vs/base/browser/ui/icons/iconSelectBox.ts
Normal file
|
@ -0,0 +1,287 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./iconSelectBox';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { IInputBoxStyles, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, DisposableStore, Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||
|
||||
export interface IIconSelectBoxOptions {
|
||||
readonly icons: ThemeIcon[];
|
||||
readonly inputBoxStyles: IInputBoxStyles;
|
||||
}
|
||||
|
||||
interface IRenderedIconItem {
|
||||
readonly icon: ThemeIcon;
|
||||
readonly element: HTMLElement;
|
||||
readonly highlightMatches?: IMatch[];
|
||||
}
|
||||
|
||||
export class IconSelectBox extends Disposable {
|
||||
|
||||
private static InstanceCount = 0;
|
||||
readonly domId = `icon_select_box_id_${++IconSelectBox.InstanceCount}`;
|
||||
|
||||
readonly domNode: HTMLElement;
|
||||
|
||||
private _onDidSelect = this._register(new Emitter<ThemeIcon>());
|
||||
readonly onDidSelect = this._onDidSelect.event;
|
||||
|
||||
private renderedIcons: IRenderedIconItem[] = [];
|
||||
|
||||
private focusedItemIndex: number = 0;
|
||||
private numberOfElementsPerRow: number = 1;
|
||||
|
||||
protected inputBox: InputBox | undefined;
|
||||
private scrollableElement: DomScrollableElement | undefined;
|
||||
private iconIdElement: HighlightedLabel | undefined;
|
||||
private readonly iconContainerWidth = 36;
|
||||
private readonly iconContainerHeight = 32;
|
||||
|
||||
constructor(
|
||||
private readonly options: IIconSelectBoxOptions,
|
||||
) {
|
||||
super();
|
||||
this.domNode = dom.$('.icon-select-box');
|
||||
this._register(this.create());
|
||||
}
|
||||
|
||||
private create(): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const iconSelectBoxContainer = dom.append(this.domNode, dom.$('.icon-select-box-container'));
|
||||
iconSelectBoxContainer.style.margin = '10px 15px';
|
||||
|
||||
const iconSelectInputContainer = dom.append(iconSelectBoxContainer, dom.$('.icon-select-input-container'));
|
||||
iconSelectInputContainer.style.paddingBottom = '10px';
|
||||
this.inputBox = disposables.add(new InputBox(iconSelectInputContainer, undefined, {
|
||||
placeholder: localize('iconSelect.placeholder', "Search icons"),
|
||||
inputBoxStyles: this.options.inputBoxStyles,
|
||||
}));
|
||||
|
||||
const iconsContainer = dom.$('.icon-select-icons-container', { id: `${this.domId}_icons` });
|
||||
iconsContainer.style.paddingRight = '10px';
|
||||
iconsContainer.role = 'listbox';
|
||||
iconsContainer.tabIndex = 0;
|
||||
this.scrollableElement = disposables.add(new DomScrollableElement(iconsContainer, {
|
||||
useShadows: false,
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
}));
|
||||
dom.append(iconSelectBoxContainer, this.scrollableElement.getDomNode());
|
||||
this.iconIdElement = new HighlightedLabel(dom.append(dom.append(iconSelectBoxContainer, dom.$('.icon-select-id-container')), dom.$('.icon-select-id-label')));
|
||||
|
||||
const iconsDisposables = disposables.add(new MutableDisposable());
|
||||
iconsDisposables.value = this.renderIcons(this.options.icons, [], iconsContainer);
|
||||
this.scrollableElement.scanDomNode();
|
||||
|
||||
disposables.add(this.inputBox.onDidChange(value => {
|
||||
const icons = [], matches = [];
|
||||
for (const icon of this.options.icons) {
|
||||
const match = this.matchesContiguous(value, icon.id);
|
||||
if (match) {
|
||||
icons.push(icon);
|
||||
matches.push(match);
|
||||
}
|
||||
}
|
||||
iconsDisposables.value = this.renderIcons(icons, matches, iconsContainer);
|
||||
this.scrollableElement?.scanDomNode();
|
||||
}));
|
||||
|
||||
this.inputBox.inputElement.role = 'combobox';
|
||||
this.inputBox.inputElement.ariaHasPopup = 'menu';
|
||||
this.inputBox.inputElement.ariaAutoComplete = 'list';
|
||||
this.inputBox.inputElement.ariaExpanded = 'true';
|
||||
this.inputBox.inputElement.setAttribute('aria-controls', iconsContainer.id);
|
||||
|
||||
return disposables;
|
||||
}
|
||||
|
||||
private renderIcons(icons: ThemeIcon[], matches: IMatch[][], container: HTMLElement): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
dom.clearNode(container);
|
||||
const focusedIcon = this.renderedIcons[this.focusedItemIndex]?.icon;
|
||||
let focusedIconIndex = 0;
|
||||
const renderedIcons: IRenderedIconItem[] = [];
|
||||
if (icons.length) {
|
||||
for (let index = 0; index < icons.length; index++) {
|
||||
const icon = icons[index];
|
||||
const iconContainer = dom.append(container, dom.$('.icon-container', { id: `${this.domId}_icons_${index}` }));
|
||||
iconContainer.style.width = `${this.iconContainerWidth}px`;
|
||||
iconContainer.style.height = `${this.iconContainerHeight}px`;
|
||||
iconContainer.title = icon.id;
|
||||
iconContainer.role = 'button';
|
||||
iconContainer.setAttribute('aria-setsize', `${icons.length}`);
|
||||
iconContainer.setAttribute('aria-posinset', `${index + 1}`);
|
||||
dom.append(iconContainer, dom.$(ThemeIcon.asCSSSelector(icon)));
|
||||
renderedIcons.push({ icon, element: iconContainer, highlightMatches: matches[index] });
|
||||
|
||||
disposables.add(dom.addDisposableListener(iconContainer, dom.EventType.CLICK, (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
this.setSelection(index);
|
||||
}));
|
||||
|
||||
disposables.add(dom.addDisposableListener(iconContainer, dom.EventType.MOUSE_OVER, (e: MouseEvent) => {
|
||||
this.focusIcon(index);
|
||||
}));
|
||||
|
||||
if (icon === focusedIcon) {
|
||||
focusedIconIndex = index;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const noResults = localize('iconSelect.noResults', "No results");
|
||||
dom.append(container, dom.$('.icon-no-results', undefined, noResults));
|
||||
alert(noResults);
|
||||
}
|
||||
|
||||
this.renderedIcons.splice(0, this.renderedIcons.length, ...renderedIcons);
|
||||
this.focusIcon(focusedIconIndex);
|
||||
|
||||
return disposables;
|
||||
}
|
||||
|
||||
private focusIcon(index: number): void {
|
||||
const existing = this.renderedIcons[this.focusedItemIndex];
|
||||
if (existing) {
|
||||
existing.element.classList.remove('focused');
|
||||
}
|
||||
|
||||
this.focusedItemIndex = index;
|
||||
const renderedItem = this.renderedIcons[index];
|
||||
|
||||
if (renderedItem) {
|
||||
renderedItem.element.classList.add('focused');
|
||||
}
|
||||
|
||||
if (this.inputBox) {
|
||||
if (renderedItem) {
|
||||
this.inputBox.inputElement.setAttribute('aria-activedescendant', renderedItem.element.id);
|
||||
} else {
|
||||
this.inputBox.inputElement.removeAttribute('aria-activedescendant');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.iconIdElement) {
|
||||
if (renderedItem) {
|
||||
this.iconIdElement.set(renderedItem.icon.id, renderedItem.highlightMatches);
|
||||
} else {
|
||||
this.iconIdElement.set('');
|
||||
}
|
||||
}
|
||||
|
||||
this.reveal(index);
|
||||
}
|
||||
|
||||
private reveal(index: number): void {
|
||||
if (!this.scrollableElement) {
|
||||
return;
|
||||
}
|
||||
if (index < 0 || index >= this.renderedIcons.length) {
|
||||
return;
|
||||
}
|
||||
const element = this.renderedIcons[index].element;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
const { height } = this.scrollableElement.getScrollDimensions();
|
||||
const { scrollTop } = this.scrollableElement.getScrollPosition();
|
||||
if (element.offsetTop + this.iconContainerHeight > scrollTop + height) {
|
||||
this.scrollableElement.setScrollPosition({ scrollTop: element.offsetTop + this.iconContainerHeight - height });
|
||||
} else if (element.offsetTop < scrollTop) {
|
||||
this.scrollableElement.setScrollPosition({ scrollTop: element.offsetTop });
|
||||
}
|
||||
}
|
||||
|
||||
private matchesContiguous(word: string, wordToMatchAgainst: string): IMatch[] | null {
|
||||
const matchIndex = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase());
|
||||
if (matchIndex !== -1) {
|
||||
return [{ start: matchIndex, end: matchIndex + word.length }];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
layout(dimension: dom.Dimension): void {
|
||||
this.domNode.style.width = `${dimension.width}px`;
|
||||
this.domNode.style.height = `${dimension.height}px`;
|
||||
|
||||
const iconsContainerWidth = dimension.width - 40;
|
||||
this.numberOfElementsPerRow = Math.floor(iconsContainerWidth / this.iconContainerWidth);
|
||||
if (this.numberOfElementsPerRow === 0) {
|
||||
throw new Error('Insufficient width');
|
||||
}
|
||||
|
||||
const extraSpace = iconsContainerWidth % this.iconContainerWidth;
|
||||
const margin = Math.floor(extraSpace / this.numberOfElementsPerRow);
|
||||
for (const { element } of this.renderedIcons) {
|
||||
element.style.marginRight = `${margin}px`;
|
||||
}
|
||||
|
||||
if (this.scrollableElement) {
|
||||
this.scrollableElement.getDomNode().style.height = `${dimension.height - 80}px`;
|
||||
this.scrollableElement.scanDomNode();
|
||||
}
|
||||
}
|
||||
|
||||
getFocus(): number[] {
|
||||
return [this.focusedItemIndex];
|
||||
}
|
||||
|
||||
setSelection(index: number): void {
|
||||
if (index < 0 || index >= this.renderedIcons.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
this.focusIcon(index);
|
||||
this._onDidSelect.fire(this.renderedIcons[index].icon);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.inputBox?.focus();
|
||||
this.focusIcon(0);
|
||||
}
|
||||
|
||||
focusNext(): void {
|
||||
this.focusIcon((this.focusedItemIndex + 1) % this.renderedIcons.length);
|
||||
}
|
||||
|
||||
focusPrevious(): void {
|
||||
this.focusIcon((this.focusedItemIndex - 1 + this.renderedIcons.length) % this.renderedIcons.length);
|
||||
}
|
||||
|
||||
focusNextRow(): void {
|
||||
let nextRowIndex = this.focusedItemIndex + this.numberOfElementsPerRow;
|
||||
if (nextRowIndex >= this.renderedIcons.length) {
|
||||
nextRowIndex = (nextRowIndex + 1) % this.numberOfElementsPerRow;
|
||||
nextRowIndex = nextRowIndex >= this.renderedIcons.length ? 0 : nextRowIndex;
|
||||
}
|
||||
this.focusIcon(nextRowIndex);
|
||||
}
|
||||
|
||||
focusPreviousRow(): void {
|
||||
let previousRowIndex = this.focusedItemIndex - this.numberOfElementsPerRow;
|
||||
if (previousRowIndex < 0) {
|
||||
const numberOfRows = Math.floor(this.renderedIcons.length / this.numberOfElementsPerRow);
|
||||
previousRowIndex = this.focusedItemIndex + (this.numberOfElementsPerRow * numberOfRows) - 1;
|
||||
previousRowIndex = previousRowIndex < 0
|
||||
? this.renderedIcons.length - 1
|
||||
: previousRowIndex >= this.renderedIcons.length
|
||||
? previousRowIndex - this.numberOfElementsPerRow
|
||||
: previousRowIndex;
|
||||
}
|
||||
this.focusIcon(previousRowIndex);
|
||||
}
|
||||
|
||||
getFocusedIcon(): ThemeIcon {
|
||||
return this.renderedIcons[this.focusedItemIndex].icon;
|
||||
}
|
||||
|
||||
}
|
|
@ -173,7 +173,7 @@ interface IPointerEventFactory {
|
|||
|
||||
class MouseEventFactory implements IPointerEventFactory {
|
||||
|
||||
private disposables = new DisposableStore();
|
||||
private readonly disposables = new DisposableStore();
|
||||
|
||||
@memoize
|
||||
get onPointerMove(): Event<PointerEvent> {
|
||||
|
@ -192,7 +192,7 @@ class MouseEventFactory implements IPointerEventFactory {
|
|||
|
||||
class GestureEventFactory implements IPointerEventFactory {
|
||||
|
||||
private disposables = new DisposableStore();
|
||||
private readonly disposables = new DisposableStore();
|
||||
|
||||
@memoize
|
||||
get onPointerMove(): Event<PointerEvent> {
|
||||
|
|
|
@ -1027,7 +1027,7 @@ class FindController<T, TFilterData> implements IDisposable {
|
|||
private readonly _onDidChangeOpenState = new Emitter<boolean>();
|
||||
readonly onDidChangeOpenState = this._onDidChangeOpenState.event;
|
||||
|
||||
private enabledDisposables = new DisposableStore();
|
||||
private readonly enabledDisposables = new DisposableStore();
|
||||
private readonly disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
|
@ -1085,8 +1085,7 @@ class FindController<T, TFilterData> implements IDisposable {
|
|||
this._history = this.widget.getHistory();
|
||||
this.widget = undefined;
|
||||
|
||||
this.enabledDisposables.dispose();
|
||||
this.enabledDisposables = new DisposableStore();
|
||||
this.enabledDisposables.clear();
|
||||
|
||||
this.previousPattern = this.pattern;
|
||||
this.onDidChangeValue('');
|
||||
|
|
|
@ -187,6 +187,12 @@ export function forEachAdjacent<T>(arr: T[], f: (item1: T | undefined, item2: T
|
|||
}
|
||||
}
|
||||
|
||||
export function forEachWithNeighbors<T>(arr: T[], f: (before: T | undefined, element: T, after: T | undefined) => void): void {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
f(i === 0 ? undefined : arr[i - 1], arr[i], i + 1 === arr.length ? undefined : arr[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
interface IMutableSplice<T> extends ISplice<T> {
|
||||
readonly toInsert: T[];
|
||||
deleteCount: number;
|
||||
|
|
|
@ -324,7 +324,7 @@ export const Codicon = {
|
|||
note: register('note', 0xeb26),
|
||||
octoface: register('octoface', 0xeb27),
|
||||
openPreview: register('open-preview', 0xeb28),
|
||||
package_: register('package', 0xeb29),
|
||||
package: register('package', 0xeb29),
|
||||
paintcan: register('paintcan', 0xeb2a),
|
||||
pin: register('pin', 0xeb2b),
|
||||
play: register('play', 0xeb2c),
|
||||
|
|
|
@ -120,14 +120,15 @@ export class HistoryNavigator2<T> {
|
|||
private head: HistoryNode<T>;
|
||||
private tail: HistoryNode<T>;
|
||||
private cursor: HistoryNode<T>;
|
||||
private size: number;
|
||||
private _size: number;
|
||||
get size(): number { return this._size; }
|
||||
|
||||
constructor(history: readonly T[], private capacity: number = 10) {
|
||||
if (history.length < 1) {
|
||||
throw new Error('not supported');
|
||||
}
|
||||
|
||||
this.size = 1;
|
||||
this._size = 1;
|
||||
this.head = this.tail = this.cursor = {
|
||||
value: history[0],
|
||||
previous: undefined,
|
||||
|
@ -150,7 +151,7 @@ export class HistoryNavigator2<T> {
|
|||
this.tail.next = node;
|
||||
this.tail = node;
|
||||
this.cursor = this.tail;
|
||||
this.size++;
|
||||
this._size++;
|
||||
|
||||
if (this.valueSet.has(value)) {
|
||||
this._deleteFromList(value);
|
||||
|
@ -158,12 +159,12 @@ export class HistoryNavigator2<T> {
|
|||
this.valueSet.add(value);
|
||||
}
|
||||
|
||||
while (this.size > this.capacity) {
|
||||
while (this._size > this.capacity) {
|
||||
this.valueSet.delete(this.head.value);
|
||||
|
||||
this.head = this.head.next!;
|
||||
this.head.previous = undefined;
|
||||
this.size--;
|
||||
this._size--;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,6 +189,24 @@ export class HistoryNavigator2<T> {
|
|||
return oldValue;
|
||||
}
|
||||
|
||||
prepend(value: T): void {
|
||||
if (this._size === this.capacity || this.valueSet.has(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node: HistoryNode<T> = {
|
||||
value,
|
||||
previous: undefined,
|
||||
next: this.head
|
||||
};
|
||||
|
||||
this.head.previous = node;
|
||||
this.head = node;
|
||||
this._size++;
|
||||
|
||||
this.valueSet.add(value);
|
||||
}
|
||||
|
||||
isAtEnd(): boolean {
|
||||
return this.cursor === this.tail;
|
||||
}
|
||||
|
@ -243,7 +262,7 @@ export class HistoryNavigator2<T> {
|
|||
temp.next!.previous = temp.previous;
|
||||
}
|
||||
|
||||
this.size--;
|
||||
this._size--;
|
||||
}
|
||||
|
||||
temp = temp.next!;
|
||||
|
|
|
@ -30,6 +30,12 @@ export namespace Iterable {
|
|||
return iterable || _empty;
|
||||
}
|
||||
|
||||
export function* reverse<T>(array: Array<T>): Iterable<T> {
|
||||
for (let i = array.length - 1; i >= 0; i--) {
|
||||
yield array[i];
|
||||
}
|
||||
}
|
||||
|
||||
export function isEmpty<T>(iterable: Iterable<T> | undefined | null): boolean {
|
||||
return !iterable || iterable[Symbol.iterator]().next().done === true;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,10 @@ class ResourceMapEntry<T> {
|
|||
constructor(readonly uri: URI, readonly value: T) { }
|
||||
}
|
||||
|
||||
function isEntries<T>(arg: ResourceMap<T> | ResourceMapKeyFn | readonly (readonly [URI, T])[] | undefined): arg is readonly (readonly [URI, T])[] {
|
||||
return Array.isArray(arg);
|
||||
}
|
||||
|
||||
export class ResourceMap<T> implements Map<URI, T> {
|
||||
|
||||
private static readonly defaultToKey = (resource: URI) => resource.toString();
|
||||
|
@ -63,13 +67,27 @@ export class ResourceMap<T> implements Map<URI, T> {
|
|||
*/
|
||||
constructor(other?: ResourceMap<T>, toKey?: ResourceMapKeyFn);
|
||||
|
||||
constructor(mapOrKeyFn?: ResourceMap<T> | ResourceMapKeyFn, toKey?: ResourceMapKeyFn) {
|
||||
if (mapOrKeyFn instanceof ResourceMap) {
|
||||
this.map = new Map(mapOrKeyFn.map);
|
||||
/**
|
||||
*
|
||||
* @param other Another resource which this maps is created from
|
||||
* @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
|
||||
*/
|
||||
constructor(entries?: readonly (readonly [URI, T])[], toKey?: ResourceMapKeyFn);
|
||||
|
||||
constructor(arg?: ResourceMap<T> | ResourceMapKeyFn | readonly (readonly [URI, T])[], toKey?: ResourceMapKeyFn) {
|
||||
if (arg instanceof ResourceMap) {
|
||||
this.map = new Map(arg.map);
|
||||
this.toKey = toKey ?? ResourceMap.defaultToKey;
|
||||
} else if (isEntries(arg)) {
|
||||
this.map = new Map();
|
||||
this.toKey = toKey ?? ResourceMap.defaultToKey;
|
||||
|
||||
for (const [resource, value] of arg) {
|
||||
this.set(resource, value);
|
||||
}
|
||||
} else {
|
||||
this.map = new Map();
|
||||
this.toKey = mapOrKeyFn ?? ResourceMap.defaultToKey;
|
||||
this.toKey = arg ?? ResourceMap.defaultToKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
export const enum MarshalledId {
|
||||
Uri = 1,
|
||||
Regexp,
|
||||
|
@ -19,6 +18,7 @@ export const enum MarshalledId {
|
|||
TimelineActionContext,
|
||||
NotebookCellActionContext,
|
||||
NotebookActionContext,
|
||||
TerminalContext,
|
||||
TestItemContext,
|
||||
Date,
|
||||
TestMessageMenuArgs,
|
||||
|
|
241
src/vs/base/common/tfIdf.ts
Normal file
241
src/vs/base/common/tfIdf.ts
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
type SparseEmbedding = Record</* word */ string, /* weight */number>;
|
||||
type TermFrequencies = Map</* word */ string, /*occurrences*/ number>;
|
||||
type DocumentOccurrences = Map</* word */ string, /*documentOccurrences*/ number>;
|
||||
|
||||
function countMapFrom<K>(values: Iterable<K>): Map<K, number> {
|
||||
const map = new Map<K, number>();
|
||||
for (const value of values) {
|
||||
map.set(value, (map.get(value) ?? 0) + 1);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
interface DocumentChunkEntry {
|
||||
readonly text: string;
|
||||
readonly tf: TermFrequencies;
|
||||
}
|
||||
|
||||
export interface TfIdfDocument {
|
||||
readonly key: string;
|
||||
readonly textChunks: readonly string[];
|
||||
}
|
||||
|
||||
export interface TfIdfScore {
|
||||
readonly key: string;
|
||||
/**
|
||||
* An unbounded number.
|
||||
*/
|
||||
readonly score: number;
|
||||
}
|
||||
|
||||
export interface NormalizedTfIdfScore {
|
||||
readonly key: string;
|
||||
/**
|
||||
* A number between 0 and 1.
|
||||
*/
|
||||
readonly score: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of tf-idf (term frequency-inverse document frequency) for a set of
|
||||
* documents where each document contains one or more chunks of text.
|
||||
* Each document is identified by a key, and the score for each document is computed
|
||||
* by taking the max score over all the chunks in the document.
|
||||
*/
|
||||
export class TfIdfCalculator {
|
||||
calculateScores(query: string, token: CancellationToken): TfIdfScore[] {
|
||||
const embedding = this.computeEmbedding(query);
|
||||
const idfCache = new Map<string, number>();
|
||||
const scores: TfIdfScore[] = [];
|
||||
// For each document, generate one score
|
||||
for (const [key, doc] of this.documents) {
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const chunk of doc.chunks) {
|
||||
const score = this.computeSimilarityScore(chunk, embedding, idfCache);
|
||||
if (score > 0) {
|
||||
scores.push({ key, score });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count how many times each term (word) appears in a string.
|
||||
*/
|
||||
private static termFrequencies(input: string): TermFrequencies {
|
||||
return countMapFrom(TfIdfCalculator.splitTerms(input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Break a string into terms (words).
|
||||
*/
|
||||
private static *splitTerms(input: string): Iterable<string> {
|
||||
const normalize = (word: string) => word.toLowerCase();
|
||||
|
||||
// Only match on words that are at least 3 characters long and start with a letter
|
||||
for (const [word] of input.matchAll(/\b\p{Letter}[\p{Letter}\d]{2,}\b/gu)) {
|
||||
yield normalize(word);
|
||||
|
||||
// eslint-disable-next-line local/code-no-look-behind-regex
|
||||
const camelParts = word.split(/(?<=[a-z])(?=[A-Z])/g);
|
||||
if (camelParts.length > 1) {
|
||||
for (const part of camelParts) {
|
||||
// Require at least 3 letters in the parts of a camel case word
|
||||
if (part.length > 2 && /\p{Letter}{3,}/gu.test(part)) {
|
||||
yield normalize(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Total number of chunks
|
||||
*/
|
||||
private chunkCount = 0;
|
||||
|
||||
private readonly chunkOccurrences: DocumentOccurrences = new Map</* word */ string, /*documentOccurrences*/ number>();
|
||||
|
||||
private readonly documents = new Map</* key */ string, {
|
||||
readonly chunks: ReadonlyArray<DocumentChunkEntry>;
|
||||
}>();
|
||||
|
||||
updateDocuments(documents: ReadonlyArray<TfIdfDocument>): this {
|
||||
for (const { key } of documents) {
|
||||
this.deleteDocument(key);
|
||||
}
|
||||
|
||||
for (const doc of documents) {
|
||||
const chunks: Array<{ text: string; tf: TermFrequencies }> = [];
|
||||
for (const text of doc.textChunks) {
|
||||
// TODO: See if we can compute the tf lazily
|
||||
// The challenge is that we need to also update the `chunkOccurrences`
|
||||
// and all of those updates need to get flushed before the real TF-IDF of
|
||||
// anything is computed.
|
||||
const tf = TfIdfCalculator.termFrequencies(text);
|
||||
|
||||
// Update occurrences list
|
||||
for (const term of tf.keys()) {
|
||||
this.chunkOccurrences.set(term, (this.chunkOccurrences.get(term) ?? 0) + 1);
|
||||
}
|
||||
|
||||
chunks.push({ text, tf });
|
||||
}
|
||||
|
||||
this.chunkCount += chunks.length;
|
||||
this.documents.set(doc.key, { chunks });
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
deleteDocument(key: string) {
|
||||
const doc = this.documents.get(key);
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.documents.delete(key);
|
||||
this.chunkCount -= doc.chunks.length;
|
||||
|
||||
// Update term occurrences for the document
|
||||
for (const chunk of doc.chunks) {
|
||||
for (const term of chunk.tf.keys()) {
|
||||
const currentOccurrences = this.chunkOccurrences.get(term);
|
||||
if (typeof currentOccurrences === 'number') {
|
||||
const newOccurrences = currentOccurrences - 1;
|
||||
if (newOccurrences <= 0) {
|
||||
this.chunkOccurrences.delete(term);
|
||||
} else {
|
||||
this.chunkOccurrences.set(term, newOccurrences);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private computeSimilarityScore(chunk: DocumentChunkEntry, queryEmbedding: SparseEmbedding, idfCache: Map<string, number>): number {
|
||||
// Compute the dot product between the chunk's embedding and the query embedding
|
||||
|
||||
// Note that the chunk embedding is computed lazily on a per-term basis.
|
||||
// This lets us skip a large number of calculations because the majority
|
||||
// of chunks do not share any terms with the query.
|
||||
|
||||
let sum = 0;
|
||||
for (const [term, termTfidf] of Object.entries(queryEmbedding)) {
|
||||
const chunkTf = chunk.tf.get(term);
|
||||
if (!chunkTf) {
|
||||
// Term does not appear in chunk so it has no contribution
|
||||
continue;
|
||||
}
|
||||
|
||||
let chunkIdf = idfCache.get(term);
|
||||
if (typeof chunkIdf !== 'number') {
|
||||
chunkIdf = this.computeIdf(term);
|
||||
idfCache.set(term, chunkIdf);
|
||||
}
|
||||
|
||||
const chunkTfidf = chunkTf * chunkIdf;
|
||||
sum += chunkTfidf * termTfidf;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
private computeEmbedding(input: string): SparseEmbedding {
|
||||
const tf = TfIdfCalculator.termFrequencies(input);
|
||||
return this.computeTfidf(tf);
|
||||
}
|
||||
|
||||
private computeIdf(term: string): number {
|
||||
const chunkOccurrences = this.chunkOccurrences.get(term) ?? 0;
|
||||
return chunkOccurrences > 0
|
||||
? Math.log((this.chunkCount + 1) / chunkOccurrences)
|
||||
: 0;
|
||||
}
|
||||
|
||||
private computeTfidf(termFrequencies: TermFrequencies): SparseEmbedding {
|
||||
const embedding = Object.create(null);
|
||||
for (const [word, occurrences] of termFrequencies) {
|
||||
const idf = this.computeIdf(word);
|
||||
if (idf > 0) {
|
||||
embedding[word] = occurrences * idf;
|
||||
}
|
||||
}
|
||||
return embedding;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the scores to be between 0 and 1 and sort them decending.
|
||||
* @param scores array of scores from {@link TfIdfCalculator.calculateScores}
|
||||
* @returns normalized scores
|
||||
*/
|
||||
export function normalizeTfIdfScores(scores: TfIdfScore[]): NormalizedTfIdfScore[] {
|
||||
|
||||
// copy of scores
|
||||
const result = scores.slice(0) as { score: number }[];
|
||||
|
||||
// sort descending
|
||||
result.sort((a, b) => b.score - a.score);
|
||||
|
||||
// normalize
|
||||
const max = result[0]?.score ?? 0;
|
||||
if (max > 0) {
|
||||
for (const score of result) {
|
||||
score.score /= max;
|
||||
}
|
||||
}
|
||||
|
||||
return result as TfIdfScore[];
|
||||
}
|
|
@ -222,3 +222,8 @@ export type OmitOptional<T> = { [K in keyof T as T[K] extends Required<T>[K] ? K
|
|||
export type Mutable<T> = {
|
||||
-readonly [P in keyof T]: T[P]
|
||||
};
|
||||
|
||||
/**
|
||||
* A single object or an array of the objects.
|
||||
*/
|
||||
export type SingleOrMany<T> = T | T[];
|
||||
|
|
|
@ -798,7 +798,7 @@ export class IPCServer<TContext = string> implements IChannelServer<TContext>, I
|
|||
private readonly _onDidRemoveConnection = new Emitter<Connection<TContext>>();
|
||||
readonly onDidRemoveConnection: Event<Connection<TContext>> = this._onDidRemoveConnection.event;
|
||||
|
||||
private disposables = new DisposableStore();
|
||||
private readonly disposables = new DisposableStore();
|
||||
|
||||
get connections(): Connection<TContext>[] {
|
||||
const result: Connection<TContext>[] = [];
|
||||
|
|
|
@ -113,7 +113,7 @@ interface ITestService {
|
|||
|
||||
class TestService implements ITestService {
|
||||
|
||||
private disposables = new DisposableStore();
|
||||
private readonly disposables = new DisposableStore();
|
||||
|
||||
private readonly _onPong = new Emitter<string>();
|
||||
readonly onPong = this._onPong.event;
|
||||
|
|
|
@ -102,7 +102,7 @@ const NullAccessor = new NullAccessorClass();
|
|||
suite('Fuzzy Scorer', () => {
|
||||
|
||||
test('score (fuzzy)', function () {
|
||||
const target = 'HeLlo-World';
|
||||
const target = 'HelLo-World';
|
||||
|
||||
const scores: FuzzyScore[] = [];
|
||||
scores.push(_doScore(target, 'HelLo-World', true)); // direct case match
|
||||
|
@ -134,7 +134,7 @@ suite('Fuzzy Scorer', () => {
|
|||
});
|
||||
|
||||
test('score (non fuzzy)', function () {
|
||||
const target = 'HeLlo-World';
|
||||
const target = 'HelLo-World';
|
||||
|
||||
assert.ok(_doScore(target, 'HelLo-World', false)[0] > 0);
|
||||
assert.strictEqual(_doScore(target, 'HelLo-World', false)[1].length, 'HelLo-World'.length);
|
||||
|
@ -1161,10 +1161,10 @@ suite('Fuzzy Scorer', () => {
|
|||
});
|
||||
|
||||
test('fuzzyScore2 (matching)', function () {
|
||||
const target = 'HeLlo-World';
|
||||
const target = 'HelLo-World';
|
||||
|
||||
for (const offset of [0, 3]) {
|
||||
let [score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HeLlo-World', offset);
|
||||
let [score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HelLo-World', offset);
|
||||
|
||||
assert.ok(score);
|
||||
assert.strictEqual(matches.length, 1);
|
||||
|
@ -1183,7 +1183,7 @@ suite('Fuzzy Scorer', () => {
|
|||
});
|
||||
|
||||
test('fuzzyScore2 (multiple queries)', function () {
|
||||
const target = 'HeLlo-World';
|
||||
const target = 'HelLo-World';
|
||||
|
||||
const [firstSingleScore, firstSingleMatches] = _doScore2(target, 'HelLo');
|
||||
const [secondSingleScore, secondSingleMatches] = _doScore2(target, 'World');
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { HistoryNavigator, HistoryNavigator2 } from 'vs/base/common/history';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
|
||||
|
||||
suite('History Navigator', () => {
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
test('create reduces the input to limit', () => {
|
||||
const testObject = new HistoryNavigator(['1', '2', '3', '4'], 2);
|
||||
|
||||
|
@ -269,4 +272,21 @@ suite('History Navigator 2', () => {
|
|||
assert.strictEqual(testObject.previous(), '1');
|
||||
});
|
||||
|
||||
test('prepend', () => {
|
||||
const testObject = new HistoryNavigator2(['1', '2', '3', '4']);
|
||||
assert.strictEqual(testObject.current(), '4');
|
||||
assert.ok(testObject.isAtEnd());
|
||||
assert.deepStrictEqual(Array.from(testObject), ['1', '2', '3', '4']);
|
||||
|
||||
testObject.prepend('0');
|
||||
assert.strictEqual(testObject.current(), '4');
|
||||
assert.ok(testObject.isAtEnd());
|
||||
assert.deepStrictEqual(Array.from(testObject), ['0', '1', '2', '3', '4']);
|
||||
|
||||
testObject.prepend('2');
|
||||
assert.strictEqual(testObject.current(), '4');
|
||||
assert.ok(testObject.isAtEnd());
|
||||
assert.deepStrictEqual(Array.from(testObject), ['0', '1', '2', '3', '4']);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
194
src/vs/base/test/common/tfIdf.test.ts
Normal file
194
src/vs/base/test/common/tfIdf.test.ts
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { TfIdfCalculator, TfIdfDocument, TfIdfScore } from 'vs/base/common/tfIdf';
|
||||
|
||||
/**
|
||||
* Generates all permutations of an array.
|
||||
*
|
||||
* This is useful for testing to make sure order does not effect the result.
|
||||
*/
|
||||
function permutate<T>(arr: T[]): T[][] {
|
||||
if (arr.length === 0) {
|
||||
return [[]];
|
||||
}
|
||||
|
||||
const result: T[][] = [];
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const rest = [...arr.slice(0, i), ...arr.slice(i + 1)];
|
||||
const permutationsRest = permutate(rest);
|
||||
for (let j = 0; j < permutationsRest.length; j++) {
|
||||
result.push([arr[i], ...permutationsRest[j]]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function assertScoreOrdersEqual(actualScores: TfIdfScore[], expectedScoreKeys: string[]): void {
|
||||
actualScores.sort((a, b) => (b.score - a.score) || a.key.localeCompare(b.key));
|
||||
assert.strictEqual(actualScores.length, expectedScoreKeys.length);
|
||||
for (let i = 0; i < expectedScoreKeys.length; i++) {
|
||||
assert.strictEqual(actualScores[i].key, expectedScoreKeys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
suite('TF-IDF Calculator', function () {
|
||||
test('Should return no scores when no documents are given', () => {
|
||||
const tfidf = new TfIdfCalculator();
|
||||
const scores = tfidf.calculateScores('something', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, []);
|
||||
});
|
||||
|
||||
test('Should return no scores for term not in document', () => {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments([
|
||||
makeDocument('A', 'cat dog fish'),
|
||||
]);
|
||||
const scores = tfidf.calculateScores('elepant', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, []);
|
||||
});
|
||||
|
||||
test('Should return scores for document with exact match', () => {
|
||||
for (const docs of permutate([
|
||||
makeDocument('A', 'cat dog cat'),
|
||||
makeDocument('B', 'cat fish'),
|
||||
])) {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments(docs);
|
||||
const scores = tfidf.calculateScores('dog', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['A']);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should return document with more matches first', () => {
|
||||
for (const docs of permutate([
|
||||
makeDocument('/A', 'cat dog cat'),
|
||||
makeDocument('/B', 'cat fish'),
|
||||
makeDocument('/C', 'frog'),
|
||||
])) {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments(docs);
|
||||
const scores = tfidf.calculateScores('cat', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/A', '/B']);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should return document with more matches first when term appears in all documents', () => {
|
||||
for (const docs of permutate([
|
||||
makeDocument('/A', 'cat dog cat cat'),
|
||||
makeDocument('/B', 'cat fish'),
|
||||
makeDocument('/C', 'frog cat cat'),
|
||||
])) {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments(docs);
|
||||
const scores = tfidf.calculateScores('cat', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/A', '/C', '/B']);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should weigh less common term higher', () => {
|
||||
for (const docs of permutate([
|
||||
makeDocument('/A', 'cat dog cat'),
|
||||
makeDocument('/B', 'fish'),
|
||||
makeDocument('/C', 'cat cat cat cat'),
|
||||
makeDocument('/D', 'cat fish')
|
||||
])) {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments(docs);
|
||||
const scores = tfidf.calculateScores('cat the dog', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/A', '/C', '/D']);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should weigh chunks with less common terms higher', () => {
|
||||
for (const docs of permutate([
|
||||
makeDocument('/A', ['cat dog cat', 'fish']),
|
||||
makeDocument('/B', ['cat cat cat cat dog', 'dog'])
|
||||
])) {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments(docs);
|
||||
const scores = tfidf.calculateScores('cat', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/B', '/A']);
|
||||
}
|
||||
|
||||
for (const docs of permutate([
|
||||
makeDocument('/A', ['cat dog cat', 'fish']),
|
||||
makeDocument('/B', ['cat cat cat cat dog', 'dog'])
|
||||
])) {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments(docs);
|
||||
const scores = tfidf.calculateScores('dog', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/A', '/B', '/B']);
|
||||
}
|
||||
|
||||
for (const docs of permutate([
|
||||
makeDocument('/A', ['cat dog cat', 'fish']),
|
||||
makeDocument('/B', ['cat cat cat cat dog', 'dog'])
|
||||
])) {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments(docs);
|
||||
const scores = tfidf.calculateScores('cat the dog', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/B', '/A', '/B']);
|
||||
}
|
||||
|
||||
for (const docs of permutate([
|
||||
makeDocument('/A', ['cat dog cat', 'fish']),
|
||||
makeDocument('/B', ['cat cat cat cat dog', 'dog'])
|
||||
])) {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments(docs);
|
||||
const scores = tfidf.calculateScores('lake fish', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/A']);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should ignore case and punctuation', () => {
|
||||
for (const docs of permutate([
|
||||
makeDocument('/A', 'Cat doG.cat'),
|
||||
makeDocument('/B', 'cAt fiSH'),
|
||||
makeDocument('/C', 'frOg'),
|
||||
])) {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments(docs);
|
||||
const scores = tfidf.calculateScores('. ,CaT! ', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/A', '/B']);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should match on camelCase words', () => {
|
||||
for (const docs of permutate([
|
||||
makeDocument('/A', 'catDog cat'),
|
||||
makeDocument('/B', 'fishCatFish'),
|
||||
makeDocument('/C', 'frogcat'),
|
||||
])) {
|
||||
const tfidf = new TfIdfCalculator().updateDocuments(docs);
|
||||
const scores = tfidf.calculateScores('catDOG', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/A', '/B']);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should not match document after delete', () => {
|
||||
const docA = makeDocument('/A', 'cat dog cat');
|
||||
const docB = makeDocument('/B', 'cat fish');
|
||||
const docC = makeDocument('/C', 'frog');
|
||||
|
||||
const tfidf = new TfIdfCalculator().updateDocuments([docA, docB, docC]);
|
||||
let scores = tfidf.calculateScores('cat', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/A', '/B']);
|
||||
|
||||
tfidf.deleteDocument(docA.key);
|
||||
scores = tfidf.calculateScores('cat', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/B']);
|
||||
|
||||
tfidf.deleteDocument(docC.key);
|
||||
scores = tfidf.calculateScores('cat', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, ['/B']);
|
||||
|
||||
tfidf.deleteDocument(docB.key);
|
||||
scores = tfidf.calculateScores('cat', CancellationToken.None);
|
||||
assertScoreOrdersEqual(scores, []);
|
||||
});
|
||||
});
|
||||
|
||||
function makeDocument(key: string, content: string | string[]): TfIdfDocument {
|
||||
return {
|
||||
key,
|
||||
textChunks: Array.isArray(content) ? content : [content],
|
||||
};
|
||||
}
|
|
@ -45,7 +45,7 @@ import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/commo
|
|||
import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService';
|
||||
import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter';
|
||||
import { ExtensionHostStarter } from 'vs/platform/extensions/electron-main/extensionHostStarter';
|
||||
import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal';
|
||||
import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/electron-main/externalTerminal';
|
||||
import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService';
|
||||
import { LOCAL_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/files/common/diskFileSystemProviderClient';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
|
|
|
@ -1052,10 +1052,11 @@ export class IssueReporter extends Disposable {
|
|||
}, extension.name);
|
||||
};
|
||||
|
||||
const extensionsSelector = this.getElementById('extension-selector');
|
||||
const extensionsSelector = this.getElementById<HTMLSelectElement>('extension-selector');
|
||||
if (extensionsSelector) {
|
||||
const { selectedExtension } = this.issueReporterModel.getData();
|
||||
reset(extensionsSelector, $<HTMLOptionElement>('option'), ...extensionOptions.map(extension => makeOption(extension, selectedExtension)));
|
||||
reset(extensionsSelector, this.makeOption('', localize('selectExtension', "Select extension"), true), ...extensionOptions.map(extension => makeOption(extension, selectedExtension)));
|
||||
extensionsSelector.selectedIndex = 0;
|
||||
|
||||
this.addEventListener('extension-selector', 'change', (e: Event) => {
|
||||
const selectedExtensionId = (<HTMLInputElement>e.target).value;
|
||||
|
|
|
@ -9,10 +9,12 @@ import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
|||
import { IVoiceRecognitionService } from 'vs/platform/voiceRecognition/node/voiceRecognitionService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { LimitedQueue } from 'vs/base/common/async';
|
||||
import { LimitedQueue, Queue } from 'vs/base/common/async';
|
||||
|
||||
export class VoiceTranscriptionManager extends Disposable {
|
||||
|
||||
private static USE_SLIDING_WINDOW = !!process.env.VSCODE_VOICE_USE_SLIDING_WINDOW;
|
||||
|
||||
constructor(
|
||||
private readonly onDidWindowConnectRaw: Event<MessagePortMain>,
|
||||
@IVoiceRecognitionService private readonly voiceRecognitionService: IVoiceRecognitionService,
|
||||
|
@ -25,23 +27,25 @@ export class VoiceTranscriptionManager extends Disposable {
|
|||
|
||||
private registerListeners(): void {
|
||||
this._register(this.onDidWindowConnectRaw(port => {
|
||||
this._register(new VoiceTranscriber(port, this.voiceRecognitionService, this.logService));
|
||||
this.logService.info(`[voice] transcriber: new connection (sliding window: ${VoiceTranscriptionManager.USE_SLIDING_WINDOW})`);
|
||||
|
||||
if (VoiceTranscriptionManager.USE_SLIDING_WINDOW) {
|
||||
this._register(new SlidingWindowVoiceTranscriber(port, this.voiceRecognitionService, this.logService));
|
||||
} else {
|
||||
this._register(new FullWindowVoiceTranscriber(port, this.voiceRecognitionService, this.logService));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class VoiceTranscriber extends Disposable {
|
||||
abstract class VoiceTranscriber extends Disposable {
|
||||
|
||||
private static MAX_DATA_LENGTH = 30 /* seconds */ * 16000 /* sampling rate */ * 16 /* bith depth */ * 1 /* channels */ / 8;
|
||||
|
||||
private readonly transcriptionQueue = new LimitedQueue();
|
||||
|
||||
private data: Float32Array | undefined = undefined;
|
||||
protected static MAX_DATA_LENGTH = 30 /* seconds */ * 16000 /* sampling rate */ * 16 /* bith depth */ * 1 /* channels */ / 8;
|
||||
|
||||
constructor(
|
||||
private readonly port: MessagePortMain,
|
||||
private readonly voiceRecognitionService: IVoiceRecognitionService,
|
||||
private readonly logService: ILogService
|
||||
protected readonly port: MessagePortMain,
|
||||
protected readonly voiceRecognitionService: IVoiceRecognitionService,
|
||||
protected readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -49,39 +53,115 @@ class VoiceTranscriber extends Disposable {
|
|||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.logService.info(`[voice] transcriber: new connection`);
|
||||
|
||||
const cts = new CancellationTokenSource();
|
||||
this._register(toDisposable(() => cts.dispose(true)));
|
||||
|
||||
const requestHandler = (e: MessageEvent) => this.handleRequest(e, cts.token);
|
||||
const requestHandler = (e: MessageEvent) => {
|
||||
if (!(e.data instanceof Float32Array)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleRequest(e.data, cts.token);
|
||||
};
|
||||
this.port.on('message', requestHandler);
|
||||
this._register(toDisposable(() => this.port.off('message', requestHandler)));
|
||||
|
||||
this.port.start();
|
||||
this._register(toDisposable(() => this.port.close()));
|
||||
|
||||
let closed = false;
|
||||
this.port.on('close', () => {
|
||||
this.logService.info(`[voice] transcriber: closed connection`);
|
||||
|
||||
cts.dispose(true);
|
||||
closed = true;
|
||||
this.dispose();
|
||||
});
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
if (!closed) {
|
||||
this.port.close();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private async handleRequest(e: MessageEvent, cancellation: CancellationToken): Promise<void> {
|
||||
if (!(Array.isArray(e.data))) {
|
||||
protected abstract handleRequest(data: Float32Array, cancellation: CancellationToken): Promise<void>;
|
||||
|
||||
protected joinFloat32Arrays(float32Arrays: Float32Array[]): Float32Array {
|
||||
const result = new Float32Array(float32Arrays.reduce((prev, curr) => prev + curr.length, 0));
|
||||
|
||||
let offset = 0;
|
||||
for (const float32Array of float32Arrays) {
|
||||
result.set(float32Array, offset);
|
||||
offset += float32Array.length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class SlidingWindowVoiceTranscriber extends VoiceTranscriber {
|
||||
|
||||
private readonly transcriptionQueue = this._register(new Queue());
|
||||
|
||||
private transcribedResults: string[] = [];
|
||||
private data: Float32Array = new Float32Array(0);
|
||||
|
||||
protected async handleRequest(data: Float32Array, cancellation: CancellationToken): Promise<void> {
|
||||
if (data.length > 0) {
|
||||
this.logService.info(`[voice] transcriber: voice detected, storing in buffer`);
|
||||
|
||||
this.data = this.data ? this.joinFloat32Arrays([this.data, data]) : data;
|
||||
} else {
|
||||
this.logService.info(`[voice] transcriber: silence detected, transcribing window...`);
|
||||
|
||||
const data = this.data.slice(0);
|
||||
this.data = new Float32Array(0);
|
||||
|
||||
this.transcriptionQueue.queue(() => this.transcribe(data, cancellation));
|
||||
}
|
||||
}
|
||||
|
||||
private async transcribe(data: Float32Array, cancellation: CancellationToken): Promise<void> {
|
||||
if (cancellation.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newData: Float32Array[] = [];
|
||||
for (const channelData of e.data) {
|
||||
if (channelData instanceof Float32Array) {
|
||||
newData.push(channelData);
|
||||
if (data.length > VoiceTranscriber.MAX_DATA_LENGTH) {
|
||||
this.logService.warn(`[voice] transcriber: refusing to accept more than 30s of audio data`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length !== 0) {
|
||||
const result = await this.voiceRecognitionService.transcribe(data, cancellation);
|
||||
if (result) {
|
||||
this.transcribedResults.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
const dataCandidate = this.joinFloat32Arrays(this.data ? [this.data, ...newData] : newData);
|
||||
if (cancellation.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.port.postMessage(this.transcribedResults.join(' '));
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.data = new Float32Array(0);
|
||||
}
|
||||
}
|
||||
|
||||
class FullWindowVoiceTranscriber extends VoiceTranscriber {
|
||||
|
||||
private readonly transcriptionQueue = new LimitedQueue();
|
||||
|
||||
private data: Float32Array | undefined = undefined;
|
||||
|
||||
private transcribedDataLength = 0;
|
||||
private transcribedResult = '';
|
||||
|
||||
protected async handleRequest(data: Float32Array, cancellation: CancellationToken): Promise<void> {
|
||||
const dataCandidate = this.data ? this.joinFloat32Arrays([this.data, data]) : data;
|
||||
if (dataCandidate.length > VoiceTranscriber.MAX_DATA_LENGTH) {
|
||||
this.logService.warn(`[voice] transcriber: refusing to accept more than 30s of audio data`);
|
||||
return;
|
||||
|
@ -102,7 +182,20 @@ class VoiceTranscriber extends Disposable {
|
|||
return;
|
||||
}
|
||||
|
||||
const result = await this.voiceRecognitionService.transcribe(data, cancellation);
|
||||
let result: string;
|
||||
if (data.length === this.transcribedDataLength) {
|
||||
// Optimization: if the data is the same as the last time
|
||||
// we transcribed, don't transcribe again, just return the
|
||||
// same result as we had last time.
|
||||
this.logService.info(`[voice] transcriber: silence detected, reusing previous transcription result`);
|
||||
result = this.transcribedResult;
|
||||
} else {
|
||||
this.logService.info(`[voice] transcriber: voice detected, transcribing everything...`);
|
||||
result = await this.voiceRecognitionService.transcribe(data, cancellation);
|
||||
}
|
||||
|
||||
this.transcribedResult = result;
|
||||
this.transcribedDataLength = data.length;
|
||||
|
||||
if (cancellation.isCancellationRequested) {
|
||||
return;
|
||||
|
@ -111,15 +204,9 @@ class VoiceTranscriber extends Disposable {
|
|||
this.port.postMessage(result);
|
||||
}
|
||||
|
||||
private joinFloat32Arrays(float32Arrays: Float32Array[]): Float32Array {
|
||||
const result = new Float32Array(float32Arrays.reduce((prev, curr) => prev + curr.length, 0));
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
let offset = 0;
|
||||
for (const float32Array of float32Arrays) {
|
||||
result.set(float32Array, offset);
|
||||
offset += float32Array.length;
|
||||
}
|
||||
|
||||
return result;
|
||||
this.data = undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ export class MouseHandler extends ViewEventHandler {
|
|||
// remove this listener
|
||||
|
||||
if (!this._mouseLeaveMonitor) {
|
||||
this._mouseLeaveMonitor = dom.addDisposableListener(document, 'mousemove', (e) => {
|
||||
this._mouseLeaveMonitor = dom.addDisposableListener(this.viewHelper.viewDomNode.ownerDocument, 'mousemove', (e) => {
|
||||
if (!this.viewHelper.viewDomNode.contains(e.target as Node | null)) {
|
||||
// went outside the editor!
|
||||
this._onMouseLeave(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode));
|
||||
|
|
|
@ -336,7 +336,7 @@ export class HitTestContext {
|
|||
}
|
||||
|
||||
private static _findAttribute(element: Element, attr: string, stopAt: Element): string | null {
|
||||
while (element && element !== document.body) {
|
||||
while (element && element !== element.ownerDocument.body) {
|
||||
if (element.hasAttribute && element.hasAttribute(attr)) {
|
||||
return element.getAttribute(attr);
|
||||
}
|
||||
|
@ -917,7 +917,7 @@ export class MouseTargetFactory {
|
|||
range = (<any>shadowRoot).caretRangeFromPoint(coords.clientX, coords.clientY);
|
||||
}
|
||||
} else {
|
||||
range = (<any>document).caretRangeFromPoint(coords.clientX, coords.clientY);
|
||||
range = (<any>ctx.viewDomNode.ownerDocument).caretRangeFromPoint(coords.clientX, coords.clientY);
|
||||
}
|
||||
|
||||
if (!range || !range.startContainer) {
|
||||
|
@ -959,7 +959,7 @@ export class MouseTargetFactory {
|
|||
* Most probably Gecko
|
||||
*/
|
||||
private static _doHitTestWithCaretPositionFromPoint(ctx: HitTestContext, coords: ClientCoordinates): HitTestResult {
|
||||
const hitResult: { offsetNode: Node; offset: number } = (<any>document).caretPositionFromPoint(coords.clientX, coords.clientY);
|
||||
const hitResult: { offsetNode: Node; offset: number } = (<any>ctx.viewDomNode.ownerDocument).caretPositionFromPoint(coords.clientX, coords.clientY);
|
||||
|
||||
if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) {
|
||||
// offsetNode is expected to be the token text
|
||||
|
@ -1011,9 +1011,9 @@ export class MouseTargetFactory {
|
|||
private static _doHitTest(ctx: HitTestContext, request: BareHitTestRequest): HitTestResult {
|
||||
|
||||
let result: HitTestResult = new UnknownHitTestResult();
|
||||
if (typeof (<any>document).caretRangeFromPoint === 'function') {
|
||||
if (typeof (<any>ctx.viewDomNode.ownerDocument).caretRangeFromPoint === 'function') {
|
||||
result = this._doHitTestWithCaretRangeFromPoint(ctx, request);
|
||||
} else if ((<any>document).caretPositionFromPoint) {
|
||||
} else if ((<any>ctx.viewDomNode.ownerDocument).caretPositionFromPoint) {
|
||||
result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates());
|
||||
}
|
||||
if (result.type === HitTestResultType.Content) {
|
||||
|
|
|
@ -391,7 +391,7 @@ export class TextAreaHandler extends ViewPart {
|
|||
const distanceToModelLineStart = startModelPosition.column - 1 - visibleBeforeCharCount;
|
||||
const hiddenLineTextBefore = lineTextBeforeSelection.substring(0, lineTextBeforeSelection.length - visibleBeforeCharCount);
|
||||
const { tabSize } = this._context.viewModel.model.getOptions();
|
||||
const widthOfHiddenTextBefore = measureText(hiddenLineTextBefore, this._fontInfo, tabSize);
|
||||
const widthOfHiddenTextBefore = measureText(this.textArea.domNode.ownerDocument, hiddenLineTextBefore, this._fontInfo, tabSize);
|
||||
|
||||
return { distanceToModelLineStart, widthOfHiddenTextBefore };
|
||||
})();
|
||||
|
@ -927,7 +927,7 @@ interface IRenderData {
|
|||
strikethrough?: boolean;
|
||||
}
|
||||
|
||||
function measureText(text: string, fontInfo: FontInfo, tabSize: number): number {
|
||||
function measureText(document: Document, text: string, fontInfo: FontInfo, tabSize: number): number {
|
||||
if (text.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -110,6 +110,8 @@ export interface ICompleteTextAreaWrapper extends ITextAreaWrapper {
|
|||
readonly onBlur: Event<FocusEvent>;
|
||||
readonly onSyntheticTap: Event<void>;
|
||||
|
||||
readonly ownerDocument: Document;
|
||||
|
||||
setIgnoreSelectionChangeTime(reason: string): void;
|
||||
getIgnoreSelectionChangeTime(): number;
|
||||
resetSelectionChangeTime(): void;
|
||||
|
@ -494,7 +496,7 @@ export class TextAreaInput extends Disposable {
|
|||
// `selectionchange` events often come multiple times for a single logical change
|
||||
// so throttle multiple `selectionchange` events that burst in a short period of time.
|
||||
let previousSelectionChangeEventTime = 0;
|
||||
return dom.addDisposableListener(document, 'selectionchange', (e) => {
|
||||
return dom.addDisposableListener(this._textArea.ownerDocument, 'selectionchange', (e) => {//todo
|
||||
inputLatency.onSelectionChange();
|
||||
|
||||
if (!this._hasFocus) {
|
||||
|
@ -701,6 +703,10 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap
|
|||
public readonly onFocus = this._register(new DomEmitter(this._actual, 'focus')).event;
|
||||
public readonly onBlur = this._register(new DomEmitter(this._actual, 'blur')).event;
|
||||
|
||||
public get ownerDocument(): Document {
|
||||
return this._actual.ownerDocument;
|
||||
}
|
||||
|
||||
private _onSyntheticTap = this._register(new Emitter<void>());
|
||||
public readonly onSyntheticTap: Event<void> = this._onSyntheticTap.event;
|
||||
|
||||
|
@ -725,7 +731,7 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap
|
|||
if (shadowRoot) {
|
||||
return shadowRoot.activeElement === this._actual;
|
||||
} else if (dom.isInDOM(this._actual)) {
|
||||
return document.activeElement === this._actual;
|
||||
return this._actual.ownerDocument.activeElement === this._actual;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -775,7 +781,7 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap
|
|||
if (shadowRoot) {
|
||||
activeElement = shadowRoot.activeElement;
|
||||
} else {
|
||||
activeElement = document.activeElement;
|
||||
activeElement = textArea.ownerDocument.activeElement;
|
||||
}
|
||||
|
||||
const currentIsFocused = (activeElement === textArea);
|
||||
|
|
|
@ -29,6 +29,7 @@ import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/co
|
|||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IViewModel } from 'vs/editor/common/viewModel';
|
||||
import { ISelection } from 'vs/editor/common/core/selection';
|
||||
import { getActiveElement } from 'vs/base/browser/dom';
|
||||
|
||||
const CORE_WEIGHT = KeybindingWeight.EditorCore;
|
||||
|
||||
|
@ -315,9 +316,9 @@ abstract class EditorOrNativeTextInputCommand {
|
|||
// 2. handle case when focus is in some other `input` / `textarea`.
|
||||
target.addImplementation(1000, 'generic-dom-input-textarea', (accessor: ServicesAccessor, args: unknown) => {
|
||||
// Only if focused on an element that allows for entering text
|
||||
const activeElement = <HTMLElement>document.activeElement;
|
||||
const activeElement = getActiveElement();
|
||||
if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) {
|
||||
this.runDOMCommand();
|
||||
this.runDOMCommand(activeElement);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -343,7 +344,7 @@ abstract class EditorOrNativeTextInputCommand {
|
|||
return true;
|
||||
}
|
||||
|
||||
public abstract runDOMCommand(): void;
|
||||
public abstract runDOMCommand(activeElement: Element): void;
|
||||
public abstract runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: unknown): void | Promise<void>;
|
||||
}
|
||||
|
||||
|
@ -1875,13 +1876,13 @@ export namespace CoreNavigationCommands {
|
|||
constructor() {
|
||||
super(SelectAllCommand);
|
||||
}
|
||||
public runDOMCommand(): void {
|
||||
public runDOMCommand(activeElement: Element): void {
|
||||
if (isFirefox) {
|
||||
(<HTMLInputElement>document.activeElement).focus();
|
||||
(<HTMLInputElement>document.activeElement).select();
|
||||
(<HTMLInputElement>activeElement).focus();
|
||||
(<HTMLInputElement>activeElement).select();
|
||||
}
|
||||
|
||||
document.execCommand('selectAll');
|
||||
activeElement.ownerDocument.execCommand('selectAll');
|
||||
}
|
||||
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void {
|
||||
const viewModel = editor._getViewModel();
|
||||
|
@ -2090,8 +2091,8 @@ export namespace CoreEditingCommands {
|
|||
constructor() {
|
||||
super(UndoCommand);
|
||||
}
|
||||
public runDOMCommand(): void {
|
||||
document.execCommand('undo');
|
||||
public runDOMCommand(activeElement: Element): void {
|
||||
activeElement.ownerDocument.execCommand('undo');
|
||||
}
|
||||
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: unknown): void | Promise<void> {
|
||||
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) {
|
||||
|
@ -2105,8 +2106,8 @@ export namespace CoreEditingCommands {
|
|||
constructor() {
|
||||
super(RedoCommand);
|
||||
}
|
||||
public runDOMCommand(): void {
|
||||
document.execCommand('redo');
|
||||
public runDOMCommand(activeElement: Element): void {
|
||||
activeElement.ownerDocument.execCommand('redo');
|
||||
}
|
||||
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: unknown): void | Promise<void> {
|
||||
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) {
|
||||
|
|
|
@ -241,7 +241,7 @@ export class GlobalEditorPointerMoveMonitor extends Disposable {
|
|||
|
||||
// Add a <<capture>> keydown event listener that will cancel the monitoring
|
||||
// if something other than a modifier key is pressed
|
||||
this._keydownListener = dom.addStandardDisposableListener(<any>document, 'keydown', (e) => {
|
||||
this._keydownListener = dom.addStandardDisposableListener(<any>initialElement.ownerDocument, 'keydown', (e) => {
|
||||
const chord = e.toKeyCodeChord();
|
||||
if (chord.isModifierKey()) {
|
||||
// Allow modifier keys
|
||||
|
|
|
@ -24,6 +24,7 @@ import { ThemeIcon } from 'vs/base/common/themables';
|
|||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { getActiveElement } from 'vs/base/browser/dom';
|
||||
|
||||
export type ServicesAccessor = InstantiationServicesAccessor;
|
||||
export type EditorContributionCtor = IConstructorSignature<IEditorContribution, [ICodeEditor]>;
|
||||
|
@ -219,7 +220,7 @@ export class MultiCommand extends Command {
|
|||
logService.trace(`Executing Command '${this.id}' which has ${this._implementations.length} bound.`);
|
||||
for (const impl of this._implementations) {
|
||||
if (impl.when) {
|
||||
const context = contextKeyService.getContext(document.activeElement);
|
||||
const context = contextKeyService.getContext(getActiveElement());
|
||||
const value = impl.when.evaluate(context);
|
||||
if (!value) {
|
||||
continue;
|
||||
|
|
|
@ -57,7 +57,7 @@ export class PartFingerprints {
|
|||
const result: PartFingerprint[] = [];
|
||||
let resultLen = 0;
|
||||
|
||||
while (child && child !== document.body) {
|
||||
while (child && child !== child.ownerDocument.body) {
|
||||
if (child === stopAt) {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -266,9 +266,11 @@ class Widget {
|
|||
}
|
||||
|
||||
private _getMaxWidth(): number {
|
||||
const elDocument = this.domNode.domNode.ownerDocument;
|
||||
const elWindow = elDocument.defaultView;
|
||||
return (
|
||||
this.allowEditorOverflow
|
||||
? window.innerWidth || document.documentElement!.offsetWidth || document.body.offsetWidth
|
||||
? elWindow?.innerWidth || elDocument.documentElement.offsetWidth || elDocument.body.offsetWidth
|
||||
: this._contentWidth
|
||||
);
|
||||
}
|
||||
|
@ -326,7 +328,9 @@ class Widget {
|
|||
const MIN_LIMIT = Math.max(LEFT_PADDING, domNodePosition.left - width);
|
||||
const MAX_LIMIT = Math.min(domNodePosition.left + domNodePosition.width + width, windowSize.width - RIGHT_PADDING);
|
||||
|
||||
let absoluteLeft = domNodePosition.left + left - window.scrollX;
|
||||
const elDocument = this._viewDomNode.domNode.ownerDocument;
|
||||
const elWindow = elDocument.defaultView;
|
||||
let absoluteLeft = domNodePosition.left + left - (elWindow?.scrollX ?? 0);
|
||||
|
||||
if (absoluteLeft + width > MAX_LIMIT) {
|
||||
const delta = absoluteLeft - (MAX_LIMIT - width);
|
||||
|
@ -348,10 +352,12 @@ class Widget {
|
|||
const belowTop = anchor.top + anchor.height;
|
||||
|
||||
const domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode);
|
||||
const absoluteAboveTop = domNodePosition.top + aboveTop - window.scrollY;
|
||||
const absoluteBelowTop = domNodePosition.top + belowTop - window.scrollY;
|
||||
const elDocument = this._viewDomNode.domNode.ownerDocument;
|
||||
const elWindow = elDocument.defaultView;
|
||||
const absoluteAboveTop = domNodePosition.top + aboveTop - (elWindow?.scrollY ?? 0);
|
||||
const absoluteBelowTop = domNodePosition.top + belowTop - (elWindow?.scrollY ?? 0);
|
||||
|
||||
const windowSize = dom.getClientArea(document.body);
|
||||
const windowSize = dom.getClientArea(elDocument.body);
|
||||
const [left, absoluteAboveLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, anchor.left - ctx.scrollLeft + this._contentLeft, width);
|
||||
|
||||
// Leave some clearance to the top/bottom
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getActiveElement } from 'vs/base/browser/dom';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
|
@ -290,10 +291,11 @@ export function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor |
|
|||
}
|
||||
}
|
||||
|
||||
if (document.activeElement) {
|
||||
const activeElement = getActiveElement();
|
||||
if (activeElement) {
|
||||
for (const d of diffEditors) {
|
||||
const container = d.getContainerDomNode();
|
||||
if (isElementOrParentOf(container, document.activeElement)) {
|
||||
if (isElementOrParentOf(container, activeElement)) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,12 @@ export class OffsetRange {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
public intersectsOrTouches(other: OffsetRange): boolean {
|
||||
const start = Math.max(this.start, other.start);
|
||||
const end = Math.min(this.endExclusive, other.endExclusive);
|
||||
return start <= end;
|
||||
}
|
||||
|
||||
public slice<T>(arr: T[]): T[] {
|
||||
return arr.slice(this.start, this.endExclusive);
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@ export interface IDiffAlgorithm {
|
|||
|
||||
export class DiffAlgorithmResult {
|
||||
static trivial(seq1: ISequence, seq2: ISequence): DiffAlgorithmResult {
|
||||
return new DiffAlgorithmResult([new SequenceDiff(new OffsetRange(0, seq1.length), new OffsetRange(0, seq2.length))], false);
|
||||
return new DiffAlgorithmResult([new SequenceDiff(OffsetRange.ofLength(seq1.length), OffsetRange.ofLength(seq2.length))], false);
|
||||
}
|
||||
|
||||
static trivialTimedOut(seq1: ISequence, seq2: ISequence): DiffAlgorithmResult {
|
||||
return new DiffAlgorithmResult([new SequenceDiff(new OffsetRange(0, seq1.length), new OffsetRange(0, seq2.length))], true);
|
||||
return new DiffAlgorithmResult([new SequenceDiff(OffsetRange.ofLength(seq1.length), OffsetRange.ofLength(seq2.length))], true);
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
@ -36,21 +36,22 @@ export class DiffAlgorithmResult {
|
|||
export class SequenceDiff {
|
||||
public static invert(sequenceDiffs: SequenceDiff[], doc1Length: number): SequenceDiff[] {
|
||||
const result: SequenceDiff[] = [];
|
||||
|
||||
forEachAdjacent(sequenceDiffs, (a, b) => {
|
||||
const seq1Start = a ? a.seq1Range.endExclusive : 0;
|
||||
const seq2Start = a ? a.seq2Range.endExclusive : 0;
|
||||
const seq1EndEx = b ? b.seq1Range.start : doc1Length;
|
||||
const seq2EndEx = b ? b.seq2Range.start : (a ? a.seq2Range.endExclusive - a.seq1Range.endExclusive : 0) + doc1Length;
|
||||
result.push(new SequenceDiff(
|
||||
new OffsetRange(seq1Start, seq1EndEx),
|
||||
new OffsetRange(seq2Start, seq2EndEx),
|
||||
result.push(SequenceDiff.fromOffsetPairs(
|
||||
a ? a.getEndExclusives() : OffsetPair.zero,
|
||||
b ? b.getStarts() : new OffsetPair(doc1Length, (a ? a.seq2Range.endExclusive - a.seq1Range.endExclusive : 0) + doc1Length)
|
||||
));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static fromOffsetPairs(start: OffsetPair, endExclusive: OffsetPair): SequenceDiff {
|
||||
return new SequenceDiff(
|
||||
new OffsetRange(start.offset1, endExclusive.offset1),
|
||||
new OffsetRange(start.offset2, endExclusive.offset2),
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly seq1Range: OffsetRange,
|
||||
public readonly seq2Range: OffsetRange,
|
||||
|
@ -74,6 +75,56 @@ export class SequenceDiff {
|
|||
}
|
||||
return new SequenceDiff(this.seq1Range.delta(offset), this.seq2Range.delta(offset));
|
||||
}
|
||||
|
||||
public deltaStart(offset: number): SequenceDiff {
|
||||
if (offset === 0) {
|
||||
return this;
|
||||
}
|
||||
return new SequenceDiff(this.seq1Range.deltaStart(offset), this.seq2Range.deltaStart(offset));
|
||||
}
|
||||
|
||||
public deltaEnd(offset: number): SequenceDiff {
|
||||
if (offset === 0) {
|
||||
return this;
|
||||
}
|
||||
return new SequenceDiff(this.seq1Range.deltaEnd(offset), this.seq2Range.deltaEnd(offset));
|
||||
}
|
||||
|
||||
public intersectsOrTouches(other: SequenceDiff): boolean {
|
||||
return this.seq1Range.intersectsOrTouches(other.seq1Range) || this.seq2Range.intersectsOrTouches(other.seq2Range);
|
||||
}
|
||||
|
||||
public intersect(other: SequenceDiff): SequenceDiff | undefined {
|
||||
const i1 = this.seq1Range.intersect(other.seq1Range);
|
||||
const i2 = this.seq2Range.intersect(other.seq2Range);
|
||||
if (!i1 || !i2) {
|
||||
return undefined;
|
||||
}
|
||||
return new SequenceDiff(i1, i2);
|
||||
}
|
||||
|
||||
public getStarts(): OffsetPair {
|
||||
return new OffsetPair(this.seq1Range.start, this.seq2Range.start);
|
||||
}
|
||||
|
||||
public getEndExclusives(): OffsetPair {
|
||||
return new OffsetPair(this.seq1Range.endExclusive, this.seq2Range.endExclusive);
|
||||
}
|
||||
}
|
||||
|
||||
export class OffsetPair {
|
||||
public static readonly zero = new OffsetPair(0, 0);
|
||||
public static readonly max = new OffsetPair(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
|
||||
|
||||
constructor(
|
||||
public readonly offset1: number,
|
||||
public readonly offset2: number,
|
||||
) {
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `${this.offset1} <-> ${this.offset2}`;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ISequence {
|
||||
|
|
|
@ -196,6 +196,9 @@ function computeUnchangedMoves(
|
|||
for (extendToTop = 0; extendToTop < linesAbove; extendToTop++) {
|
||||
const origLine = move.original.startLineNumber - extendToTop - 1;
|
||||
const modLine = move.modified.startLineNumber - extendToTop - 1;
|
||||
if (origLine > originalLines.length || modLine > modifiedLines.length) {
|
||||
break;
|
||||
}
|
||||
if (modifiedSet.contains(modLine) || originalSet.contains(origLine)) {
|
||||
break;
|
||||
}
|
||||
|
@ -213,6 +216,9 @@ function computeUnchangedMoves(
|
|||
for (extendToBottom = 0; extendToBottom < linesBelow; extendToBottom++) {
|
||||
const origLine = move.original.endLineNumberExclusive + extendToBottom;
|
||||
const modLine = move.modified.endLineNumberExclusive + extendToBottom;
|
||||
if (origLine > originalLines.length || modLine > modifiedLines.length) {
|
||||
break;
|
||||
}
|
||||
if (modifiedSet.contains(modLine) || originalSet.contains(origLine)) {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { forEachWithNeighbors } from 'vs/base/common/arrays';
|
||||
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||
import { ISequence, SequenceDiff } from 'vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm';
|
||||
import { ISequence, OffsetPair, SequenceDiff } from 'vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm';
|
||||
import { LineSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/lineSequence';
|
||||
import { LinesSliceCharSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence';
|
||||
|
||||
|
@ -425,28 +426,33 @@ export function removeVeryShortMatchingTextBetweenLongDiffs(sequence1: LinesSlic
|
|||
diffs = result;
|
||||
} while (counter++ < 10 && shouldRepeat);
|
||||
|
||||
// Remove short suffixes/prefixes
|
||||
for (let i = 0; i < diffs.length; i++) {
|
||||
const cur = diffs[i];
|
||||
const newDiffs: SequenceDiff[] = [];
|
||||
|
||||
let range1 = cur.seq1Range;
|
||||
let range2 = cur.seq2Range;
|
||||
// Remove short suffixes/prefixes
|
||||
forEachWithNeighbors(diffs, (prev, cur, next) => {
|
||||
let newDiff = cur;
|
||||
|
||||
function shouldMarkAsChanged(text: string): boolean {
|
||||
return text.length > 0 && text.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 100;
|
||||
}
|
||||
|
||||
const fullRange1 = sequence1.extendToFullLines(cur.seq1Range);
|
||||
const prefix = sequence1.getText(new OffsetRange(fullRange1.start, cur.seq1Range.start));
|
||||
if (prefix.length > 0 && prefix.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 100) {
|
||||
range1 = cur.seq1Range.deltaStart(-prefix.length);
|
||||
range2 = cur.seq2Range.deltaStart(-prefix.length);
|
||||
if (shouldMarkAsChanged(prefix)) {
|
||||
newDiff = newDiff.deltaStart(-prefix.length);
|
||||
}
|
||||
|
||||
const suffix = sequence1.getText(new OffsetRange(cur.seq1Range.endExclusive, fullRange1.endExclusive));
|
||||
if (suffix.length > 0 && (suffix.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 150)) {
|
||||
range1 = range1.deltaEnd(suffix.length);
|
||||
range2 = range2.deltaEnd(suffix.length);
|
||||
if (shouldMarkAsChanged(suffix)) {
|
||||
newDiff = newDiff.deltaEnd(suffix.length);
|
||||
}
|
||||
|
||||
diffs[i] = new SequenceDiff(range1, range2);
|
||||
}
|
||||
const availableSpace = SequenceDiff.fromOffsetPairs(
|
||||
prev ? prev.getEndExclusives() : OffsetPair.zero,
|
||||
next ? next.getStarts() : OffsetPair.max,
|
||||
);
|
||||
const result = newDiff.intersect(availableSpace)!;
|
||||
newDiffs.push(result);
|
||||
});
|
||||
|
||||
return diffs;
|
||||
return newDiffs;
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ export class LinesSliceCharSequence implements ISequence {
|
|||
let score = 0;
|
||||
if (prevCategory !== nextCategory) {
|
||||
score += 10;
|
||||
if (nextCategory === CharBoundaryCategory.WordUpper) {
|
||||
if (prevCategory === CharBoundaryCategory.WordLower && nextCategory === CharBoundaryCategory.WordUpper) {
|
||||
score += 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -840,6 +840,13 @@ export interface ITextModel {
|
|||
*/
|
||||
isTooLargeForTokenization(): boolean;
|
||||
|
||||
/**
|
||||
* The file is so large, that operations on it might be too large for heap
|
||||
* and can lead to OOM crashes so they should be disabled.
|
||||
* @internal
|
||||
*/
|
||||
isTooLargeForHeapOperation(): boolean;
|
||||
|
||||
/**
|
||||
* Search the model.
|
||||
* @param searchString The string used to search. If it is a regular expression, set `isRegex` to true.
|
||||
|
|
|
@ -177,6 +177,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
static _MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB, // used in tests
|
||||
private static readonly LARGE_FILE_SIZE_THRESHOLD = 20 * 1024 * 1024; // 20 MB;
|
||||
private static readonly LARGE_FILE_LINE_COUNT_THRESHOLD = 300 * 1000; // 300K lines
|
||||
private static readonly LARGE_FILE_HEAP_OPERATION_THRESHOLD = 256 * 1024 * 1024; // 256M characters, usually ~> 512MB memory usage
|
||||
|
||||
public static DEFAULT_CREATION_OPTIONS: model.ITextModelCreationOptions = {
|
||||
isForSimpleWidget: false,
|
||||
|
@ -257,6 +258,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
private _initialUndoRedoSnapshot: ResourceEditStackSnapshot | null;
|
||||
private readonly _isTooLargeForSyncing: boolean;
|
||||
private readonly _isTooLargeForTokenization: boolean;
|
||||
private readonly _isTooLargeForHeapOperation: boolean;
|
||||
|
||||
//#region Editing
|
||||
private readonly _commandManager: EditStack;
|
||||
|
@ -345,8 +347,11 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
(bufferTextLength > TextModel.LARGE_FILE_SIZE_THRESHOLD)
|
||||
|| (bufferLineCount > TextModel.LARGE_FILE_LINE_COUNT_THRESHOLD)
|
||||
);
|
||||
|
||||
this._isTooLargeForHeapOperation = bufferTextLength > TextModel.LARGE_FILE_HEAP_OPERATION_THRESHOLD;
|
||||
} else {
|
||||
this._isTooLargeForTokenization = false;
|
||||
this._isTooLargeForHeapOperation = false;
|
||||
}
|
||||
|
||||
this._isTooLargeForSyncing = (bufferTextLength > TextModel._MODEL_SYNC_LIMIT);
|
||||
|
@ -587,6 +592,10 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
return this._isTooLargeForTokenization;
|
||||
}
|
||||
|
||||
public isTooLargeForHeapOperation(): boolean {
|
||||
return this._isTooLargeForHeapOperation;
|
||||
}
|
||||
|
||||
public isDisposed(): boolean {
|
||||
return this._isDisposed;
|
||||
}
|
||||
|
@ -743,6 +752,10 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
|
||||
public getValue(eol?: model.EndOfLinePreference, preserveBOM: boolean = false): string {
|
||||
this._assertNotDisposed();
|
||||
if (this.isTooLargeForHeapOperation()) {
|
||||
throw new BugIndicatingError('Operation would exceed heap memory limits');
|
||||
}
|
||||
|
||||
const fullModelRange = this.getFullModelRange();
|
||||
const fullModelValue = this.getValueInRange(fullModelRange, eol);
|
||||
|
||||
|
@ -809,6 +822,10 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
|
||||
public getLinesContent(): string[] {
|
||||
this._assertNotDisposed();
|
||||
if (this.isTooLargeForHeapOperation()) {
|
||||
throw new BugIndicatingError('Operation would exceed heap memory limits');
|
||||
}
|
||||
|
||||
return this._buffer.getLinesContent();
|
||||
}
|
||||
|
||||
|
|
|
@ -574,7 +574,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
|
|||
return result;
|
||||
}
|
||||
|
||||
public async computeHumanReadableDiff(modelUrl: string, edits: TextEdit[], options: ILinesDiffComputerOptions): Promise<TextEdit[]> {
|
||||
public computeHumanReadableDiff(modelUrl: string, edits: TextEdit[], options: ILinesDiffComputerOptions): TextEdit[] {
|
||||
const model = this._getModel(modelUrl);
|
||||
if (!model) {
|
||||
return edits;
|
||||
|
|
|
@ -572,28 +572,28 @@ export enum KeyCode {
|
|||
* Either the angle bracket key or the backslash key on the RT 102-key keyboard.
|
||||
*/
|
||||
IntlBackslash = 97,
|
||||
Numpad0 = 98,
|
||||
Numpad1 = 99,
|
||||
Numpad2 = 100,
|
||||
Numpad3 = 101,
|
||||
Numpad4 = 102,
|
||||
Numpad5 = 103,
|
||||
Numpad6 = 104,
|
||||
Numpad7 = 105,
|
||||
Numpad8 = 106,
|
||||
Numpad9 = 107,
|
||||
NumpadMultiply = 108,
|
||||
NumpadAdd = 109,
|
||||
NUMPAD_SEPARATOR = 110,
|
||||
NumpadSubtract = 111,
|
||||
NumpadDecimal = 112,
|
||||
NumpadDivide = 113,
|
||||
Numpad0 = 98,// VK_NUMPAD0, 0x60, Numeric keypad 0 key
|
||||
Numpad1 = 99,// VK_NUMPAD1, 0x61, Numeric keypad 1 key
|
||||
Numpad2 = 100,// VK_NUMPAD2, 0x62, Numeric keypad 2 key
|
||||
Numpad3 = 101,// VK_NUMPAD3, 0x63, Numeric keypad 3 key
|
||||
Numpad4 = 102,// VK_NUMPAD4, 0x64, Numeric keypad 4 key
|
||||
Numpad5 = 103,// VK_NUMPAD5, 0x65, Numeric keypad 5 key
|
||||
Numpad6 = 104,// VK_NUMPAD6, 0x66, Numeric keypad 6 key
|
||||
Numpad7 = 105,// VK_NUMPAD7, 0x67, Numeric keypad 7 key
|
||||
Numpad8 = 106,// VK_NUMPAD8, 0x68, Numeric keypad 8 key
|
||||
Numpad9 = 107,// VK_NUMPAD9, 0x69, Numeric keypad 9 key
|
||||
NumpadMultiply = 108,// VK_MULTIPLY, 0x6A, Multiply key
|
||||
NumpadAdd = 109,// VK_ADD, 0x6B, Add key
|
||||
NUMPAD_SEPARATOR = 110,// VK_SEPARATOR, 0x6C, Separator key
|
||||
NumpadSubtract = 111,// VK_SUBTRACT, 0x6D, Subtract key
|
||||
NumpadDecimal = 112,// VK_DECIMAL, 0x6E, Decimal key
|
||||
NumpadDivide = 113,// VK_DIVIDE, 0x6F,
|
||||
/**
|
||||
* Cover all key codes when IME is processing input.
|
||||
*/
|
||||
KEY_IN_COMPOSITION = 114,
|
||||
ABNT_C1 = 115,
|
||||
ABNT_C2 = 116,
|
||||
ABNT_C1 = 115,// Brazilian (ABNT) Keyboard
|
||||
ABNT_C2 = 116,// Brazilian (ABNT) Keyboard
|
||||
AudioVolumeMute = 117,
|
||||
AudioVolumeUp = 118,
|
||||
AudioVolumeDown = 119,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { getActiveDocument } from 'vs/base/browser/dom';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { CopyOptions, InMemoryClipboardMetadataManager } from 'vs/editor/browser/controller/textAreaInput';
|
||||
|
@ -179,7 +180,7 @@ class ExecCommandCopyWithSyntaxHighlightingAction extends EditorAction {
|
|||
|
||||
CopyOptions.forceCopyWithSyntaxHighlighting = true;
|
||||
editor.focus();
|
||||
document.execCommand('copy');
|
||||
editor.getContainerDomNode().ownerDocument.execCommand('copy');
|
||||
CopyOptions.forceCopyWithSyntaxHighlighting = false;
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +201,7 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman
|
|||
if (selection && selection.isEmpty() && !emptySelectionClipboard) {
|
||||
return true;
|
||||
}
|
||||
document.execCommand(browserCommand);
|
||||
focusedEditor.getContainerDomNode().ownerDocument.execCommand(browserCommand);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -208,7 +209,7 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman
|
|||
|
||||
// 2. (default) handle case when focus is somewhere else.
|
||||
target.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: any) => {
|
||||
document.execCommand(browserCommand);
|
||||
getActiveDocument().execCommand(browserCommand);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
@ -225,7 +226,7 @@ if (PasteAction) {
|
|||
// Only if editor text focus (i.e. not if editor has widget focus).
|
||||
const focusedEditor = codeEditorService.getFocusedCodeEditor();
|
||||
if (focusedEditor && focusedEditor.hasTextFocus()) {
|
||||
const result = document.execCommand('paste');
|
||||
const result = focusedEditor.getContainerDomNode().ownerDocument.execCommand('paste');
|
||||
// Use the clipboard service if document.execCommand('paste') was not successful
|
||||
if (!result && platform.isWeb) {
|
||||
return (async () => {
|
||||
|
@ -256,7 +257,7 @@ if (PasteAction) {
|
|||
|
||||
// 2. Paste: (default) handle case when focus is somewhere else.
|
||||
PasteAction.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: any) => {
|
||||
document.execCommand('paste');
|
||||
getActiveDocument().execCommand('paste');
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { coalesce, equals, isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { illegalArgument, isCancellationError, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
|
@ -18,7 +19,6 @@ import { ITextModel } from 'vs/editor/common/model';
|
|||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { IModelService } from 'vs/editor/common/services/model';
|
||||
import { TextModelCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState';
|
||||
import * as nls from 'vs/nls';
|
||||
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
@ -89,6 +89,10 @@ export async function getCodeActions(
|
|||
token: CancellationToken,
|
||||
): Promise<CodeActionSet> {
|
||||
const filter = trigger.filter || {};
|
||||
const notebookFilter: CodeActionFilter = {
|
||||
...filter,
|
||||
excludes: [...(filter.excludes || []), CodeActionKind.Notebook],
|
||||
};
|
||||
|
||||
const codeActionContext: languages.CodeActionContext = {
|
||||
only: filter.include?.value,
|
||||
|
@ -96,7 +100,9 @@ export async function getCodeActions(
|
|||
};
|
||||
|
||||
const cts = new TextModelCancellationTokenSource(model, token);
|
||||
const providers = getCodeActionProviders(registry, model, filter);
|
||||
// if the trigger is auto (autosave, lightbulb, etc), we should exclude notebook codeActions
|
||||
const excludeNotebookCodeActions = (trigger.type === languages.CodeActionTriggerType.Auto);
|
||||
const providers = getCodeActionProviders(registry, model, (excludeNotebookCodeActions) ? notebookFilter : filter);
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
const promises = providers.map(async provider => {
|
||||
|
|
|
@ -30,6 +30,7 @@ import { IMarkerService } from 'vs/platform/markers/common/markers';
|
|||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { CodeActionAutoApply, CodeActionFilter, CodeActionItem, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types';
|
||||
import { CodeActionModel, CodeActionsState } from './codeActionModel';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
|
||||
interface IActionShowOptions {
|
||||
|
@ -251,6 +252,13 @@ export class CodeActionController extends Disposable implements IEditorContribut
|
|||
},
|
||||
onHide: () => {
|
||||
this._editor?.focus();
|
||||
},
|
||||
onFocus: async (action: CodeActionItem, token: CancellationToken) => {
|
||||
await action.resolve(token);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
return { canPreview: !!action.action.edit?.edits.length };
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -44,7 +44,8 @@ export function toMenuItems(
|
|||
item: action,
|
||||
group: uncategorizedCodeActionGroup,
|
||||
disabled: !!action.action.disabled,
|
||||
label: action.action.disabled || action.action.title
|
||||
label: action.action.disabled || action.action.title,
|
||||
canPreview: !!action.action.edit?.edits.length,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ export class CodeActionKind {
|
|||
public static readonly RefactorInline = CodeActionKind.Refactor.append('inline');
|
||||
public static readonly RefactorMove = CodeActionKind.Refactor.append('move');
|
||||
public static readonly RefactorRewrite = CodeActionKind.Refactor.append('rewrite');
|
||||
public static readonly Notebook = new CodeActionKind('notebook');
|
||||
public static readonly Source = new CodeActionKind('source');
|
||||
public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports');
|
||||
public static readonly SourceFixAll = CodeActionKind.Source.append('fixAll');
|
||||
|
|
|
@ -39,7 +39,7 @@ export class CodeLensContribution implements IEditorContribution {
|
|||
private readonly _resolveCodeLensesScheduler: RunOnceScheduler;
|
||||
|
||||
private _getCodeLensModelPromise: CancelablePromise<CodeLensModel> | undefined;
|
||||
private _oldCodeLensModels = new DisposableStore();
|
||||
private readonly _oldCodeLensModels = new DisposableStore();
|
||||
private _currentCodeLensModel: CodeLensModel | undefined;
|
||||
private _resolveCodeLensesPromise: CancelablePromise<any> | undefined;
|
||||
|
||||
|
|
|
@ -264,7 +264,7 @@ class SaturationBox extends Disposable {
|
|||
|
||||
this.monitor.startMonitoring(e.target, e.pointerId, e.buttons, event => this.onDidChangePosition(event.pageX - origin.left, event.pageY - origin.top), () => null);
|
||||
|
||||
const pointerUpListener = dom.addDisposableListener(document, dom.EventType.POINTER_UP, () => {
|
||||
const pointerUpListener = dom.addDisposableListener(e.target.ownerDocument, dom.EventType.POINTER_UP, () => {
|
||||
this._onColorFlushed.fire();
|
||||
pointerUpListener.dispose();
|
||||
if (this.monitor) {
|
||||
|
@ -387,7 +387,7 @@ abstract class Strip extends Disposable {
|
|||
|
||||
monitor.startMonitoring(e.target, e.pointerId, e.buttons, event => this.onDidChangeTop(event.pageY - origin.top), () => null);
|
||||
|
||||
const pointerUpListener = dom.addDisposableListener(document, dom.EventType.POINTER_UP, () => {
|
||||
const pointerUpListener = dom.addDisposableListener(e.target.ownerDocument, dom.EventType.POINTER_UP, () => {
|
||||
this._onColorFlushed.fire();
|
||||
pointerUpListener.dispose();
|
||||
monitor.stopMonitoring(true);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { addDisposableListener, getActiveDocument } from 'vs/base/browser/dom';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
@ -99,7 +99,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
this._editor.focus();
|
||||
try {
|
||||
this._pasteAsActionContext = { preferredId };
|
||||
document.execCommand('paste');
|
||||
getActiveDocument().execCommand('paste');
|
||||
} finally {
|
||||
this._pasteAsActionContext = undefined;
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
|||
protected readonly _storageService: IStorageService;
|
||||
private readonly _clipboardService: IClipboardService;
|
||||
protected readonly _contextKeyService: IContextKeyService;
|
||||
protected readonly _notificationService: INotificationService;
|
||||
|
||||
get editor() {
|
||||
return this._editor;
|
||||
|
@ -110,7 +111,8 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
|||
editor: ICodeEditor,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IClipboardService clipboardService: IClipboardService
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@INotificationService notificationService: INotificationService
|
||||
) {
|
||||
super();
|
||||
this._editor = editor;
|
||||
|
@ -118,6 +120,7 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
|||
this._contextKeyService = contextKeyService;
|
||||
this._storageService = storageService;
|
||||
this._clipboardService = clipboardService;
|
||||
this._notificationService = notificationService;
|
||||
|
||||
this._updateHistoryDelayer = new Delayer<void>(500);
|
||||
this._state = this._register(new FindReplaceState());
|
||||
|
@ -390,6 +393,10 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
|||
|
||||
public replaceAll(): boolean {
|
||||
if (this._model) {
|
||||
if (this._editor.getModel()?.isTooLargeForHeapOperation()) {
|
||||
this._notificationService.warn(nls.localize('too.large.for.replaceall', "The file is too large to perform a replace all operation."));
|
||||
return false;
|
||||
}
|
||||
this._model.replaceAll();
|
||||
return true;
|
||||
}
|
||||
|
@ -437,11 +444,11 @@ export class FindController extends CommonFindController implements IFindControl
|
|||
@IContextKeyService _contextKeyService: IContextKeyService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IStorageService _storageService: IStorageService,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
) {
|
||||
super(editor, _contextKeyService, _storageService, clipboardService);
|
||||
super(editor, _contextKeyService, _storageService, clipboardService, notificationService);
|
||||
this._widget = null;
|
||||
this._findOptionsWidget = null;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
|
|||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageService, InMemoryStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
|
||||
class TestFindController extends CommonFindController {
|
||||
|
@ -33,9 +34,10 @@ class TestFindController extends CommonFindController {
|
|||
editor: ICodeEditor,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IClipboardService clipboardService: IClipboardService
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@INotificationService notificationService: INotificationService
|
||||
) {
|
||||
super(editor, contextKeyService, storageService, clipboardService);
|
||||
super(editor, contextKeyService, storageService, clipboardService, notificationService);
|
||||
this._findInputFocused = CONTEXT_FIND_INPUT_FOCUSED.bindTo(contextKeyService);
|
||||
this._updateHistoryDelayer = new Delayer<void>(50);
|
||||
this.hasFocus = false;
|
||||
|
|
|
@ -675,7 +675,7 @@ export class ContentHoverWidget extends ResizableContentWidget {
|
|||
);
|
||||
|
||||
if (overflowing || this._hover.containerDomNode.clientWidth < initialWidth) {
|
||||
const bodyBoxWidth = dom.getClientArea(document.body).width;
|
||||
const bodyBoxWidth = dom.getClientArea(this._hover.containerDomNode.ownerDocument.body).width;
|
||||
const horizontalPadding = 14;
|
||||
return bodyBoxWidth - horizontalPadding;
|
||||
} else {
|
||||
|
|
|
@ -78,7 +78,7 @@ export abstract class ResizableContentWidget extends Disposable implements ICont
|
|||
return;
|
||||
}
|
||||
const editorBox = dom.getDomNodePagePosition(editorDomNode);
|
||||
const bodyBox = dom.getClientArea(document.body);
|
||||
const bodyBox = dom.getClientArea(editorDomNode.ownerDocument.body);
|
||||
const mouseBottom = editorBox.top + mouseBox.top + mouseBox.height;
|
||||
return bodyBox.height - mouseBottom - BOTTOM_HEIGHT;
|
||||
}
|
||||
|
|
|
@ -708,7 +708,7 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction {
|
|||
if (selection.isEmpty()) {
|
||||
if (selection.startColumn === 1) {
|
||||
const deleteFromLine = Math.max(1, selection.startLineNumber - 1);
|
||||
const deleteFromColumn = selection.startLineNumber === 1 ? 1 : model.getLineContent(deleteFromLine).length + 1;
|
||||
const deleteFromColumn = selection.startLineNumber === 1 ? 1 : model.getLineLength(deleteFromLine) + 1;
|
||||
return new Range(deleteFromLine, deleteFromColumn, selection.startLineNumber, 1);
|
||||
} else {
|
||||
return new Range(selection.startLineNumber, 1, selection.startLineNumber, selection.startColumn);
|
||||
|
@ -864,7 +864,7 @@ export class JoinLinesAction extends EditorAction {
|
|||
let endLineNumber: number,
|
||||
endColumn: number;
|
||||
|
||||
const selectionEndPositionOffset = model.getLineContent(selection.endLineNumber).length - selection.endColumn;
|
||||
const selectionEndPositionOffset = model.getLineLength(selection.endLineNumber) - selection.endColumn;
|
||||
|
||||
if (selection.isEmpty() || selection.startLineNumber === selection.endLineNumber) {
|
||||
const position = selection.getStartPosition();
|
||||
|
|
|
@ -82,7 +82,7 @@ export class MessageController implements IEditorContribution {
|
|||
return; // override when mouse over message
|
||||
}
|
||||
|
||||
if (this._messageWidget.value && dom.isAncestor(document.activeElement, this._messageWidget.value.getDomNode())) {
|
||||
if (this._messageWidget.value && dom.isAncestor(dom.getActiveElement(), this._messageWidget.value.getDomNode())) {
|
||||
return; // override when focus is inside the message
|
||||
}
|
||||
|
||||
|
|
|
@ -192,7 +192,7 @@ export class RenameInputField implements IContentWidget {
|
|||
};
|
||||
|
||||
disposeOnDone.add(token.onCancellationRequested(() => this.cancelInput(true)));
|
||||
disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!document.hasFocus())));
|
||||
disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus())));
|
||||
|
||||
this._show();
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ export class SnippetController2 implements IEditorContribution {
|
|||
private readonly _hasPrevTabstop: IContextKey<boolean>;
|
||||
|
||||
private _session?: SnippetSession;
|
||||
private _snippetListener = new DisposableStore();
|
||||
private readonly _snippetListener = new DisposableStore();
|
||||
private _modelVersionId: number = -1;
|
||||
private _currentChoice?: Choice;
|
||||
|
||||
|
|
|
@ -787,7 +787,7 @@ export class SuggestWidget implements IDisposable {
|
|||
return;
|
||||
}
|
||||
|
||||
const bodyBox = dom.getClientArea(document.body);
|
||||
const bodyBox = dom.getClientArea(this.element.domNode.ownerDocument.body);
|
||||
const info = this.getLayoutInfo();
|
||||
|
||||
if (!size) {
|
||||
|
|
|
@ -372,7 +372,7 @@ export class SuggestDetailsOverlay implements IOverlayWidget {
|
|||
}
|
||||
|
||||
_placeAtAnchor(anchorBox: dom.IDomNodePagePosition, size: dom.Dimension, preferAlignAtTop: boolean) {
|
||||
const bodyBox = dom.getClientArea(document.body);
|
||||
const bodyBox = dom.getClientArea(this.getDomNode().ownerDocument.body);
|
||||
|
||||
const info = this.widget.getLayoutInfo();
|
||||
|
||||
|
|
|
@ -91,6 +91,8 @@ suite('TextAreaInput', () => {
|
|||
private _state: IRecordedTextareaState;
|
||||
private _currDispatchingEvent: IRecordedEvent | null;
|
||||
|
||||
public ownerDocument = document;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._state = {
|
||||
|
|
|
@ -100,6 +100,7 @@ export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor {
|
|||
|
||||
class TestEditorDomElement {
|
||||
parentElement: IContextKeyServiceTarget | null = null;
|
||||
ownerDocument = document;
|
||||
setAttribute(attr: string, value: string): void { }
|
||||
removeAttribute(attr: string): void { }
|
||||
hasAttribute(attr: string): boolean { return false; }
|
||||
|
|
|
@ -257,7 +257,7 @@ suite('EditorSimpleWorker', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test.skip('[Bug] Getting Message "Overlapping ranges are not allowed" and nothing happens with Inline-Chat ', async function () {
|
||||
test('[Bug] Getting Message "Overlapping ranges are not allowed" and nothing happens with Inline-Chat ', async function () {
|
||||
await testEdits(("const API = require('../src/api');\n\ndescribe('API', () => {\n let api;\n let database;\n\n beforeAll(() => {\n database = {\n getAllBooks: jest.fn(),\n getBooksByAuthor: jest.fn(),\n getBooksByTitle: jest.fn(),\n };\n api = new API(database);\n });\n\n describe('GET /books', () => {\n it('should return all books', async () => {\n const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }];\n database.getAllBooks.mockResolvedValue(mockBooks);\n\n const req = {};\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === '/books') {\n handler(req, res);\n }\n },\n });\n\n expect(database.getAllBooks).toHaveBeenCalled();\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n\n describe('GET /books/author/:author', () => {\n it('should return books by author', async () => {\n const mockAuthor = 'John Doe';\n const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }];\n database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n const req = {\n params: {\n author: mockAuthor,\n },\n };\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === `/books/author/${mockAuthor}`) {\n handler(req, res);\n }\n },\n });\n\n expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n\n describe('GET /books/title/:title', () => {\n it('should return books by title', async () => {\n const mockTitle = 'Book 1';\n const mockBooks = [{ title: mockTitle, author: 'John Doe' }];\n database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n const req = {\n params: {\n title: mockTitle,\n },\n };\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === `/books/title/${mockTitle}`) {\n handler(req, res);\n }\n },\n });\n\n expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n});\n").split('\n'),
|
||||
[{
|
||||
range: { startLineNumber: 1, startColumn: 1, endLineNumber: 96, endColumn: 1 },
|
||||
|
@ -337,7 +337,7 @@ function applyEdits(text: string, edits: { range: IRange; text: string }[]): str
|
|||
class PositionOffsetTransformer {
|
||||
private readonly lineStartOffsetByLineIdx: number[];
|
||||
|
||||
constructor(text: string) {
|
||||
constructor(private readonly text: string) {
|
||||
this.lineStartOffsetByLineIdx = [];
|
||||
this.lineStartOffsetByLineIdx.push(0);
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
|
@ -349,7 +349,7 @@ class PositionOffsetTransformer {
|
|||
}
|
||||
|
||||
getOffset(position: Position): number {
|
||||
const nextLineOffset = this.lineStartOffsetByLineIdx[position.lineNumber];
|
||||
return Math.min(this.lineStartOffsetByLineIdx[position.lineNumber - 1] + position.column - 1, nextLineOffset - 1);
|
||||
const maxLineOffset = position.lineNumber >= this.lineStartOffsetByLineIdx.length ? this.text.length : (this.lineStartOffsetByLineIdx[position.lineNumber] - 1);
|
||||
return Math.min(this.lineStartOffsetByLineIdx[position.lineNumber - 1] + position.column - 1, maxLineOffset);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { FileAccess } from 'vs/base/common/network';
|
|||
import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping';
|
||||
import { LegacyLinesDiffComputer } from 'vs/editor/common/diff/legacyLinesDiffComputer';
|
||||
import { DefaultLinesDiffComputer } from 'vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
|
||||
suite('diffing fixtures', () => {
|
||||
setup(() => {
|
||||
|
@ -48,12 +49,18 @@ suite('diffing fixtures', () => {
|
|||
originalRange: c.original.toString(),
|
||||
modifiedRange: c.modified.toString(),
|
||||
innerChanges: c.innerChanges?.map<IDiff>(c => ({
|
||||
originalRange: c.originalRange.toString(),
|
||||
modifiedRange: c.modifiedRange.toString(),
|
||||
originalRange: formatRange(c.originalRange, firstContentLines),
|
||||
modifiedRange: formatRange(c.modifiedRange, secondContentLines),
|
||||
})) || null
|
||||
}));
|
||||
}
|
||||
|
||||
function formatRange(range: Range, lines: string[]): string {
|
||||
const toLastChar = range.endColumn === lines[range.endLineNumber - 1].length + 1;
|
||||
|
||||
return '[' + range.startLineNumber + ',' + range.startColumn + ' -> ' + range.endLineNumber + ',' + range.endColumn + (toLastChar ? ' EOL' : '') + ']';
|
||||
}
|
||||
|
||||
const actualDiffingResult: DiffingResult = {
|
||||
original: { content: firstContent, fileName: `./${firstFileName}` },
|
||||
modified: { content: secondContent, fileName: `./${secondFileName}` },
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
"modifiedRange": "[29,31)",
|
||||
"innerChanges": [
|
||||
{
|
||||
"originalRange": "[29,1 -> 33,41]",
|
||||
"modifiedRange": "[29,1 -> 30,54]"
|
||||
"originalRange": "[29,1 -> 33,41 EOL]",
|
||||
"modifiedRange": "[29,1 -> 30,54 EOL]"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -59,8 +59,8 @@
|
|||
"modifiedRange": "[36,42 -> 36,43]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[41,29 -> 45,24]",
|
||||
"modifiedRange": "[36,46 -> 36,66]"
|
||||
"originalRange": "[41,29 -> 45,24 EOL]",
|
||||
"modifiedRange": "[36,46 -> 36,66 EOL]"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
"innerChanges": [
|
||||
{
|
||||
"originalRange": "[1,1 -> 29,64]",
|
||||
"modifiedRange": "[1,1 -> 1,1]"
|
||||
"modifiedRange": "[1,1 -> 1,1 EOL]"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
"modifiedRange": "[156,163)",
|
||||
"innerChanges": [
|
||||
{
|
||||
"originalRange": "[159,1 -> 159,1]",
|
||||
"modifiedRange": "[156,1 -> 163,1]"
|
||||
"originalRange": "[159,1 -> 159,1 EOL]",
|
||||
"modifiedRange": "[156,1 -> 163,1 EOL]"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
"modifiedRange": "[226,234)",
|
||||
"innerChanges": [
|
||||
{
|
||||
"originalRange": "[222,17 -> 222,19]",
|
||||
"modifiedRange": "[226,17 -> 226,17]"
|
||||
"originalRange": "[222,17 -> 222,19 EOL]",
|
||||
"modifiedRange": "[226,17 -> 226,17 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[223,4 -> 223,28]",
|
||||
|
@ -40,8 +40,8 @@
|
|||
"modifiedRange": "[227,41 -> 227,48]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[223,54 -> 223,65]",
|
||||
"modifiedRange": "[227,53 -> 227,62]"
|
||||
"originalRange": "[223,54 -> 223,65 EOL]",
|
||||
"modifiedRange": "[227,53 -> 227,62 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[224,4 -> 224,21]",
|
||||
|
@ -60,8 +60,8 @@
|
|||
"modifiedRange": "[228,123 -> 228,152]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[226,22 -> 226,25]",
|
||||
"modifiedRange": "[228,166 -> 228,169]"
|
||||
"originalRange": "[226,22 -> 226,25 EOL]",
|
||||
"modifiedRange": "[228,166 -> 228,169 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[227,5 -> 227,30]",
|
||||
|
@ -72,16 +72,16 @@
|
|||
"modifiedRange": "[229,28 -> 229,32]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[227,45 -> 227,93]",
|
||||
"modifiedRange": "[229,35 -> 229,67]"
|
||||
"originalRange": "[227,45 -> 227,93 EOL]",
|
||||
"modifiedRange": "[229,35 -> 229,67 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[228,5 -> 228,51]",
|
||||
"modifiedRange": "[230,5 -> 230,30]"
|
||||
"originalRange": "[228,5 -> 228,51 EOL]",
|
||||
"modifiedRange": "[230,5 -> 230,30 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[229,6 -> 230,6]",
|
||||
"modifiedRange": "[231,6 -> 231,40]"
|
||||
"originalRange": "[229,6 -> 230,6 EOL]",
|
||||
"modifiedRange": "[231,6 -> 231,40 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[231,4 -> 232,42]",
|
||||
|
@ -92,8 +92,8 @@
|
|||
"modifiedRange": "[232,25 -> 233,32]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[232,72 -> 233,4]",
|
||||
"modifiedRange": "[233,35 -> 233,60]"
|
||||
"originalRange": "[232,72 -> 233,4 EOL]",
|
||||
"modifiedRange": "[233,35 -> 233,60 EOL]"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -17,32 +17,32 @@
|
|||
"modifiedRange": "[1,1 -> 1,1]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[2,15 -> 2,15]",
|
||||
"modifiedRange": "[1,15 -> 1,16]"
|
||||
"originalRange": "[2,15 -> 2,15 EOL]",
|
||||
"modifiedRange": "[1,15 -> 1,16 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[3,15 -> 3,15]",
|
||||
"modifiedRange": "[2,15 -> 2,16]"
|
||||
"originalRange": "[3,15 -> 3,15 EOL]",
|
||||
"modifiedRange": "[2,15 -> 2,16 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[4,15 -> 4,15]",
|
||||
"modifiedRange": "[3,15 -> 3,16]"
|
||||
"originalRange": "[4,15 -> 4,15 EOL]",
|
||||
"modifiedRange": "[3,15 -> 3,16 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[5,15 -> 5,15]",
|
||||
"modifiedRange": "[4,15 -> 5,1]"
|
||||
"originalRange": "[5,15 -> 5,15 EOL]",
|
||||
"modifiedRange": "[4,15 -> 5,1 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[6,15 -> 6,15]",
|
||||
"modifiedRange": "[6,15 -> 6,16]"
|
||||
"originalRange": "[6,15 -> 6,15 EOL]",
|
||||
"modifiedRange": "[6,15 -> 6,16 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[7,15 -> 7,15]",
|
||||
"modifiedRange": "[7,15 -> 7,16]"
|
||||
"originalRange": "[7,15 -> 7,15 EOL]",
|
||||
"modifiedRange": "[7,15 -> 7,16 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[8,15 -> 8,15]",
|
||||
"modifiedRange": "[8,15 -> 9,1]"
|
||||
"originalRange": "[8,15 -> 8,15 EOL]",
|
||||
"modifiedRange": "[8,15 -> 9,1 EOL]"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import { RuntimeMode } from './runtimeMode';
|
||||
import { PromiseQueue } from './telemetry';
|
||||
import { TestNotificationSender, TestUrlOpener } from './testHelpers';
|
|
@ -0,0 +1,3 @@
|
|||
import { RuntimeMode } from './runtimeMode';
|
||||
import { PromiseQueue, TestPromiseQueue } from './telemetry';
|
||||
import { TestNotificationSender, TestUrlOpener } from './testHelpers';
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"original": {
|
||||
"content": "import { RuntimeMode } from './runtimeMode';\nimport { PromiseQueue } from './telemetry';\nimport { TestNotificationSender, TestUrlOpener } from './testHelpers';\n",
|
||||
"fileName": "./1.tst"
|
||||
},
|
||||
"modified": {
|
||||
"content": "import { RuntimeMode } from './runtimeMode';\nimport { PromiseQueue, TestPromiseQueue } from './telemetry';\nimport { TestNotificationSender, TestUrlOpener } from './testHelpers';\n",
|
||||
"fileName": "./2.tst"
|
||||
},
|
||||
"diffs": [
|
||||
{
|
||||
"originalRange": "[2,3)",
|
||||
"modifiedRange": "[2,3)",
|
||||
"innerChanges": [
|
||||
{
|
||||
"originalRange": "[2,22 -> 2,22]",
|
||||
"modifiedRange": "[2,22 -> 2,40]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"original": {
|
||||
"content": "import { RuntimeMode } from './runtimeMode';\nimport { PromiseQueue } from './telemetry';\nimport { TestNotificationSender, TestUrlOpener } from './testHelpers';\n",
|
||||
"fileName": "./1.tst"
|
||||
},
|
||||
"modified": {
|
||||
"content": "import { RuntimeMode } from './runtimeMode';\nimport { PromiseQueue, TestPromiseQueue } from './telemetry';\nimport { TestNotificationSender, TestUrlOpener } from './testHelpers';\n",
|
||||
"fileName": "./2.tst"
|
||||
},
|
||||
"diffs": [
|
||||
{
|
||||
"originalRange": "[2,3)",
|
||||
"modifiedRange": "[2,3)",
|
||||
"innerChanges": [
|
||||
{
|
||||
"originalRange": "[2,22 -> 2,22]",
|
||||
"modifiedRange": "[2,22 -> 2,40]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -13,16 +13,16 @@
|
|||
"modifiedRange": "[1,4)",
|
||||
"innerChanges": [
|
||||
{
|
||||
"originalRange": "[1,15 -> 1,16]",
|
||||
"modifiedRange": "[1,15 -> 1,15]"
|
||||
"originalRange": "[1,15 -> 1,16 EOL]",
|
||||
"modifiedRange": "[1,15 -> 1,15 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[2,15 -> 3,16]",
|
||||
"modifiedRange": "[2,15 -> 2,15]"
|
||||
"originalRange": "[2,15 -> 3,16 EOL]",
|
||||
"modifiedRange": "[2,15 -> 2,15 EOL]"
|
||||
},
|
||||
{
|
||||
"originalRange": "[4,15 -> 4,16]",
|
||||
"modifiedRange": "[3,15 -> 3,15]"
|
||||
"originalRange": "[4,15 -> 4,16 EOL]",
|
||||
"modifiedRange": "[3,15 -> 3,15 EOL]"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue