Merge branch 'main' into aamunger/scrollOutputWithKeys

This commit is contained in:
Aaron Munger 2023-05-11 11:07:02 -07:00 committed by GitHub
commit 9ede543860
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 1864 additions and 1266 deletions

View file

@ -51,6 +51,7 @@ const setLauncherEnvironmentVars = () => {
['VSCODE_CLI_TUNNEL_SERVICE_MUTEX', product.win32TunnelServiceMutex],
['VSCODE_CLI_TUNNEL_CLI_MUTEX', product.win32TunnelMutex],
['VSCODE_CLI_COMMIT', commit],
['VSCODE_CLI_DEFAULT_PARENT_DATA_DIR', product.dataFolderName],
[
'VSCODE_CLI_WIN32_APP_IDS',
!isOSS && JSON.stringify(makeQualityMap(json => Object.entries(json)
@ -88,4 +89,4 @@ const setLauncherEnvironmentVars = () => {
if (require.main === module) {
setLauncherEnvironmentVars();
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlcGFyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInByZXBhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxxREFBa0Q7QUFDbEQseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QixxREFBcUQ7QUFFckQsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1QkFBdUIsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDeEcsTUFBTSxRQUFRLEdBQUcsQ0FBQyxJQUFZLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUU3RSxJQUFJLGVBQXVCLENBQUM7QUFDNUIsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEtBQUssS0FBSyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUM7QUFDbEYsSUFBSSxLQUFLLEVBQUU7SUFDVixlQUFlLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsY0FBYyxDQUFDLENBQUM7Q0FDbEQ7S0FBTTtJQUNOLGVBQWUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFlLEVBQUUsY0FBYyxDQUFDLENBQUM7Q0FDeEY7QUFFRCxPQUFPLENBQUMsS0FBSyxDQUFDLDJCQUEyQixFQUFFLGVBQWUsQ0FBQyxDQUFDO0FBQzVELE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQztBQUMxQyxNQUFNLHVCQUF1QixHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQztLQUMxRixHQUFHLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxjQUFjLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ25HLE1BQU0sTUFBTSxHQUFHLElBQUEsdUJBQVUsRUFBQyxJQUFJLENBQUMsQ0FBQztBQUVoQyxNQUFNLGNBQWMsR0FBRyxDQUFJLENBQTJDLEVBQXFCLEVBQUU7SUFDNUYsTUFBTSxNQUFNLEdBQXNCLEVBQUUsQ0FBQztJQUNyQyxLQUFLLE1BQU0sRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLElBQUksdUJBQXVCLEVBQUU7UUFDeEQsTUFBTSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUM7S0FDbkM7SUFDRCxPQUFPLE1BQU0sQ0FBQztBQUNmLENBQUMsQ0FBQztBQUVGOztHQUVHO0FBQ0gsTUFBTSwwQkFBMEIsR0FBRyxHQUFHLEVBQUU7SUFDdkMsTUFBTSxJQUFJLEdBQUcsSUFBSSxHQUFHLENBQUM7UUFDcEIsQ0FBQyxnQ0FBZ0MsRUFBRSxPQUFPLENBQUMsYUFBYSxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN0RSxDQUFDLGtDQUFrQyxFQUFFLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQztRQUNqRSxDQUFDLG1CQUFtQixFQUFFLE9BQU8sQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDO1FBQy9DLENBQUMsd0JBQXdCLEVBQUUsT0FBTyxDQUFDLFFBQVEsRUFBRSxXQUFXLENBQUM7UUFDekQsQ0FBQyxvQkFBb0IsRUFBRSxXQUFXLENBQUMsT0FBTyxDQUFDO1FBQzNDLENBQUMsNEJBQTRCLEVBQUUsT0FBTyxDQUFDLFNBQVMsQ0FBQztRQUNqRCxDQUFDLG9CQUFvQixFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUM7UUFDdkMsQ0FBQyx1QkFBdUIsRUFBRSxPQUFPLENBQUMsU0FBUyxDQUFDO1FBQzVDLENBQUMsc0JBQXNCLEVBQUUsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUMxQyxDQUFDLHFDQUFxQyxFQUFFLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLGFBQWEsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNwRixDQUFDLDhCQUE4QixFQUFFLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQztRQUMxRCxDQUFDLDZCQUE2QixFQUFFLE9BQU8sQ0FBQyxlQUFlLENBQUM7UUFDeEQsQ0FBQywyQkFBMkIsRUFBRSxPQUFPLENBQUMsdUJBQXVCLEVBQUUsWUFBWSxDQUFDO1FBQzVFLENBQUMsaUNBQWlDLEVBQUUsT0FBTyxDQUFDLHVCQUF1QixDQUFDO1FBQ3BFLENBQUMsNkJBQTZCLEVBQUUsT0FBTyxDQUFDLGdCQUFnQixDQUFDO1FBQ3pELENBQUMsbUJBQW1CLEVBQUUsTUFBTSxDQUFDO1FBQzdCO1lBQ0MsMEJBQTBCO1lBQzFCLENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxTQUFTLENBQ3ZCLGNBQWMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDO2lCQUN6QyxNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7aUJBQzdDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUN6RDtTQUNEO1FBQ0Q7WUFDQywwQkFBMEI7WUFDMUIsQ0FBQyxLQUFLLElBQUksSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7U0FDL0Q7UUFDRDtZQUNDLGlDQUFpQztZQUNqQyxDQUFDLEtBQUssSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztTQUN0RTtRQUNEO1lBQ0MsNEJBQTRCO1lBQzVCLENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxTQUFTLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLHFCQUFxQixDQUFDLENBQUM7U0FDNUU7UUFDRDtZQUNDLGtDQUFrQztZQUNsQyxDQUFDLEtBQUssSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztTQUNsRTtLQUNELENBQUMsQ0FBQztJQUVILElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyx5QkFBeUIsS0FBSyxNQUFNLEVBQUU7UUFDckQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7S0FDOUQ7U0FBTTtRQUNOLEtBQUssTUFBTSxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsSUFBSSxJQUFJLEVBQUU7WUFDaEMsSUFBSSxLQUFLLEVBQUU7Z0JBQ1YsT0FBTyxDQUFDLEdBQUcsQ0FBQyxtQ0FBbUMsR0FBRyxJQUFJLEtBQUssRUFBRSxDQUFDLENBQUM7YUFDL0Q7U0FDRDtLQUNEO0FBRUYsQ0FBQyxDQUFDO0FBRUYsSUFBSSxPQUFPLENBQUMsSUFBSSxLQUFLLE1BQU0sRUFBRTtJQUM1QiwwQkFBMEIsRUFBRSxDQUFDO0NBQzdCIn0=
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlcGFyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInByZXBhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxxREFBa0Q7QUFDbEQseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QixxREFBcUQ7QUFFckQsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1QkFBdUIsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDeEcsTUFBTSxRQUFRLEdBQUcsQ0FBQyxJQUFZLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUU3RSxJQUFJLGVBQXVCLENBQUM7QUFDNUIsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEtBQUssS0FBSyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUM7QUFDbEYsSUFBSSxLQUFLLEVBQUU7SUFDVixlQUFlLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsY0FBYyxDQUFDLENBQUM7Q0FDbEQ7S0FBTTtJQUNOLGVBQWUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFlLEVBQUUsY0FBYyxDQUFDLENBQUM7Q0FDeEY7QUFFRCxPQUFPLENBQUMsS0FBSyxDQUFDLDJCQUEyQixFQUFFLGVBQWUsQ0FBQyxDQUFDO0FBQzVELE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQztBQUMxQyxNQUFNLHVCQUF1QixHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQztLQUMxRixHQUFHLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxjQUFjLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ25HLE1BQU0sTUFBTSxHQUFHLElBQUEsdUJBQVUsRUFBQyxJQUFJLENBQUMsQ0FBQztBQUVoQyxNQUFNLGNBQWMsR0FBRyxDQUFJLENBQTJDLEVBQXFCLEVBQUU7SUFDNUYsTUFBTSxNQUFNLEdBQXNCLEVBQUUsQ0FBQztJQUNyQyxLQUFLLE1BQU0sRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLElBQUksdUJBQXVCLEVBQUU7UUFDeEQsTUFBTSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUM7S0FDbkM7SUFDRCxPQUFPLE1BQU0sQ0FBQztBQUNmLENBQUMsQ0FBQztBQUVGOztHQUVHO0FBQ0gsTUFBTSwwQkFBMEIsR0FBRyxHQUFHLEVBQUU7SUFDdkMsTUFBTSxJQUFJLEdBQUcsSUFBSSxHQUFHLENBQUM7UUFDcEIsQ0FBQyxnQ0FBZ0MsRUFBRSxPQUFPLENBQUMsYUFBYSxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN0RSxDQUFDLGtDQUFrQyxFQUFFLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQztRQUNqRSxDQUFDLG1CQUFtQixFQUFFLE9BQU8sQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDO1FBQy9DLENBQUMsd0JBQXdCLEVBQUUsT0FBTyxDQUFDLFFBQVEsRUFBRSxXQUFXLENBQUM7UUFDekQsQ0FBQyxvQkFBb0IsRUFBRSxXQUFXLENBQUMsT0FBTyxDQUFDO1FBQzNDLENBQUMsNEJBQTRCLEVBQUUsT0FBTyxDQUFDLFNBQVMsQ0FBQztRQUNqRCxDQUFDLG9CQUFvQixFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUM7UUFDdkMsQ0FBQyx1QkFBdUIsRUFBRSxPQUFPLENBQUMsU0FBUyxDQUFDO1FBQzVDLENBQUMsc0JBQXNCLEVBQUUsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUMxQyxDQUFDLHFDQUFxQyxFQUFFLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLGFBQWEsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNwRixDQUFDLDhCQUE4QixFQUFFLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQztRQUMxRCxDQUFDLDZCQUE2QixFQUFFLE9BQU8sQ0FBQyxlQUFlLENBQUM7UUFDeEQsQ0FBQywyQkFBMkIsRUFBRSxPQUFPLENBQUMsdUJBQXVCLEVBQUUsWUFBWSxDQUFDO1FBQzVFLENBQUMsaUNBQWlDLEVBQUUsT0FBTyxDQUFDLHVCQUF1QixDQUFDO1FBQ3BFLENBQUMsNkJBQTZCLEVBQUUsT0FBTyxDQUFDLGdCQUFnQixDQUFDO1FBQ3pELENBQUMsbUJBQW1CLEVBQUUsTUFBTSxDQUFDO1FBQzdCLENBQUMsb0NBQW9DLEVBQUUsT0FBTyxDQUFDLGNBQWMsQ0FBQztRQUM5RDtZQUNDLDBCQUEwQjtZQUMxQixDQUFDLEtBQUssSUFBSSxJQUFJLENBQUMsU0FBUyxDQUN2QixjQUFjLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQztpQkFDekMsTUFBTSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsRUFBRSxFQUFFLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO2lCQUM3QyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxDQUFDLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FDekQ7U0FDRDtRQUNEO1lBQ0MsMEJBQTBCO1lBQzFCLENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxTQUFTLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1NBQy9EO1FBQ0Q7WUFDQyxpQ0FBaUM7WUFDakMsQ0FBQyxLQUFLLElBQUksSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLENBQUM7U0FDdEU7UUFDRDtZQUNDLDRCQUE0QjtZQUM1QixDQUFDLEtBQUssSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO1NBQzVFO1FBQ0Q7WUFDQyxrQ0FBa0M7WUFDbEMsQ0FBQyxLQUFLLElBQUksSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUM7U0FDbEU7S0FDRCxDQUFDLENBQUM7SUFFSCxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMseUJBQXlCLEtBQUssTUFBTSxFQUFFO1FBQ3JELE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0tBQzlEO1NBQU07UUFDTixLQUFLLE1BQU0sQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLElBQUksSUFBSSxFQUFFO1lBQ2hDLElBQUksS0FBSyxFQUFFO2dCQUNWLE9BQU8sQ0FBQyxHQUFHLENBQUMsbUNBQW1DLEdBQUcsSUFBSSxLQUFLLEVBQUUsQ0FBQyxDQUFDO2FBQy9EO1NBQ0Q7S0FDRDtBQUVGLENBQUMsQ0FBQztBQUVGLElBQUksT0FBTyxDQUFDLElBQUksS0FBSyxNQUFNLEVBQUU7SUFDNUIsMEJBQTBCLEVBQUUsQ0FBQztDQUM3QiJ9

View file

@ -54,6 +54,7 @@ const setLauncherEnvironmentVars = () => {
['VSCODE_CLI_TUNNEL_SERVICE_MUTEX', product.win32TunnelServiceMutex],
['VSCODE_CLI_TUNNEL_CLI_MUTEX', product.win32TunnelMutex],
['VSCODE_CLI_COMMIT', commit],
['VSCODE_CLI_DEFAULT_PARENT_DATA_DIR', product.dataFolderName],
[
'VSCODE_CLI_WIN32_APP_IDS',
!isOSS && JSON.stringify(

View file

@ -36,7 +36,7 @@ async fn main() -> Result<(), std::convert::Infallible> {
});
let core = parsed.core();
let context_paths = LauncherPaths::new(&core.global_options.cli_data_dir).unwrap();
let context_paths = LauncherPaths::migrate(core.global_options.cli_data_dir.clone()).unwrap();
let context_args = core.clone();
// gets a command context without installing the global logger

View file

@ -75,6 +75,12 @@ pub const TUNNEL_ACTIVITY_NAME: &str = concatcp!(PRODUCT_NAME_LONG, " Tunnel");
const NONINTERACTIVE_VAR: &str = "VSCODE_CLI_NONINTERACTIVE";
/// Default data CLI data directory.
pub const DEFAULT_DATA_PARENT_DIR: &str = match option_env!("VSCODE_CLI_DEFAULT_PARENT_DATA_DIR") {
Some(n) => n,
None => ".vscode-oss",
};
pub fn get_default_user_agent() -> String {
format!(
"vscode-server-launcher/{}",

View file

@ -6,7 +6,7 @@
extern crate dirs;
use std::{
fs::{create_dir, read_to_string, remove_dir_all, write},
fs::{create_dir_all, read_to_string, remove_dir_all, write},
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
@ -14,7 +14,7 @@ use std::{
use serde::{de::DeserializeOwned, Serialize};
use crate::{
constants::VSCODE_CLI_QUALITY,
constants::{DEFAULT_DATA_PARENT_DIR, VSCODE_CLI_QUALITY},
download_cache::DownloadCache,
util::errors::{wrap, AnyError, NoHomeForLauncherError, WrappedError},
};
@ -107,8 +107,38 @@ where
}
impl LauncherPaths {
pub fn new(root: &Option<String>) -> Result<LauncherPaths, AnyError> {
let root = root.as_deref().unwrap_or("~/.vscode-cli");
/// todo@conno4312: temporary migration from the old CLI data directory
pub fn migrate(root: Option<String>) -> Result<LauncherPaths, AnyError> {
if root.is_some() {
return Self::new(root);
}
let home_dir = match dirs::home_dir() {
None => return Self::new(root),
Some(d) => d,
};
let old_dir = home_dir.join(".vscode-cli");
let mut new_dir = home_dir;
new_dir.push(DEFAULT_DATA_PARENT_DIR);
new_dir.push("cli");
if !old_dir.exists() || new_dir.exists() {
return Self::new_for_path(new_dir);
}
if let Err(e) = std::fs::rename(&old_dir, &new_dir) {
// no logger exists at this point in the lifecycle, so just log to stderr
eprintln!(
"Failed to migrate old CLI data directory, will create a new one ({})",
e
);
}
Self::new_for_path(new_dir)
}
pub fn new(root: Option<String>) -> Result<LauncherPaths, AnyError> {
let root = root.unwrap_or_else(|| format!("~/{}/cli", DEFAULT_DATA_PARENT_DIR));
let mut replaced = root.to_owned();
for token in HOME_DIR_ALTS {
if root.contains(token) {
@ -120,14 +150,16 @@ impl LauncherPaths {
}
}
if !Path::new(&replaced).exists() {
create_dir(&replaced)
.map_err(|e| wrap(e, format!("error creating directory {}", &replaced)))?;
Self::new_for_path(PathBuf::from(replaced))
}
fn new_for_path(root: PathBuf) -> Result<LauncherPaths, AnyError> {
if !root.exists() {
create_dir_all(&root)
.map_err(|e| wrap(e, format!("error creating directory {}", root.display())))?;
}
Ok(LauncherPaths::new_without_replacements(PathBuf::from(
replaced,
)))
Ok(LauncherPaths::new_without_replacements(root))
}
pub fn new_without_replacements(root: PathBuf) -> LauncherPaths {

View file

@ -482,11 +482,10 @@
"@types/node": "16.x"
},
"dependencies": {
"@emmetio/abbreviation": "^2.2.0",
"@emmetio/css-parser": "ramya-rao-a/css-parser#vscode",
"@emmetio/html-matcher": "^0.3.3",
"@emmetio/math-expression": "^1.0.4",
"@vscode/emmet-helper": "^2.3.0",
"@emmetio/math-expression": "^1.0.5",
"@vscode/emmet-helper": "^2.8.8",
"image-size": "~1.0.0",
"vscode-languageserver-textdocument": "^1.0.1"
},

View file

@ -2,26 +2,19 @@
# yarn lockfile v1
"@emmetio/abbreviation@^2.2.0":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@emmetio/abbreviation/-/abbreviation-2.2.2.tgz#746762fd9e7a8c2ea604f580c62e3cfe250e6989"
integrity sha512-TtE/dBnkTCct8+LntkqVrwqQao6EnPAs1YN3cUgxOxTaBlesBCY37ROUAVZrRlG64GNnVShdl/b70RfAI3w5lw==
"@emmetio/abbreviation@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@emmetio/abbreviation/-/abbreviation-2.3.2.tgz#375bf6bc6ae6405f62dd0ddab2559b46502d01f4"
integrity sha512-8vqkn4rtjm5Zv34RPgsq3/ij88ri+IcfC2MxPELytrQvfpaLyppscE0YSwDVuIUR6KL5GCBUfr5Mo7SHSbswpA==
dependencies:
"@emmetio/scanner" "^1.0.0"
"@emmetio/scanner" "^1.0.3"
"@emmetio/abbreviation@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@emmetio/abbreviation/-/abbreviation-2.2.3.tgz#2b3c0383c1a4652f677d5b56fb3f1616fe16ef10"
integrity sha512-87pltuCPt99aL+y9xS6GPZ+Wmmyhll2WXH73gG/xpGcQ84DRnptBsI2r0BeIQ0EB/SQTOe2ANPqFqj3Rj5FOGA==
"@emmetio/css-abbreviation@^2.1.7":
version "2.1.7"
resolved "https://registry.yarnpkg.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.7.tgz#9791269586d780cf4b40078ea79886d1888a188a"
integrity sha512-nrOt3/QROjYYK1cMjoO5fCfHIf0hFpcZeQQt7Ew6ixZ0ElEEs77ijnY57HC6ti91W/mn+c1T7ET8sClBMRHHBg==
dependencies:
"@emmetio/scanner" "^1.0.0"
"@emmetio/css-abbreviation@^2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.4.tgz#90362e8a1122ce3b76f6c3157907d30182f53f54"
integrity sha512-qk9L60Y+uRtM5CPbB0y+QNl/1XKE09mSO+AhhSauIfr2YOx/ta3NJw2d8RtCFxgzHeRqFRr8jgyzThbu+MZ4Uw==
dependencies:
"@emmetio/scanner" "^1.0.0"
"@emmetio/scanner" "^1.0.3"
"@emmetio/css-parser@ramya-rao-a/css-parser#vscode":
version "0.4.0"
@ -38,17 +31,22 @@
"@emmetio/stream-reader" "^2.0.0"
"@emmetio/stream-reader-utils" "^0.1.0"
"@emmetio/math-expression@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@emmetio/math-expression/-/math-expression-1.0.4.tgz#cb657ed944f82b3728f863bf5ece1b1ff3ae7497"
integrity sha512-1m7y8/VeXCAfgFoPGTerbqCIadApcIINujd3TaM/LRLPPKiod8aT1PPmh542spnsUSsSnZJjbuF7xiO4WFA42g==
"@emmetio/math-expression@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@emmetio/math-expression/-/math-expression-1.0.5.tgz#d0cc52ed453a107bc9b19c5d71d1390d3aecbe48"
integrity sha512-qf5SXD/ViS04rXSeDg9CRGM10xLC9dVaKIbMHrrwxYr5LNB/C0rOfokhGSBwnVQKcidLmdRJeNWH1V1tppZ84Q==
dependencies:
"@emmetio/scanner" "^1.0.0"
"@emmetio/scanner" "^1.0.4"
"@emmetio/scanner@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emmetio/scanner/-/scanner-1.0.0.tgz#065b2af6233fe7474d44823e3deb89724af42b5f"
integrity sha512-8HqW8EVqjnCmWXVpqAOZf+EGESdkR27odcMMMGefgKXtar00SoYNSryGv//TELI4T3QFsECo78p+0lmalk/CFA==
"@emmetio/scanner@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@emmetio/scanner/-/scanner-1.0.3.tgz#755e581517e2302d31a387e4064bf73035ebfc46"
integrity sha512-/EFyTijquAwKMGSBd50RnjxsfDXmZAFp71PGu7sM6LEnEJXMV+FKL7Rvr6YLu4czQmPVRsfyhcbQz+WZnM4AZw==
"@emmetio/scanner@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@emmetio/scanner/-/scanner-1.0.4.tgz#e9cdc67194fd91f8b7eb141014be4f2d086c15f1"
integrity sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==
"@emmetio/stream-reader-utils@^0.1.0":
version "0.1.0"
@ -65,24 +63,24 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae"
integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==
"@vscode/emmet-helper@^2.3.0":
version "2.8.6"
resolved "https://registry.yarnpkg.com/@vscode/emmet-helper/-/emmet-helper-2.8.6.tgz#ee2fa52321d6af8a40310fd9d37b8590a4dabb18"
integrity sha512-IIB8jbiKy37zN8bAIHx59YmnIelY78CGHtThnibD/d3tQOKRY83bYVi9blwmZVUZh6l9nfkYH3tvReaiNxY9EQ==
"@vscode/emmet-helper@^2.8.8":
version "2.8.8"
resolved "https://registry.yarnpkg.com/@vscode/emmet-helper/-/emmet-helper-2.8.8.tgz#df64989d2812e031cd6393ce896a2fe33ae976bd"
integrity sha512-QuD4CmNeXSFxuP8VZwI6qL+8vmmd7JcSdwsEIdsrzb4YumWs/+4rXRX9MM+NsFfUO69g6ezngCD7XRd6jY9TQw==
dependencies:
emmet "^2.3.0"
emmet "^2.4.3"
jsonc-parser "^2.3.0"
vscode-languageserver-textdocument "^1.0.1"
vscode-languageserver-types "^3.15.1"
vscode-uri "^2.1.2"
emmet@^2.3.0:
version "2.3.6"
resolved "https://registry.yarnpkg.com/emmet/-/emmet-2.3.6.tgz#1d93c1ac03164da9ddf74864c1f341ed6ff6c336"
integrity sha512-pLS4PBPDdxuUAmw7Me7+TcHbykTsBKN/S9XJbUOMFQrNv9MoshzyMFK/R57JBm94/6HSL4vHnDeEmxlC82NQ4A==
emmet@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/emmet/-/emmet-2.4.3.tgz#c99f19e572a270da27f456dd7f65dfda83dc0ec1"
integrity sha512-Bq6zozVDVrLbBmKdosI9Q2DvrFh/ehwnNjgDRsvGVjPOEAhMKie9HwQnPuUi3NOZ2itVGyRwsLAdufnG9DVFwg==
dependencies:
"@emmetio/abbreviation" "^2.2.3"
"@emmetio/css-abbreviation" "^2.1.4"
"@emmetio/abbreviation" "^2.3.2"
"@emmetio/css-abbreviation" "^2.1.7"
image-size@~1.0.0:
version "1.0.0"
@ -114,9 +112,9 @@ vscode-languageserver-textdocument@^1.0.1:
integrity sha512-ynEGytvgTb6HVSUwPJIAZgiHQmPCx8bZ8w5um5Lz+q5DjP0Zj8wTFhQpyg8xaMvefDytw2+HH5yzqS+FhsR28A==
vscode-languageserver-types@^3.15.1:
version "3.17.2"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz#b2c2e7de405ad3d73a883e91989b850170ffc4f2"
integrity sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==
version "3.17.3"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64"
integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==
vscode-uri@^2.1.2:
version "2.1.2"

View file

@ -1681,17 +1681,17 @@
{
"command": "git.stageSelectedRanges",
"group": "2_git@1",
"when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
"when": "isInDiffRightEditor && !isEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
},
{
"command": "git.unstageSelectedRanges",
"group": "2_git@2",
"when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
"when": "isInDiffRightEditor && !isEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
},
{
"command": "git.revertSelectedRanges",
"group": "2_git@3",
"when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
"when": "isInDiffRightEditor && !isEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
}
],
"editor/content": [
@ -2073,6 +2073,12 @@
"markdownDescription": "%config.autofetchPeriod%",
"default": 180
},
"git.defaultBranchName": {
"type": "string",
"description": "%config.defaultBranchName%",
"default": "main",
"scope": "resource"
},
"git.branchPrefix": {
"type": "string",
"description": "%config.branchPrefix%",

View file

@ -131,6 +131,7 @@
"config.checkoutType.local": "Local branches",
"config.checkoutType.tags": "Tags",
"config.checkoutType.remote": "Remote branches",
"config.defaultBranchName": "The name of the default branch (ex: main, trunk, development) when initializing a new git repository. When set to empty, the default branch name configured in git will be used.",
"config.branchPrefix": "Prefix used when creating a new branch.",
"config.branchProtection": "List of protected branches. By default, a prompt is shown before changes are committed to a protected branch. The prompt can be controlled using the `#git.branchProtectionPrompt#` setting.",
"config.branchProtectionPrompt": "Controls whether a prompt is being shown before changes are committed to a protected branch.",

View file

@ -307,6 +307,10 @@ function getCheckoutProcessor(repository: Repository, type: string): CheckoutPro
return undefined;
}
function sanitizeBranchName(name: string, whitespaceChar: string): string {
return name.trim().replace(/^-+/, '').replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, whitespaceChar);
}
function sanitizeRemoteName(name: string) {
name = name.trim();
return name && name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, '-');
@ -772,7 +776,11 @@ export class CommandCenter {
}
}
await this.git.init(repositoryPath);
const config = workspace.getConfiguration('git');
const defaultBranchName = config.get<string>('defaultBranchName', 'main');
const branchWhitespaceChar = config.get<string>('branchWhitespaceChar', '-');
await this.git.init(repositoryPath, { defaultBranch: sanitizeBranchName(defaultBranchName, branchWhitespaceChar) });
let message = l10n.t('Would you like to open the initialized repository?');
const open = l10n.t('Open');
@ -2179,9 +2187,6 @@ export class CommandCenter {
const branchPrefix = config.get<string>('branchPrefix')!;
const branchWhitespaceChar = config.get<string>('branchWhitespaceChar')!;
const branchValidationRegex = config.get<string>('branchValidationRegex')!;
const sanitize = (name: string) => name ?
name.trim().replace(/^-+/, '').replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar)
: name;
let rawBranchName = defaultName;
@ -2206,7 +2211,7 @@ export class CommandCenter {
ignoreFocusOut: true,
validateInput: (name: string) => {
const validateName = new RegExp(branchValidationRegex);
const sanitizedName = sanitize(name);
const sanitizedName = sanitizeBranchName(name, branchWhitespaceChar);
if (validateName.test(sanitizedName)) {
// If the sanitized name that we will use is different than what is
// in the input box, show an info message to the user informing them
@ -2224,7 +2229,7 @@ export class CommandCenter {
});
}
return sanitize(rawBranchName || '');
return sanitizeBranchName(rawBranchName || '', branchWhitespaceChar);
}
private async _branch(repository: Repository, defaultName?: string, from = false): Promise<void> {

View file

@ -401,9 +401,14 @@ export class Git {
return new Repository(this, repository, dotGit, logger);
}
async init(repository: string): Promise<void> {
await this.exec(repository, ['init']);
return;
async init(repository: string, options: { defaultBranch?: string } = {}): Promise<void> {
const args = ['init'];
if (options.defaultBranch && options.defaultBranch !== '') {
args.push('-b', options.defaultBranch);
}
await this.exec(repository, args);
}
async clone(url: string, options: ICloneOptions, cancellationToken?: CancellationToken): Promise<string> {

View file

@ -45,8 +45,9 @@ function getImageMimeType(uri: vscode.Uri): string | undefined {
return imageExtToMime.get(extname(uri.fsPath).toLowerCase());
}
const id = 'insertAttachment';
class CopyPasteEditProvider implements vscode.DocumentPasteEditProvider {
class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscode.DocumentDropEditProvider {
private readonly id = 'insertAttachment';
async provideDocumentPasteEdits(
document: vscode.TextDocument,
@ -59,18 +60,16 @@ class CopyPasteEditProvider implements vscode.DocumentPasteEditProvider {
return;
}
const insert = await createInsertImageAttachmentEdit(document, dataTransfer, token);
const insert = await this.createInsertImageAttachmentEdit(document, dataTransfer, token);
if (!insert) {
return;
}
const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, id, vscode.l10n.t('Insert Image as Attachment'));
const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, this.id, vscode.l10n.t('Insert Image as Attachment'));
pasteEdit.priority = this.getPriority(dataTransfer);
pasteEdit.additionalEdit = insert.additionalEdit;
return pasteEdit;
}
}
class DropEditProvider implements vscode.DocumentDropEditProvider {
async provideDocumentDropEdits(
document: vscode.TextDocument,
@ -78,58 +77,69 @@ class DropEditProvider implements vscode.DocumentDropEditProvider {
dataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken,
): Promise<vscode.DocumentDropEdit | undefined> {
const insert = await createInsertImageAttachmentEdit(document, dataTransfer, token);
const insert = await this.createInsertImageAttachmentEdit(document, dataTransfer, token);
if (!insert) {
return;
}
const dropEdit = new vscode.DocumentDropEdit(insert.insertText);
dropEdit.id = id;
dropEdit.id = this.id;
dropEdit.priority = this.getPriority(dataTransfer);
dropEdit.additionalEdit = insert.additionalEdit;
dropEdit.label = vscode.l10n.t('Insert Image as Attachment');
return dropEdit;
}
}
async function createInsertImageAttachmentEdit(
document: vscode.TextDocument,
dataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken,
): Promise<{ insertText: vscode.SnippetString; additionalEdit: vscode.WorkspaceEdit } | undefined> {
const imageData = await getDroppedImageData(dataTransfer, token);
if (!imageData.length || token.isCancellationRequested) {
return;
}
const currentCell = getCellFromCellDocument(document);
if (!currentCell) {
return undefined;
}
// create updated metadata for cell (prep for WorkspaceEdit)
const newAttachment = buildAttachment(currentCell, imageData);
if (!newAttachment) {
return;
}
// build edits
const additionalEdit = new vscode.WorkspaceEdit();
const nbEdit = vscode.NotebookEdit.updateCellMetadata(currentCell.index, newAttachment.metadata);
const notebookUri = currentCell.notebook.uri;
additionalEdit.set(notebookUri, [nbEdit]);
// create a snippet for paste
const insertText = new vscode.SnippetString();
newAttachment.filenames.forEach((filename, i) => {
insertText.appendText('![');
insertText.appendPlaceholder(`${filename}`);
insertText.appendText(`](${/\s/.test(filename) ? `<attachment:${filename}>` : `attachment:${filename}`})`);
if (i !== newAttachment.filenames.length - 1) {
insertText.appendText(' ');
private getPriority(dataTransfer: vscode.DataTransfer): number {
if (dataTransfer.get('text/plain')) {
// Deprioritize in favor of normal text content
return -5;
}
});
return { insertText, additionalEdit };
// Otherwise boost priority so attachments are preferred
return 5;
}
private async createInsertImageAttachmentEdit(
document: vscode.TextDocument,
dataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken,
): Promise<{ insertText: vscode.SnippetString; additionalEdit: vscode.WorkspaceEdit } | undefined> {
const imageData = await getDroppedImageData(dataTransfer, token);
if (!imageData.length || token.isCancellationRequested) {
return;
}
const currentCell = getCellFromCellDocument(document);
if (!currentCell) {
return undefined;
}
// create updated metadata for cell (prep for WorkspaceEdit)
const newAttachment = buildAttachment(currentCell, imageData);
if (!newAttachment) {
return;
}
// build edits
const additionalEdit = new vscode.WorkspaceEdit();
const nbEdit = vscode.NotebookEdit.updateCellMetadata(currentCell.index, newAttachment.metadata);
const notebookUri = currentCell.notebook.uri;
additionalEdit.set(notebookUri, [nbEdit]);
// create a snippet for paste
const insertText = new vscode.SnippetString();
newAttachment.filenames.forEach((filename, i) => {
insertText.appendText('![');
insertText.appendPlaceholder(`${filename}`);
insertText.appendText(`](${/\s/.test(filename) ? `<attachment:${filename}>` : `attachment:${filename}`})`);
if (i !== newAttachment.filenames.length - 1) {
insertText.appendText(' ');
}
});
return { insertText, additionalEdit };
}
}
async function getDroppedImageData(
@ -296,14 +306,15 @@ function buildAttachment(
}
export function notebookImagePasteSetup(): vscode.Disposable {
const provider = new DropOrPasteEditProvider();
return vscode.Disposable.from(
vscode.languages.registerDocumentPasteEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, new CopyPasteEditProvider(), {
vscode.languages.registerDocumentPasteEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, {
pasteMimeTypes: [
MimeType.png,
MimeType.uriList,
],
}),
vscode.languages.registerDocumentDropEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, new DropEditProvider(), {
vscode.languages.registerDocumentDropEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, {
dropMimeTypes: [
...Object.values(imageExtToMime),
MimeType.uriList,

View file

@ -32,13 +32,19 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
return;
}
const edit = await this._makeCreateImagePasteEdit(document, dataTransfer, token);
if (edit) {
return edit;
const createEdit = await this._makeCreateImagePasteEdit(document, dataTransfer, token);
if (createEdit) {
return createEdit;
}
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
return snippet ? new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label) : undefined;
if (!snippet) {
return;
}
const uriEdit = new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label);
uriEdit.priority = this._getPriority(dataTransfer);
return uriEdit;
}
private async _makeCreateImagePasteEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentPasteEdit | undefined> {
@ -89,10 +95,19 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
return;
}
const pasteEdit = new vscode.DocumentPasteEdit(snippet.snippet, '', snippet.label);
const pasteEdit = new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label);
pasteEdit.additionalEdit = workspaceEdit;
pasteEdit.priority = this._getPriority(dataTransfer);
return pasteEdit;
}
private _getPriority(dataTransfer: vscode.DataTransfer): number {
if (dataTransfer.get('text/plain')) {
// Deprioritize in favor of normal text content
return -10;
}
return 0;
}
}
export function registerPasteSupport(selector: vscode.DocumentSelector,) {

View file

@ -6,10 +6,10 @@
const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f';
const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug');
const WIN_ABSOLUTE_PATH = /(?:[a-zA-Z]:(?:(?:\\|\/)[\w\.-]*)+)/;
const WIN_RELATIVE_PATH = /(?:(?:\~|\.)(?:(?:\\|\/)[\w\.-]*)+)/;
const WIN_ABSOLUTE_PATH = /(?<=^|\s)(?:[a-zA-Z]:(?:(?:\\|\/)[\w\.-]*)+)/;
const WIN_RELATIVE_PATH = /(?<=^|\s)(?:(?:\~|\.)(?:(?:\\|\/)[\w\.-]*)+)/;
const WIN_PATH = new RegExp(`(${WIN_ABSOLUTE_PATH.source}|${WIN_RELATIVE_PATH.source})`);
const POSIX_PATH = /((?:\~|\.)?(?:\/[\w\.-]*)+)/;
const POSIX_PATH = /(?<=^|\s)((?:\~|\.)?(?:\/[\w\.-]*)+)/;
const LINE_COLUMN = /(?:\:([\d]+))?(?:\:([\d]+))?/;
const isWindows = (typeof navigator !== 'undefined') ? navigator.userAgent && navigator.userAgent.indexOf('Windows') >= 0 : false;
const PATH_LINK_REGEX = new RegExp(`${isWindows ? WIN_PATH.source : POSIX_PATH.source}${LINE_COLUMN.source}`, 'g');

View file

@ -216,7 +216,7 @@ class MoveToFileRefactorCommand implements Command {
...destinationItems
], {
title: vscode.l10n.t("Move to File"),
placeHolder: vscode.l10n.t("Enter file path"),
placeHolder: vscode.l10n.t("Select move destination"),
});
if (!picked) {
return;

View file

@ -8,14 +8,14 @@ import { Iterable } from 'vs/base/common/iterator';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
interface IDataTransferFile {
export interface IDataTransferFile {
readonly id: string;
readonly name: string;
readonly uri?: URI;
data(): Promise<Uint8Array>;
}
export interface IDataTransferItem {
readonly id: string;
asString(): Thenable<string>;
asFile(): IDataTransferFile | undefined;
value: any;
@ -23,7 +23,6 @@ export interface IDataTransferItem {
export function createStringDataTransferItem(stringOrPromise: string | Promise<string>): IDataTransferItem {
return {
id: generateUuid(),
asString: async () => stringOrPromise,
asFile: () => undefined,
value: typeof stringOrPromise === 'string' ? stringOrPromise : undefined,
@ -31,36 +30,26 @@ export function createStringDataTransferItem(stringOrPromise: string | Promise<s
}
export function createFileDataTransferItem(fileName: string, uri: URI | undefined, data: () => Promise<Uint8Array>): IDataTransferItem {
const file = { id: generateUuid(), name: fileName, uri, data };
return {
id: generateUuid(),
asString: async () => '',
asFile: () => ({ name: fileName, uri, data }),
asFile: () => file,
value: undefined,
};
}
export class VSDataTransfer {
private readonly _entries = new Map<string, IDataTransferItem[]>();
export interface IReadonlyVSDataTransfer extends Iterable<readonly [string, IDataTransferItem]> {
/**
* Get the total number of entries in this data transfer.
*/
public get size(): number {
let size = 0;
this.forEach(() => size++);
return size;
}
get size(): number;
/**
* Check if this data transfer contains data for `mimeType`.
*
* This uses exact matching and does not support wildcards.
*/
public has(mimeType: string): boolean {
return this._entries.has(this.toKey(mimeType));
}
has(mimeType: string): boolean;
/**
* Check if this data transfer contains data matching `pattern`.
*
@ -68,20 +57,41 @@ export class VSDataTransfer {
*
* Use the special `files` mime type to match any file in the data transfer.
*/
matches(pattern: string): boolean;
/**
* Retrieve the first entry for `mimeType`.
*
* Note that if you want to find all entries for a given mime type, use {@link IReadonlyVSDataTransfer.entries} instead.
*/
get(mimeType: string): IDataTransferItem | undefined;
}
export class VSDataTransfer implements IReadonlyVSDataTransfer {
private readonly _entries = new Map<string, IDataTransferItem[]>();
public get size(): number {
let size = 0;
for (const _ of this._entries) {
size++;
}
return size;
}
public has(mimeType: string): boolean {
return this._entries.has(this.toKey(mimeType));
}
public matches(pattern: string): boolean {
const mimes = [...this._entries.keys()];
if (Iterable.some(this.values(), item => item.asFile())) {
if (Iterable.some(this, ([_, item]) => item.asFile())) {
mimes.push('files');
}
return matchesMimeType_normalized(normalizeMimeType(pattern), mimes);
}
/**
* Retrieve the first entry for `mimeType`.
*
* Note that if want to find all entries for a given mime type, use {@link VSDataTransfer.entries} instead.
*/
public get(mimeType: string): IDataTransferItem | undefined {
return this._entries.get(this.toKey(mimeType))?.[0];
}
@ -121,34 +131,14 @@ export class VSDataTransfer {
*
* There may be multiple entries for each mime type.
*/
public *entries(): Iterable<[string, IDataTransferItem]> {
for (const [mine, items] of this._entries.entries()) {
public *[Symbol.iterator](): IterableIterator<readonly [string, IDataTransferItem]> {
for (const [mine, items] of this._entries) {
for (const item of items) {
yield [mine, item];
}
}
}
/**
* Iterate over all items in this data transfer.
*
* There may be multiple entries for each mime type.
*/
public values(): Iterable<IDataTransferItem> {
return Array.from(this._entries.values()).flat();
}
/**
* Call `f` for each item and mime in the data transfer.
*
* There may be multiple entries for each mime type.
*/
public forEach(f: (value: IDataTransferItem, mime: string) => void) {
for (const [mime, item] of this.entries()) {
f(item, mime);
}
}
private toKey(mimeType: string): string {
return normalizeMimeType(mimeType);
}

View file

@ -290,6 +290,6 @@ export class BugIndicatingError extends Error {
// Because we know for sure only buggy code throws this,
// we definitely want to break here and fix the bug.
// eslint-disable-next-line no-debugger
debugger;
// debugger;
}
}

View file

@ -488,12 +488,6 @@ export interface IDiffEditorConstructionOptions extends IDiffEditorOptions {
* Aria label for modified editor.
*/
modifiedAriaLabel?: string;
/**
* Is the diff editor inside another editor
* Defaults to false
*/
isInEmbeddedEditor?: boolean;
}
/**

View file

@ -48,7 +48,7 @@ import { IEditorWhitespace, InlineDecoration, InlineDecorationType, IViewModel,
import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager';
import * as nls from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
@ -59,6 +59,7 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { getThemeTypeSelector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ThemeIcon } from 'vs/base/common/themables';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
export interface IDiffCodeEditorWidgetOptions {
originalEditor?: ICodeEditorWidgetOptions;
@ -239,6 +240,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
private readonly _reviewPane: DiffReview;
private isEmbeddedDiffEditorKey: IContextKey<boolean>;
constructor(
domElement: HTMLElement,
options: Readonly<editorBrowser.IDiffEditorConstructionOptions>,
@ -289,12 +292,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
accessibilityVerbose: false
});
if (typeof options.isInEmbeddedEditor !== 'undefined') {
this._contextKeyService.createKey('isInEmbeddedDiffEditor', options.isInEmbeddedEditor);
} else {
this._contextKeyService.createKey('isInEmbeddedDiffEditor', false);
}
this.isEmbeddedDiffEditorKey = EditorContextKeys.isEmbeddedDiffEditor.bindTo(this._contextKeyService);
this.isEmbeddedDiffEditorKey.set(typeof options.isInEmbeddedEditor !== 'undefined' ? options.isInEmbeddedEditor : false);
this._updateDecorationsRunner = this._register(new RunOnceScheduler(() => this._updateDecorations(), 0));
this._containerDomElement = document.createElement('div');
@ -777,6 +776,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
const changed = changedDiffEditorOptions(this._options, newOptions);
this._options = newOptions;
this.isEmbeddedDiffEditorKey.set(typeof _newOptions.isInEmbeddedEditor !== 'undefined' ? _newOptions.isInEmbeddedEditor : false);
const beginUpdateDecorations = (changed.ignoreTrimWhitespace || changed.renderIndicators || changed.renderMarginRevertIcon);
const beginUpdateDecorationsSoon = (this._isVisible && (changed.maxComputationTime || changed.maxFileSize));
this._documentDiffProvider.setOptions(newOptions);

View file

@ -800,6 +800,11 @@ export interface IDiffEditorBaseOptions {
* Configuration options for the diff editor.
*/
export interface IDiffEditorOptions extends IEditorOptions, IDiffEditorBaseOptions {
/**
* Is the diff editor inside another editor
* Defaults to false
*/
isInEmbeddedEditor?: boolean;
}
/**

View file

@ -26,6 +26,7 @@ export namespace EditorContextKeys {
export const readOnly = new RawContextKey<boolean>('editorReadonly', false, nls.localize('editorReadonly', "Whether the editor is read only"));
export const inDiffEditor = new RawContextKey<boolean>('inDiffEditor', false, nls.localize('inDiffEditor', "Whether the context is a diff editor"));
export const isEmbeddedDiffEditor = new RawContextKey<boolean>('isEmbeddedDiffEditor', false, nls.localize('isEmbeddedDiffEditor', "Whether the context is an embedded diff editor"));
export const columnSelection = new RawContextKey<boolean>('editorColumnSelection', false, nls.localize('editorColumnSelection', "Whether `editor.columnSelection` is enabled"));
export const writable = readOnly.toNegated();
export const hasNonEmptySelection = new RawContextKey<boolean>('editorHasSelection', false, nls.localize('editorHasSelection', "Whether the editor has text selected"));

View file

@ -7,7 +7,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { Color } from 'vs/base/common/color';
import { VSDataTransfer } from 'vs/base/common/dataTransfer';
import { IReadonlyVSDataTransfer } from 'vs/base/common/dataTransfer';
import { Event } from 'vs/base/common/event';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable } from 'vs/base/common/lifecycle';
@ -786,6 +786,7 @@ export interface DocumentPasteEdit {
readonly id: string;
readonly label: string;
readonly detail: string;
readonly priority: number;
insertText: string | { readonly snippet: string };
additionalEdit?: WorkspaceEdit;
}
@ -800,9 +801,9 @@ export interface DocumentPasteEditProvider {
readonly copyMimeTypes?: readonly string[];
readonly pasteMimeTypes: readonly string[];
prepareDocumentPaste?(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise<undefined | VSDataTransfer>;
prepareDocumentPaste?(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<undefined | IReadonlyVSDataTransfer>;
provideDocumentPasteEdits(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined>;
provideDocumentPasteEdits(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined>;
}
/**
@ -1948,6 +1949,7 @@ export enum ExternalUriOpenerPriority {
export interface DocumentOnDropEdit {
readonly id: string;
readonly label: string;
readonly priority: number;
insertText: string | { readonly snippet: string };
additionalEdit?: WorkspaceEdit;
}
@ -1958,5 +1960,5 @@ export interface DocumentOnDropEdit {
export interface DocumentOnDropEditProvider {
readonly dropMimeTypes?: readonly string[];
provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): ProviderResult<DocumentOnDropEdit>;
provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): ProviderResult<DocumentOnDropEdit>;
}

View file

@ -165,14 +165,18 @@ export class CopyPasteController extends Disposable implements IEditorContributi
});
const promise = createCancelablePromise(async token => {
const results = await Promise.all(providers.map(provider => {
const results = coalesce(await Promise.all(providers.map(provider => {
return provider.prepareDocumentPaste!(model, ranges, dataTransfer, token);
}));
})));
// Values from higher priority providers should overwrite values from lower priority ones.
// Reverse the array to so that the calls to `replace` below will do this
results.reverse();
for (const result of results) {
result?.forEach((value, key) => {
dataTransfer.replace(key, value);
});
for (const [mime, value] of result) {
dataTransfer.replace(mime, value);
}
}
return dataTransfer;
@ -368,9 +372,9 @@ export class CopyPasteController extends Disposable implements IEditorContributi
return;
}
toMergeDataTransfer.forEach((value, key) => {
for (const [key, value] of toMergeDataTransfer) {
dataTransfer.replace(key, value);
});
}
}
if (!dataTransfer.has(Mimes.uriList)) {
@ -391,6 +395,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
providers.map(provider => provider.provideDocumentPasteEdits(model, selections, dataTransfer, token))
).then(coalesce),
token);
result?.sort((a, b) => b.priority - a.priority);
return result ?? [];
}

View file

@ -5,7 +5,7 @@
import { coalesce } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { UriList, VSDataTransfer } from 'vs/base/common/dataTransfer';
import { IReadonlyVSDataTransfer, UriList } from 'vs/base/common/dataTransfer';
import { Disposable } from 'vs/base/common/lifecycle';
import { Mimes } from 'vs/base/common/mime';
import { Schemas } from 'vs/base/common/network';
@ -27,17 +27,17 @@ abstract class SimplePasteAndDropProvider implements DocumentOnDropEditProvider,
abstract readonly dropMimeTypes: readonly string[] | undefined;
abstract readonly pasteMimeTypes: readonly string[];
async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined> {
async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined> {
const edit = await this.getEdit(dataTransfer, token);
return edit ? { id: this.id, insertText: edit.insertText, label: edit.label, detail: edit.detail } : undefined;
return edit ? { id: this.id, insertText: edit.insertText, label: edit.label, detail: edit.detail, priority: edit.priority } : undefined;
}
async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<DocumentOnDropEdit | undefined> {
async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<DocumentOnDropEdit | undefined> {
const edit = await this.getEdit(dataTransfer, token);
return edit ? { id: this.id, insertText: edit.insertText, label: edit.label } : undefined;
return edit ? { id: this.id, insertText: edit.insertText, label: edit.label, priority: edit.priority } : undefined;
}
protected abstract getEdit(dataTransfer: VSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined>;
protected abstract getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined>;
}
class DefaultTextProvider extends SimplePasteAndDropProvider {
@ -46,7 +46,7 @@ class DefaultTextProvider extends SimplePasteAndDropProvider {
readonly dropMimeTypes = [Mimes.text];
readonly pasteMimeTypes = [Mimes.text];
protected async getEdit(dataTransfer: VSDataTransfer, _token: CancellationToken) {
protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, _token: CancellationToken) {
const textEntry = dataTransfer.get(Mimes.text);
if (!textEntry) {
return;
@ -61,6 +61,7 @@ class DefaultTextProvider extends SimplePasteAndDropProvider {
const insertText = await textEntry.asString();
return {
id: this.id,
priority: 0,
label: localize('text.label', "Insert Plain Text"),
detail: builtInLabel,
insertText
@ -74,7 +75,7 @@ class PathProvider extends SimplePasteAndDropProvider {
readonly dropMimeTypes = [Mimes.uriList];
readonly pasteMimeTypes = [Mimes.uriList];
protected async getEdit(dataTransfer: VSDataTransfer, token: CancellationToken) {
protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken) {
const entries = await extractUriList(dataTransfer);
if (!entries.length || token.isCancellationRequested) {
return;
@ -107,6 +108,7 @@ class PathProvider extends SimplePasteAndDropProvider {
return {
id: this.id,
priority: 0,
insertText,
label,
detail: builtInLabel,
@ -126,7 +128,7 @@ class RelativePathProvider extends SimplePasteAndDropProvider {
super();
}
protected async getEdit(dataTransfer: VSDataTransfer, token: CancellationToken) {
protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken) {
const entries = await extractUriList(dataTransfer);
if (!entries.length || token.isCancellationRequested) {
return;
@ -143,6 +145,7 @@ class RelativePathProvider extends SimplePasteAndDropProvider {
return {
id: this.id,
priority: 0,
insertText: relativeUris.join(' '),
label: entries.length > 1
? localize('defaultDropProvider.uriList.relativePaths', "Insert Relative Paths")
@ -152,7 +155,7 @@ class RelativePathProvider extends SimplePasteAndDropProvider {
}
}
async function extractUriList(dataTransfer: VSDataTransfer): Promise<{ readonly uri: URI; readonly originalText: string }[]> {
async function extractUriList(dataTransfer: IReadonlyVSDataTransfer): Promise<{ readonly uri: URI; readonly originalText: string }[]> {
const urlListEntry = dataTransfer.get(Mimes.uriList);
if (!urlListEntry) {
return [];

View file

@ -13,6 +13,8 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { IPosition } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { DocumentOnDropEditProvider } from 'vs/editor/common/languages';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd';
import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService';
@ -99,19 +101,15 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
return provider.dropMimeTypes.some(mime => ourDataTransfer.matches(mime));
});
const possibleDropEdits = await raceCancellation(Promise.all(providers.map(provider => {
return provider.provideDocumentOnDropEdits(model, position, ourDataTransfer, tokenSource.token);
})), tokenSource.token);
const edits = await this.getDropEdits(providers, model, position, ourDataTransfer, tokenSource);
if (tokenSource.token.isCancellationRequested) {
return;
}
if (possibleDropEdits) {
const allEdits = coalesce(possibleDropEdits);
// Pass in the parent token here as it tracks cancelling the entire drop operation.
if (edits.length) {
const canShowWidget = editor.getOption(EditorOption.dropIntoEditor).showDropSelector === 'afterDrop';
await this._postDropWidgetManager.applyEditAndShowIfNeeded([Range.fromPositions(position)], { activeEditIndex: 0, allEdits }, canShowWidget, token);
// Pass in the parent token here as it tracks cancelling the entire drop operation
await this._postDropWidgetManager.applyEditAndShowIfNeeded([Range.fromPositions(position)], { activeEditIndex: 0, allEdits: edits }, canShowWidget, token);
}
} finally {
tokenSource.dispose();
@ -125,6 +123,15 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
this._currentOperation = p;
}
private async getDropEdits(providers: DocumentOnDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, tokenSource: EditorStateCancellationTokenSource) {
const results = await raceCancellation(Promise.all(providers.map(provider => {
return provider.provideDocumentOnDropEdits(model, position, dataTransfer, tokenSource.token);
})), tokenSource.token);
const edits = coalesce(results ?? []);
edits.sort((a, b) => b.priority - a.priority);
return edits;
}
private async extractDataTransferData(dragEvent: DragEvent): Promise<VSDataTransfer> {
if (!dragEvent.dataTransfer) {
return new VSDataTransfer();
@ -138,7 +145,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
for (const id of data) {
const treeDataTransfer = await this._treeViewsDragAndDropService.removeDragOperationTransfer(id.identifier);
if (treeDataTransfer) {
for (const [type, value] of treeDataTransfer.entries()) {
for (const [type, value] of treeDataTransfer) {
dataTransfer.replace(type, value);
}
}

View file

@ -19,6 +19,7 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua
import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce';
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
suite('Sticky Scroll Tests', () => {
@ -110,120 +111,124 @@ suite('Sticky Scroll Tests', () => {
};
}
test('Testing the function getCandidateStickyLinesIntersecting', async () => {
const model = createTextModel(text);
await withAsyncTestCodeEditor(model, {
stickyScroll: {
enabled: true,
maxLineCount: 5,
defaultModel: 'outlineModel'
}, serviceCollection: serviceCollection
}, async (editor, _viewModel, instantiationService) => {
const languageService = instantiationService.get(ILanguageFeaturesService);
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
languageService.documentSymbolProvider.register('*', documentSymbolProviderForTestModel());
const provider: StickyLineCandidateProvider = new StickyLineCandidateProvider(editor, languageService, languageConfigurationService);
await provider.update();
assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 1, endLineNumber: 4 }), [new StickyLineCandidate(1, 2, 1)]);
assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 8, endLineNumber: 10 }), [new StickyLineCandidate(7, 11, 1), new StickyLineCandidate(9, 11, 2), new StickyLineCandidate(10, 10, 3)]);
assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 10, endLineNumber: 13 }), [new StickyLineCandidate(7, 11, 1), new StickyLineCandidate(9, 11, 2), new StickyLineCandidate(10, 10, 3)]);
test('Testing the function getCandidateStickyLinesIntersecting', () => {
return runWithFakedTimers({ useFakeTimers: true }, async () => {
const model = createTextModel(text);
await withAsyncTestCodeEditor(model, {
stickyScroll: {
enabled: true,
maxLineCount: 5,
defaultModel: 'outlineModel'
}, serviceCollection: serviceCollection
}, async (editor, _viewModel, instantiationService) => {
const languageService = instantiationService.get(ILanguageFeaturesService);
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
languageService.documentSymbolProvider.register('*', documentSymbolProviderForTestModel());
const provider: StickyLineCandidateProvider = new StickyLineCandidateProvider(editor, languageService, languageConfigurationService);
await provider.update();
assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 1, endLineNumber: 4 }), [new StickyLineCandidate(1, 2, 1)]);
assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 8, endLineNumber: 10 }), [new StickyLineCandidate(7, 11, 1), new StickyLineCandidate(9, 11, 2), new StickyLineCandidate(10, 10, 3)]);
assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 10, endLineNumber: 13 }), [new StickyLineCandidate(7, 11, 1), new StickyLineCandidate(9, 11, 2), new StickyLineCandidate(10, 10, 3)]);
provider.dispose();
model.dispose();
provider.dispose();
model.dispose();
});
});
});
test('issue #157180: Render the correct line corresponding to the scope definition', async () => {
test('issue #157180: Render the correct line corresponding to the scope definition', () => {
return runWithFakedTimers({ useFakeTimers: true }, async () => {
const model = createTextModel(text);
await withAsyncTestCodeEditor(model, {
stickyScroll: {
enabled: true,
maxLineCount: 5,
defaultModel: 'outlineModel'
}, serviceCollection
}, async (editor, _viewModel, instantiationService) => {
const model = createTextModel(text);
await withAsyncTestCodeEditor(model, {
stickyScroll: {
enabled: true,
maxLineCount: 5,
defaultModel: 'outlineModel'
}, serviceCollection
}, async (editor, _viewModel, instantiationService) => {
const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController);
const lineHeight: number = editor.getOption(EditorOption.lineHeight);
const languageService: ILanguageFeaturesService = instantiationService.get(ILanguageFeaturesService);
languageService.documentSymbolProvider.register('*', documentSymbolProviderForTestModel());
await stickyScrollController.stickyScrollCandidateProvider.update();
let state;
const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController);
const lineHeight: number = editor.getOption(EditorOption.lineHeight);
const languageService: ILanguageFeaturesService = instantiationService.get(ILanguageFeaturesService);
languageService.documentSymbolProvider.register('*', documentSymbolProviderForTestModel());
await stickyScrollController.stickyScrollCandidateProvider.update();
let state;
editor.setScrollTop(1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1]);
editor.setScrollTop(1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1]);
editor.setScrollTop(lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1]);
editor.setScrollTop(lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1]);
editor.setScrollTop(4 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, []);
editor.setScrollTop(4 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, []);
editor.setScrollTop(8 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [7, 9]);
editor.setScrollTop(8 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [7, 9]);
editor.setScrollTop(9 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [7, 9]);
editor.setScrollTop(9 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [7, 9]);
editor.setScrollTop(10 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [7]);
editor.setScrollTop(10 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [7]);
stickyScrollController.dispose();
stickyScrollController.stickyScrollCandidateProvider.dispose();
model.dispose();
stickyScrollController.dispose();
stickyScrollController.stickyScrollCandidateProvider.dispose();
model.dispose();
});
});
});
test('issue #156268 : Do not reveal sticky lines when they are in a folded region ', async () => {
test('issue #156268 : Do not reveal sticky lines when they are in a folded region ', () => {
return runWithFakedTimers({ useFakeTimers: true }, async () => {
const model = createTextModel(text);
await withAsyncTestCodeEditor(model, {
stickyScroll: {
enabled: true,
maxLineCount: 5,
defaultModel: 'outlineModel'
}, serviceCollection
}, async (editor, viewModel, instantiationService) => {
const model = createTextModel(text);
await withAsyncTestCodeEditor(model, {
stickyScroll: {
enabled: true,
maxLineCount: 5,
defaultModel: 'outlineModel'
}, serviceCollection
}, async (editor, viewModel, instantiationService) => {
const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController);
const lineHeight = editor.getOption(EditorOption.lineHeight);
const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController);
const lineHeight = editor.getOption(EditorOption.lineHeight);
const languageService = instantiationService.get(ILanguageFeaturesService);
languageService.documentSymbolProvider.register('*', documentSymbolProviderForTestModel());
await stickyScrollController.stickyScrollCandidateProvider.update();
editor.setHiddenAreas([{ startLineNumber: 2, endLineNumber: 2, startColumn: 1, endColumn: 1 }, { startLineNumber: 10, endLineNumber: 11, startColumn: 1, endColumn: 1 }]);
let state;
const languageService = instantiationService.get(ILanguageFeaturesService);
languageService.documentSymbolProvider.register('*', documentSymbolProviderForTestModel());
await stickyScrollController.stickyScrollCandidateProvider.update();
editor.setHiddenAreas([{ startLineNumber: 2, endLineNumber: 2, startColumn: 1, endColumn: 1 }, { startLineNumber: 10, endLineNumber: 11, startColumn: 1, endColumn: 1 }]);
let state;
editor.setScrollTop(1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1]);
editor.setScrollTop(1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1]);
editor.setScrollTop(lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, []);
editor.setScrollTop(lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, []);
editor.setScrollTop(6 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [7, 9]);
editor.setScrollTop(6 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [7, 9]);
editor.setScrollTop(7 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [7]);
editor.setScrollTop(7 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [7]);
editor.setScrollTop(10 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, []);
editor.setScrollTop(10 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, []);
stickyScrollController.dispose();
stickyScrollController.stickyScrollCandidateProvider.dispose();
model.dispose();
stickyScrollController.dispose();
stickyScrollController.stickyScrollCandidateProvider.dispose();
model.dispose();
});
});
});
@ -274,49 +279,50 @@ suite('Sticky Scroll Tests', () => {
};
}
test('issue #159271 : render the correct widget state when the child scope starts on the same line as the parent scope', async () => {
test('issue #159271 : render the correct widget state when the child scope starts on the same line as the parent scope', () => {
return runWithFakedTimers({ useFakeTimers: true }, async () => {
const model = createTextModel(textWithScopesWithSameStartingLines);
await withAsyncTestCodeEditor(model, {
stickyScroll: {
enabled: true,
maxLineCount: 5,
defaultModel: 'outlineModel'
}, serviceCollection
}, async (editor, _viewModel, instantiationService) => {
const model = createTextModel(textWithScopesWithSameStartingLines);
await withAsyncTestCodeEditor(model, {
stickyScroll: {
enabled: true,
maxLineCount: 5,
defaultModel: 'outlineModel'
}, serviceCollection
}, async (editor, _viewModel, instantiationService) => {
const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController);
await stickyScrollController.stickyScrollCandidateProvider.update();
const lineHeight = editor.getOption(EditorOption.lineHeight);
const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController);
await stickyScrollController.stickyScrollCandidateProvider.update();
const lineHeight = editor.getOption(EditorOption.lineHeight);
const languageService = instantiationService.get(ILanguageFeaturesService);
languageService.documentSymbolProvider.register('*', documentSymbolProviderForSecondTestModel());
await stickyScrollController.stickyScrollCandidateProvider.update();
let state;
const languageService = instantiationService.get(ILanguageFeaturesService);
languageService.documentSymbolProvider.register('*', documentSymbolProviderForSecondTestModel());
await stickyScrollController.stickyScrollCandidateProvider.update();
let state;
editor.setScrollTop(1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1, 2]);
editor.setScrollTop(1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1, 2]);
editor.setScrollTop(lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1, 2]);
editor.setScrollTop(lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1, 2]);
editor.setScrollTop(2 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1]);
editor.setScrollTop(2 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1]);
editor.setScrollTop(3 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1]);
editor.setScrollTop(3 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, [1]);
editor.setScrollTop(4 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, []);
editor.setScrollTop(4 * lineHeight + 1);
state = stickyScrollController.findScrollWidgetState();
assert.deepStrictEqual(state.lineNumbers, []);
stickyScrollController.dispose();
stickyScrollController.stickyScrollCandidateProvider.dispose();
model.dispose();
stickyScrollController.dispose();
stickyScrollController.stickyScrollCandidateProvider.dispose();
model.dispose();
});
});
});
});

10
src/vs/monaco.d.ts vendored
View file

@ -3866,6 +3866,11 @@ declare namespace monaco.editor {
* Configuration options for the diff editor.
*/
export interface IDiffEditorOptions extends IEditorOptions, IDiffEditorBaseOptions {
/**
* Is the diff editor inside another editor
* Defaults to false
*/
isInEmbeddedEditor?: boolean;
}
/**
@ -5502,11 +5507,6 @@ declare namespace monaco.editor {
* Aria label for modified editor.
*/
modifiedAriaLabel?: string;
/**
* Is the diff editor inside another editor
* Defaults to false
*/
isInEmbeddedEditor?: boolean;
}
/**

View file

@ -190,7 +190,7 @@ suite('AbstractKeybindingService', () => {
statusMessageCallsDisposed = null;
});
function kbItem(keybinding: number, command: string, when?: ContextKeyExpression): ResolvedKeybindingItem {
function kbItem(keybinding: number | number[], command: string | null, when?: ContextKeyExpression): ResolvedKeybindingItem {
return new ResolvedKeybindingItem(
createUSLayoutResolvedKeybinding(keybinding, OS),
command,
@ -206,6 +206,155 @@ suite('AbstractKeybindingService', () => {
return createUSLayoutResolvedKeybinding(keybinding, OS)!.getLabel()!;
}
suite('simple tests: single- and multi-chord keybindings are dispatched', () => {
test('a single-chord keybinding is dispatched correctly; this test makes sure the dispatch in general works before we test empty-string/null command ID', () => {
const key = KeyMod.CtrlCmd | KeyCode.KeyK;
const kbService = createTestKeybindingService([
kbItem(key, 'myCommand'),
]);
currentContextValue = createContext({});
const shouldPreventDefault = kbService.testDispatch(key);
assert.deepStrictEqual(shouldPreventDefault, true);
assert.deepStrictEqual(executeCommandCalls, ([{ commandId: "myCommand", args: [null] }]));
assert.deepStrictEqual(showMessageCalls, []);
assert.deepStrictEqual(statusMessageCalls, []);
assert.deepStrictEqual(statusMessageCallsDisposed, []);
kbService.dispose();
});
test('a multi-chord keybinding is dispatched correctly', () => {
const chord0 = KeyMod.CtrlCmd | KeyCode.KeyK;
const chord1 = KeyMod.CtrlCmd | KeyCode.KeyI;
const key = [chord0, chord1];
const kbService = createTestKeybindingService([
kbItem(key, 'myCommand'),
]);
currentContextValue = createContext({});
let shouldPreventDefault = kbService.testDispatch(chord0);
assert.deepStrictEqual(shouldPreventDefault, true);
assert.deepStrictEqual(executeCommandCalls, []);
assert.deepStrictEqual(showMessageCalls, []);
assert.deepStrictEqual(statusMessageCalls, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`]));
assert.deepStrictEqual(statusMessageCallsDisposed, []);
shouldPreventDefault = kbService.testDispatch(chord1);
assert.deepStrictEqual(shouldPreventDefault, true);
assert.deepStrictEqual(executeCommandCalls, ([{ commandId: "myCommand", args: [null] }]));
assert.deepStrictEqual(showMessageCalls, []);
assert.deepStrictEqual(statusMessageCalls, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`]));
assert.deepStrictEqual(statusMessageCallsDisposed, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`]));
kbService.dispose();
});
});
suite('keybindings with empty-string/null command ID', () => {
test('a single-chord keybinding with an empty string command ID unbinds the keybinding (shouldPreventDefault = false)', () => {
const kbService = createTestKeybindingService([
kbItem(KeyMod.CtrlCmd | KeyCode.KeyK, 'myCommand'),
kbItem(KeyMod.CtrlCmd | KeyCode.KeyK, ''),
]);
// send Ctrl/Cmd + K
currentContextValue = createContext({});
const shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KeyK);
assert.deepStrictEqual(shouldPreventDefault, false);
assert.deepStrictEqual(executeCommandCalls, []);
assert.deepStrictEqual(showMessageCalls, []);
assert.deepStrictEqual(statusMessageCalls, []);
assert.deepStrictEqual(statusMessageCallsDisposed, []);
kbService.dispose();
});
test('a single-chord keybinding with a null command ID unbinds the keybinding (shouldPreventDefault = false)', () => {
const kbService = createTestKeybindingService([
kbItem(KeyMod.CtrlCmd | KeyCode.KeyK, 'myCommand'),
kbItem(KeyMod.CtrlCmd | KeyCode.KeyK, null),
]);
// send Ctrl/Cmd + K
currentContextValue = createContext({});
const shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KeyK);
assert.deepStrictEqual(shouldPreventDefault, false);
assert.deepStrictEqual(executeCommandCalls, []);
assert.deepStrictEqual(showMessageCalls, []);
assert.deepStrictEqual(statusMessageCalls, []);
assert.deepStrictEqual(statusMessageCallsDisposed, []);
kbService.dispose();
});
test('a multi-chord keybinding with an empty-string command ID keeps the keybinding (shouldPreventDefault = true)', () => {
const chord0 = KeyMod.CtrlCmd | KeyCode.KeyK;
const chord1 = KeyMod.CtrlCmd | KeyCode.KeyI;
const key = [chord0, chord1];
const kbService = createTestKeybindingService([
kbItem(key, 'myCommand'),
kbItem(key, ''),
]);
currentContextValue = createContext({});
let shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KeyK);
assert.deepStrictEqual(shouldPreventDefault, true);
assert.deepStrictEqual(executeCommandCalls, []);
assert.deepStrictEqual(showMessageCalls, []);
assert.deepStrictEqual(statusMessageCalls, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`]));
assert.deepStrictEqual(statusMessageCallsDisposed, []);
shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KeyI);
assert.deepStrictEqual(shouldPreventDefault, true);
assert.deepStrictEqual(executeCommandCalls, []);
assert.deepStrictEqual(showMessageCalls, []);
assert.deepStrictEqual(statusMessageCalls, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`, `The key combination (${toUsLabel(chord0)}, ${toUsLabel(chord1)}) is not a command.`]));
assert.deepStrictEqual(statusMessageCallsDisposed, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`]));
kbService.dispose();
});
test('a multi-chord keybinding with a null command ID keeps the keybinding (shouldPreventDefault = true)', () => {
const chord0 = KeyMod.CtrlCmd | KeyCode.KeyK;
const chord1 = KeyMod.CtrlCmd | KeyCode.KeyI;
const key = [chord0, chord1];
const kbService = createTestKeybindingService([
kbItem(key, 'myCommand'),
kbItem(key, null),
]);
currentContextValue = createContext({});
let shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KeyK);
assert.deepStrictEqual(shouldPreventDefault, true);
assert.deepStrictEqual(executeCommandCalls, []);
assert.deepStrictEqual(showMessageCalls, []);
assert.deepStrictEqual(statusMessageCalls, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`]));
assert.deepStrictEqual(statusMessageCallsDisposed, []);
shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KeyI);
assert.deepStrictEqual(shouldPreventDefault, true);
assert.deepStrictEqual(executeCommandCalls, []);
assert.deepStrictEqual(showMessageCalls, []);
assert.deepStrictEqual(statusMessageCalls, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`, `The key combination (${toUsLabel(chord0)}, ${toUsLabel(chord1)}) is not a command.`]));
assert.deepStrictEqual(statusMessageCallsDisposed, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`]));
kbService.dispose();
});
});
test('issue #16498: chord mode is quit for invalid chords', () => {
const kbService = createTestKeybindingService([

View file

@ -316,8 +316,11 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
}
// Pass the sequence along to the capability
const [command, ...args] = data.split(';');
switch (command) {
const argsIndex = data.indexOf(';');
const sequenceCommand = argsIndex === -1 ? data : data.substring(0, argsIndex);
// Cast to strict checked index access
const args: (string | undefined)[] = argsIndex === -1 ? [] : data.substring(argsIndex + 1).split(';');
switch (sequenceCommand) {
case VSCodeOscPt.PromptStart:
this._createOrGetCommandDetection(this._terminal).handlePromptStart();
return true;
@ -328,18 +331,21 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
this._createOrGetCommandDetection(this._terminal).handleCommandExecuted();
return true;
case VSCodeOscPt.CommandFinished: {
const exitCode = args.length === 1 ? parseInt(args[0]) : undefined;
const arg0 = args[0];
const exitCode = arg0 !== undefined ? parseInt(arg0) : undefined;
this._createOrGetCommandDetection(this._terminal).handleCommandFinished(exitCode);
return true;
}
case VSCodeOscPt.CommandLine: {
const arg0 = args[0];
const arg1 = args[1];
let commandLine: string;
if (args.length >= 1 || args.length <= 2) {
commandLine = deserializeMessage(args[0]);
if (arg0 !== undefined) {
commandLine = deserializeMessage(arg0);
} else {
commandLine = '';
}
this._createOrGetCommandDetection(this._terminal).setCommandLine(commandLine, args[1] === this._nonce);
this._createOrGetCommandDetection(this._terminal).setCommandLine(commandLine, arg1 === this._nonce);
return true;
}
case VSCodeOscPt.ContinuationStart: {
@ -359,7 +365,8 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
return true;
}
case VSCodeOscPt.Property: {
const deserialized = args.length ? deserializeMessage(args[0]) : '';
const arg0 = args[0];
const deserialized = arg0 !== undefined ? deserializeMessage(arg0) : '';
const { key, value } = parseKeyValueAssignment(deserialized);
if (value === undefined) {
return true;
@ -539,10 +546,14 @@ export function parseKeyValueAssignment(message: string): { key: string; value:
}
export function parseMarkSequence(sequence: string[]): { id?: string; hidden?: boolean } {
export function parseMarkSequence(sequence: (string | undefined)[]): { id?: string; hidden?: boolean } {
let id = undefined;
let hidden = false;
for (const property of sequence) {
// Sanity check, this shouldn't happen in practice
if (property === undefined) {
continue;
}
if (property === 'Hidden') {
hidden = true;
}

View file

@ -53,7 +53,7 @@ export class Win32UpdateService extends AbstractUpdateService {
@memoize
get cachePath(): Promise<string> {
const result = path.join(tmpdir(), `vscode-update-${this.productService.target}-${process.arch}`);
const result = path.join(tmpdir(), `vscode-${this.productService.quality}-${this.productService.target}-${process.arch}`);
return pfs.Promises.mkdir(result, { recursive: true }).then(() => result);
}

View file

@ -5,7 +5,7 @@
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { createStringDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer';
import { createStringDataTransferItem, IReadonlyVSDataTransfer, VSDataTransfer } from 'vs/base/common/dataTransfer';
import { CancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable, Disposable, DisposableMap, toDisposable } from 'vs/base/common/lifecycle';
@ -27,7 +27,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { reviveWorkspaceEditDto } from 'vs/workbench/api/browser/mainThreadBulkEdits';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import { DataTransferCache } from 'vs/workbench/api/common/shared/dataTransferCache';
import { DataTransferFileCache } from 'vs/workbench/api/common/shared/dataTransferCache';
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import * as search from 'vs/workbench/contrib/search/common/search';
import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';
@ -927,12 +927,12 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider {
private readonly dataTransfers = new DataTransferCache();
private readonly dataTransfers = new DataTransferFileCache();
public readonly copyMimeTypes?: readonly string[];
public readonly pasteMimeTypes: readonly string[];
readonly prepareDocumentPaste?: (model: ITextModel, ranges: readonly IRange[], dataTransfer: VSDataTransfer, token: CancellationToken) => Promise<undefined | VSDataTransfer>;
readonly prepareDocumentPaste?: languages.DocumentPasteEditProvider['prepareDocumentPaste'];
constructor(
private readonly handle: number,
@ -944,40 +944,41 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider
this.pasteMimeTypes = metadata.pasteMimeTypes;
if (metadata.supportsCopy) {
this.prepareDocumentPaste = async (model: ITextModel, selections: readonly IRange[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise<VSDataTransfer | undefined> => {
const dataTransferDto = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer);
this.prepareDocumentPaste = async (model: ITextModel, selections: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<IReadonlyVSDataTransfer | undefined> => {
const dataTransferDto = await typeConvert.DataTransfer.from(dataTransfer);
if (token.isCancellationRequested) {
return undefined;
}
const result = await this._proxy.$prepareDocumentPaste(handle, model.uri, selections, dataTransferDto, token);
if (!result) {
const newDataTransfer = await this._proxy.$prepareDocumentPaste(handle, model.uri, selections, dataTransferDto, token);
if (!newDataTransfer) {
return undefined;
}
const dataTransferOut = new VSDataTransfer();
result.items.forEach(([type, item]) => {
for (const [type, item] of newDataTransfer.items) {
dataTransferOut.replace(type, createStringDataTransferItem(item.asString));
});
}
return dataTransferOut;
};
}
}
async provideDocumentPasteEdits(model: ITextModel, selections: Selection[], dataTransfer: VSDataTransfer, token: CancellationToken) {
async provideDocumentPasteEdits(model: ITextModel, selections: Selection[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken) {
const request = this.dataTransfers.add(dataTransfer);
try {
const d = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer);
const result = await this._proxy.$providePasteEdits(this.handle, request.id, model.uri, selections, d, token);
const dataTransferDto = await typeConvert.DataTransfer.from(dataTransfer);
if (token.isCancellationRequested) {
return;
}
const result = await this._proxy.$providePasteEdits(this.handle, request.id, model.uri, selections, dataTransferDto, token);
if (!result) {
return undefined;
return;
}
return {
id: result.id,
label: result.label,
detail: result.detail,
insertText: result.insertText,
...result,
additionalEdit: result.additionalEdit ? reviveWorkspaceEditDto(result.additionalEdit, this._uriIdentService, dataId => this.resolveFileData(request.id, dataId)) : undefined,
};
} finally {
@ -992,7 +993,7 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider
class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEditProvider {
private readonly dataTransfers = new DataTransferCache();
private readonly dataTransfers = new DataTransferFileCache();
readonly dropMimeTypes?: readonly string[];
@ -1005,18 +1006,21 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd
this.dropMimeTypes = metadata?.dropMimeTypes ?? ['*/*'];
}
async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<languages.DocumentOnDropEdit | null | undefined> {
async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<languages.DocumentOnDropEdit | null | undefined> {
const request = this.dataTransfers.add(dataTransfer);
try {
const dataTransferDto = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer);
const dataTransferDto = await typeConvert.DataTransfer.from(dataTransfer);
if (token.isCancellationRequested) {
return;
}
const edit = await this._proxy.$provideDocumentOnDropEdits(this.handle, request.id, model.uri, position, dataTransferDto, token);
if (!edit) {
return undefined;
return;
}
return {
id: edit.id,
label: edit.label,
insertText: edit.insertText,
...edit,
additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService, dataId => this.resolveDocumentOnDropFileData(request.id, dataId)),
};
} finally {

View file

@ -16,7 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { createStringDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer';
import { VSBuffer } from 'vs/base/common/buffer';
import { DataTransferCache } from 'vs/workbench/api/common/shared/dataTransferCache';
import { DataTransferFileCache } from 'vs/workbench/api/common/shared/dataTransferCache';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
@extHostNamedCustomer(MainContext.MainThreadTreeViews)
@ -210,7 +210,7 @@ type TreeItemHandle = string;
class TreeViewDragAndDropController implements ITreeViewDragAndDropController {
private readonly dataTransfersCache = new DataTransferCache();
private readonly dataTransfersCache = new DataTransferFileCache();
constructor(private readonly treeViewId: string,
readonly dropMimeTypes: string[],
@ -222,7 +222,11 @@ class TreeViewDragAndDropController implements ITreeViewDragAndDropController {
operationUuid?: string, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise<void> {
const request = this.dataTransfersCache.add(dataTransfer);
try {
return await this._proxy.$handleDrop(this.treeViewId, request.id, await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer), targetTreeItem?.handle, token, operationUuid, sourceTreeId, sourceTreeItemHandles);
const dataTransferDto = await typeConvert.DataTransfer.from(dataTransfer);
if (token.isCancellationRequested) {
return;
}
return await this._proxy.$handleDrop(this.treeViewId, request.id, dataTransferDto, targetTreeItem?.handle, token, operationUuid, sourceTreeId, sourceTreeItemHandles);
} finally {
request.dispose();
}

View file

@ -191,23 +191,27 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
}
public $disposeWebview(handle: extHostProtocol.WebviewHandle): void {
const webview = this.getWebviewInput(handle);
const webview = this.tryGetWebviewInput(handle);
if (!webview) {
return;
}
webview.dispose();
}
public $setTitle(handle: extHostProtocol.WebviewHandle, value: string): void {
const webview = this.getWebviewInput(handle);
webview.setName(value);
this.tryGetWebviewInput(handle)?.setName(value);
}
public $setIconPath(handle: extHostProtocol.WebviewHandle, value: extHostProtocol.IWebviewIconPath | undefined): void {
const webview = this.getWebviewInput(handle);
webview.iconPath = reviveWebviewIcon(value);
const webview = this.tryGetWebviewInput(handle);
if (webview) {
webview.iconPath = reviveWebviewIcon(value);
}
}
public $reveal(handle: extHostProtocol.WebviewHandle, showOptions: extHostProtocol.WebviewPanelShowOptions): void {
const webview = this.getWebviewInput(handle);
if (webview.isDisposed()) {
const webview = this.tryGetWebviewInput(handle);
if (!webview || webview.isDisposed()) {
return;
}
@ -342,14 +346,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
}
}
private getWebviewInput(handle: extHostProtocol.WebviewHandle): WebviewInput {
const webview = this.tryGetWebviewInput(handle);
if (!webview) {
throw new Error(`Unknown webview handle:${handle}`);
}
return webview;
}
private tryGetWebviewInput(handle: extHostProtocol.WebviewHandle): WebviewInput | undefined {
return this._webviewInputs.getInputForHandle(handle);
}

View file

@ -53,17 +53,21 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
}
public $setHtml(handle: extHostProtocol.WebviewHandle, value: string): void {
const webview = this.getWebview(handle);
webview.setHtml(value);
this.tryGetWebview(handle)?.setHtml(value);
}
public $setOptions(handle: extHostProtocol.WebviewHandle, options: extHostProtocol.IWebviewContentOptions): void {
const webview = this.getWebview(handle);
webview.contentOptions = reviveWebviewContentOptions(options);
const webview = this.tryGetWebview(handle);
if (webview) {
webview.contentOptions = reviveWebviewContentOptions(options);
}
}
public async $postMessage(handle: extHostProtocol.WebviewHandle, jsonMessage: string, ...buffers: VSBuffer[]): Promise<boolean> {
const webview = this.getWebview(handle);
const webview = this.tryGetWebview(handle);
if (!webview) {
return false;
}
const { message, arrayBuffers } = deserializeWebviewMessage(jsonMessage, buffers);
return webview.postMessage(message, arrayBuffers);
}
@ -113,8 +117,12 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
return false;
}
private tryGetWebview(handle: extHostProtocol.WebviewHandle): IWebview | undefined {
return this._webviews.get(handle);
}
private getWebview(handle: extHostProtocol.WebviewHandle): IWebview {
const webview = this._webviews.get(handle);
const webview = this.tryGetWebview(handle);
if (!webview) {
throw new Error(`Unknown webview handle:${handle}`);
}

View file

@ -254,7 +254,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostAuthentication.getSession(extension, providerId, scopes, options as any);
},
getSessions(providerId: string, scopes: readonly string[]) {
checkProposedApiEnabled(extension, 'getSessions');
checkProposedApiEnabled(extension, 'authGetSessions');
return extHostAuthentication.getSessions(extension, providerId, scopes);
},
// TODO: remove this after GHPR and Codespaces move off of it

View file

@ -1493,12 +1493,12 @@ export interface ExtHostDocumentsAndEditorsShape {
}
export interface IDataTransferFileDTO {
readonly id: string;
readonly name: string;
readonly uri?: UriComponents;
}
export interface DataTransferItemDTO {
readonly id: string;
readonly asString: string;
readonly fileData: IDataTransferFileDTO | undefined;
readonly uriListData?: ReadonlyArray<string | UriComponents>;
@ -1838,6 +1838,7 @@ export interface IPasteEditDto {
id: string;
label: string;
detail: string;
priority: number;
insertText: string | { snippet: string };
additionalEdit?: IWorkspaceEditDto;
}
@ -1849,6 +1850,7 @@ export interface IDocumentDropEditProviderMetadata {
export interface IDocumentOnDropEditDto {
id: string;
label: string;
priority: number;
insertText: string | { snippet: string };
additionalEdit?: IWorkspaceEditDto;
}

View file

@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { mixin } from 'vs/base/common/objects';
import type * as vscode from 'vscode';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKind } from 'vs/workbench/api/common/extHostTypes';
import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKind, InternalDataTransferItem } from 'vs/workbench/api/common/extHostTypes';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import * as languages from 'vs/editor/common/languages';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
@ -510,7 +510,7 @@ class DocumentPasteEditProvider {
async prepareDocumentPaste(resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.DataTransferDTO | undefined> {
if (!this._provider.prepareDocumentPaste) {
return undefined;
return;
}
const doc = this._documents.getDocument(resource);
@ -520,8 +520,13 @@ class DocumentPasteEditProvider {
throw new NotImplementedError();
});
await this._provider.prepareDocumentPaste(doc, vscodeRanges, dataTransfer, token);
if (token.isCancellationRequested) {
return;
}
return typeConvert.DataTransfer.toDataTransferDTO(dataTransfer);
// Only send back values that have been added to the data transfer
const entries = Array.from(dataTransfer).filter(([, value]) => !(value instanceof InternalDataTransferItem));
return typeConvert.DataTransfer.from(entries);
}
async providePasteEdits(requestId: number, resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<undefined | extHostProtocol.IPasteEditDto> {
@ -541,6 +546,7 @@ class DocumentPasteEditProvider {
id: edit.id ? this._extension.identifier.value + '.' + edit.id : this._extension.identifier.value,
label: edit.label ?? localize('defaultPasteLabel', "Paste using '{0}' extension", this._extension.displayName || this._extension.name),
detail: this._extension.displayName || this._extension.name,
priority: edit.priority ?? 0,
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
};
@ -1754,6 +1760,7 @@ class DocumentOnDropEditAdapter {
return {
id: edit.id ? this._extension.identifier.value + '.' + edit.id : this._extension.identifier.value,
label: edit.label ?? localize('defaultDropLabel', "Drop using '{0}' extension", this._extension.displayName || this._extension.name),
priority: edit.priority ?? 0,
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
};

View file

@ -191,11 +191,11 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
}
const treeDataTransfer = await this.addAdditionalTransferItems(new types.DataTransfer(), treeView, sourceTreeItemHandles, token, operationUuid);
if (!treeDataTransfer) {
if (!treeDataTransfer || token.isCancellationRequested) {
return;
}
return DataTransfer.toDataTransferDTO(treeDataTransfer);
return DataTransfer.from(treeDataTransfer);
}
async $hasResolve(treeViewId: string): Promise<boolean> {

View file

@ -5,7 +5,7 @@
import { asArray, coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
import { VSBuffer, encodeBase64 } from 'vs/base/common/buffer';
import { IDataTransferItem, UriList, VSDataTransfer } from 'vs/base/common/dataTransfer';
import { IDataTransferFile, IDataTransferItem, UriList } from 'vs/base/common/dataTransfer';
import { once } from 'vs/base/common/functional';
import * as htmlContent from 'vs/base/common/htmlContent';
import { DisposableStore } from 'vs/base/common/lifecycle';
@ -2035,21 +2035,18 @@ export namespace ViewBadge {
}
export namespace DataTransferItem {
export function to(mime: string, item: extHostProtocol.DataTransferItemDTO, resolveFileData: () => Promise<Uint8Array>): types.DataTransferItem {
export function to(mime: string, item: extHostProtocol.DataTransferItemDTO, resolveFileData: (id: string) => Promise<Uint8Array>): types.DataTransferItem {
const file = item.fileData;
if (file) {
return new class extends types.DataTransferItem {
override asFile() {
return new types.DataTransferFile(file.name, URI.revive(file.uri), item.id, once(() => resolveFileData()));
}
}('', item.id);
return new types.InternalFileDataTransferItem(
new types.DataTransferFile(file.name, URI.revive(file.uri), file.id, once(() => resolveFileData(file.id))));
}
if (mime === Mimes.uriList && item.uriListData) {
return new types.DataTransferItem(reviveUriList(item.uriListData));
return new types.InternalDataTransferItem(reviveUriList(item.uriListData));
}
return new types.DataTransferItem(item.asString);
return new types.InternalDataTransferItem(item.asString);
}
export async function from(mime: string, item: vscode.DataTransferItem | IDataTransferItem): Promise<extHostProtocol.DataTransferItemDTO> {
@ -2057,7 +2054,6 @@ export namespace DataTransferItem {
if (mime === Mimes.uriList) {
return {
id: (item as IDataTransferItem | types.DataTransferItem).id,
asString: stringValue,
fileData: undefined,
uriListData: serializeUriList(stringValue),
@ -2066,9 +2062,12 @@ export namespace DataTransferItem {
const fileValue = item.asFile();
return {
id: (item as IDataTransferItem | types.DataTransferItem).id,
asString: stringValue,
fileData: fileValue ? { name: fileValue.name, uri: fileValue.uri } : undefined,
fileData: fileValue ? {
name: fileValue.name,
uri: fileValue.uri,
id: (fileValue as types.DataTransferFile)._itemId ?? (fileValue as IDataTransferFile).id,
} : undefined,
};
}
@ -2098,21 +2097,20 @@ export namespace DataTransferItem {
export namespace DataTransfer {
export function toDataTransfer(value: extHostProtocol.DataTransferDTO, resolveFileData: (itemId: string) => Promise<Uint8Array>): types.DataTransfer {
const init = value.items.map(([type, item]) => {
return [type, DataTransferItem.to(type, item, () => resolveFileData(item.id))] as const;
return [type, DataTransferItem.to(type, item, resolveFileData)] as const;
});
return new types.DataTransfer(init);
}
export async function toDataTransferDTO(value: vscode.DataTransfer | VSDataTransfer): Promise<extHostProtocol.DataTransferDTO> {
export async function from(dataTransfer: Iterable<readonly [string, vscode.DataTransferItem | IDataTransferItem]>): Promise<extHostProtocol.DataTransferDTO> {
const newDTO: extHostProtocol.DataTransferDTO = { items: [] };
const promises: Promise<any>[] = [];
value.forEach((value, key) => {
for (const [mime, value] of dataTransfer) {
promises.push((async () => {
newDTO.items.push([key, await DataTransferItem.from(key, value)]);
newDTO.items.push([mime, await DataTransferItem.from(mime, value)]);
})());
});
}
await Promise.all(promises);

View file

@ -2594,13 +2594,34 @@ export class DataTransferItem implements vscode.DataTransferItem {
return undefined;
}
public readonly id: string;
constructor(
public readonly value: any,
id?: string,
) {
this.id = id ?? generateUuid();
) { }
}
/**
* A data transfer item that has been created by VS Code instead of by a extension.
*
* Intentionally not exported to extensions.
*/
export class InternalDataTransferItem extends DataTransferItem { }
/**
* A data transfer item for a file.
*
* Intentionally not exported to extensions as only we can create these.
*/
export class InternalFileDataTransferItem extends InternalDataTransferItem {
readonly #file: vscode.DataTransferFile;
constructor(file: vscode.DataTransferFile) {
super('');
this.#file = file;
}
override asFile() {
return this.#file;
}
}

View file

@ -3,45 +3,41 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { coalesce } from 'vs/base/common/arrays';
import { VSBuffer } from 'vs/base/common/buffer';
import { VSDataTransfer, IDataTransferItem } from 'vs/base/common/dataTransfer';
import { IDataTransferFile, IReadonlyVSDataTransfer } from 'vs/base/common/dataTransfer';
export class DataTransferCache {
export class DataTransferFileCache {
private requestIdPool = 0;
private readonly dataTransfers = new Map</* requestId */ number, ReadonlyArray<IDataTransferItem>>();
private readonly dataTransferFiles = new Map</* requestId */ number, ReadonlyArray<IDataTransferFile>>();
public add(dataTransfer: VSDataTransfer): { id: number; dispose: () => void } {
public add(dataTransfer: IReadonlyVSDataTransfer): { id: number; dispose: () => void } {
const requestId = this.requestIdPool++;
this.dataTransfers.set(requestId, [...dataTransfer.values()]);
this.dataTransferFiles.set(requestId, coalesce(Array.from(dataTransfer, ([, item]) => item.asFile())));
return {
id: requestId,
dispose: () => {
this.dataTransfers.delete(requestId);
this.dataTransferFiles.delete(requestId);
}
};
}
async resolveFileData(requestId: number, dataItemId: string): Promise<VSBuffer> {
const entry = this.dataTransfers.get(requestId);
if (!entry) {
const files = this.dataTransferFiles.get(requestId);
if (!files) {
throw new Error('No data transfer found');
}
const item = entry.find(x => x.id === dataItemId);
if (!item) {
throw new Error('No item found in data transfer');
}
const file = item.asFile();
const file = files.find(file => file.id === dataItemId);
if (!file) {
throw new Error('Found data transfer item is not a file');
throw new Error('No matching file found in data transfer');
}
return VSBuffer.wrap(await file.data());
}
dispose() {
this.dataTransfers.clear();
this.dataTransferFiles.clear();
}
}

View file

@ -61,7 +61,7 @@ export class EditorGroupWatermark extends Disposable {
private readonly shortcuts: HTMLElement;
private transientDisposables = this._register(new DisposableStore());
private enabled: boolean;
private enabled: boolean = false;
private workbenchState: WorkbenchState;
constructor(
@ -83,14 +83,10 @@ export class EditorGroupWatermark extends Disposable {
append(container, elements.root);
this.shortcuts = elements.shortcuts;
this.workbenchState = contextService.getWorkbenchState();
this.enabled = this.configurationService.getValue<boolean>('workbench.tips.enabled');
this.registerListeners();
if (this.enabled) {
this.render();
}
this.workbenchState = contextService.getWorkbenchState();
this.render();
}
private registerListeners(): void {
@ -98,24 +94,12 @@ export class EditorGroupWatermark extends Disposable {
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('workbench.tips.enabled')) {
const enabled = this.configurationService.getValue<boolean>('workbench.tips.enabled');
if (enabled === this.enabled) {
return;
}
this.enabled = enabled;
if (this.enabled) {
this.render();
} else {
this.clear();
}
this.render();
}
}));
this._register(this.contextService.onDidChangeWorkbenchState(workbenchState => {
if (!this.enabled || this.workbenchState === workbenchState) {
if (this.workbenchState === workbenchState) {
return;
}
@ -134,8 +118,19 @@ export class EditorGroupWatermark extends Disposable {
}
private render(): void {
const enabled = this.configurationService.getValue<boolean>('workbench.tips.enabled');
if (enabled === this.enabled) {
return;
}
this.enabled = enabled;
this.clear();
if (!enabled) {
return;
}
const box = append(this.shortcuts, $('.watermark-box'));
const folder = this.workbenchState !== WorkbenchState.EMPTY;
const selected = (folder ? folderEntries : noFolderEntries)

View file

@ -1547,7 +1547,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
return dndController.handleDrag(itemHandles, uuid, dragCancellationToken).then(additionalDataTransfer => {
if (additionalDataTransfer) {
const unlistedTypes: string[] = [];
for (const item of additionalDataTransfer.entries()) {
for (const item of additionalDataTransfer) {
if ((item[0] !== this.treeMimeType) && (dndController.dragMimeTypes.findIndex(value => value === item[0]) < 0)) {
unlistedTypes.push(item[0]);
}
@ -1625,7 +1625,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
const dataTransfer = toExternalVSDataTransfer(originalEvent.dataTransfer!);
const types = new Set<string>(Array.from(dataTransfer.entries()).map(x => x[0]));
const types = new Set<string>(Array.from(dataTransfer, x => x[0]));
if (originalEvent.dataTransfer) {
// Also add uri-list if we have any files. At this stage we can't actually access the file itself though.
@ -1689,7 +1689,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
const originalDataTransfer = toExternalVSDataTransfer(originalEvent.dataTransfer, true);
const outDataTransfer = new VSDataTransfer();
for (const [type, item] of originalDataTransfer.entries()) {
for (const [type, item] of originalDataTransfer) {
if (type === this.treeMimeType || dndController.dropMimeTypes.includes(type) || (item.asFile() && dndController.dropMimeTypes.includes(DataTransfers.FILES.toLowerCase()))) {
outDataTransfer.append(type, item);
if (type === this.treeMimeType) {
@ -1704,7 +1704,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
const additionalDataTransfer = await this.treeViewsDragAndDropService.removeDragOperationTransfer(willDropUuid);
if (additionalDataTransfer) {
for (const [type, item] of additionalDataTransfer.entries()) {
for (const [type, item] of additionalDataTransfer) {
outDataTransfer.append(type, item);
}
}

View file

@ -9,8 +9,8 @@ import * as nls from 'vs/nls';
import { contrastBorder, disabledForeground, listFocusOutline, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme } from 'vs/platform/theme/common/themeService';
const resolvedCommentViewIcon = registerColor('commentsView.resolvedIcon', { dark: disabledForeground, light: disabledForeground, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('resolvedCommentBorder', 'Color of borders and arrow for resolved comments.'));
const unresolvedCommentViewIcon = registerColor('commentsView.unresolvedIcon', { dark: listFocusOutline, light: listFocusOutline, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('unresolvedCommentBorder', 'Color of borders and arrow for unresolved comments.'));
const resolvedCommentViewIcon = registerColor('commentsView.resolvedIcon', { dark: disabledForeground, light: disabledForeground, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('resolvedCommentIcon', 'Icon color for resolved comments.'));
const unresolvedCommentViewIcon = registerColor('commentsView.unresolvedIcon', { dark: listFocusOutline, light: listFocusOutline, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('unresolvedCommentIcon', 'Icon color for unresolved comments.'));
const resolvedCommentBorder = registerColor('editorCommentsWidget.resolvedBorder', { dark: resolvedCommentViewIcon, light: resolvedCommentViewIcon, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('resolvedCommentBorder', 'Color of borders and arrow for resolved comments.'));
const unresolvedCommentBorder = registerColor('editorCommentsWidget.unresolvedBorder', { dark: unresolvedCommentViewIcon, light: unresolvedCommentViewIcon, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('unresolvedCommentBorder', 'Color of borders and arrow for unresolved comments.'));

View file

@ -14,6 +14,9 @@ import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon';
import { CommentThreadState } from 'vs/editor/common/languages';
export const overviewRulerCommentingRangeForeground = registerColor('editorGutter.commentRangeForeground', { dark: opaque(listInactiveSelectionBackground, editorBackground), light: darken(opaque(listInactiveSelectionBackground, editorBackground), .05), hcDark: Color.white, hcLight: Color.black }, nls.localize('editorGutterCommentRangeForeground', 'Editor gutter decoration color for commenting ranges. This color should be opaque.'));
const overviewRulerCommentForeground = registerColor('editorOverviewRuler.commentForeground', { dark: overviewRulerCommentingRangeForeground, light: overviewRulerCommentingRangeForeground, hcDark: overviewRulerCommentingRangeForeground, hcLight: overviewRulerCommentingRangeForeground }, nls.localize('editorOverviewRuler.commentForeground', 'Editor overview ruler decoration color for resolved comments. This color should be opaque.'));
const overviewRulerCommentUnresolvedForeground = registerColor('editorOverviewRuler.commentUnresolvedForeground', { dark: overviewRulerCommentForeground, light: overviewRulerCommentForeground, hcDark: overviewRulerCommentForeground, hcLight: overviewRulerCommentForeground }, nls.localize('editorOverviewRuler.commentUnresolvedForeground', 'Editor overview ruler decoration color for unresolved comments. This color should be opaque.'));
const editorGutterCommentGlyphForeground = registerColor('editorGutter.commentGlyphForeground', { dark: editorForeground, light: editorForeground, hcDark: Color.black, hcLight: Color.white }, nls.localize('editorGutterCommentGlyphForeground', 'Editor gutter decoration color for commenting glyphs.'));
registerColor('editorGutter.commentUnresolvedGlyphForeground', { dark: editorGutterCommentGlyphForeground, light: editorGutterCommentGlyphForeground, hcDark: editorGutterCommentGlyphForeground, hcLight: editorGutterCommentGlyphForeground }, nls.localize('editorGutterCommentUnresolvedGlyphForeground', 'Editor gutter decoration color for commenting glyphs for unresolved comment threads.'));
@ -33,15 +36,16 @@ export class CommentGlyphWidget {
}
private createDecorationOptions(): ModelDecorationOptions {
const unresolved = this._threadState === CommentThreadState.Unresolved;
const decorationOptions: IModelDecorationOptions = {
description: CommentGlyphWidget.description,
isWholeLine: true,
overviewRuler: {
color: themeColorFromId(overviewRulerCommentingRangeForeground),
color: themeColorFromId(unresolved ? overviewRulerCommentUnresolvedForeground : overviewRulerCommentForeground),
position: OverviewRulerLane.Center
},
collapseOnReplaceEdit: true,
linesDecorationsClassName: `comment-range-glyph comment-thread${this._threadState === CommentThreadState.Unresolved ? '-unresolved' : ''}`
linesDecorationsClassName: `comment-range-glyph comment-thread${unresolved ? '-unresolved' : ''}`
};
return ModelDecorationOptions.createDynamic(decorationOptions);

View file

@ -148,7 +148,7 @@ export class InteractiveEditor extends EditorPane {
this.#notebookExecutionStateService = notebookExecutionStateService;
this.#extensionService = extensionService;
this.#notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, { cellToolbarInteraction: 'hover', globalToolbar: true, dragAndDropEnabled: false });
this.#notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, dragAndDropEnabled: false });
this.#editorMemento = this.getEditorMemento<InteractiveEditorViewState>(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY);
codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {});

View file

@ -157,7 +157,7 @@ export class ArrowOutUpAction extends AbstractInteractiveEditorAction {
super({
id: 'interactiveEditor.arrowOutUp',
title: localize('arrowUp', 'Cursor Up'),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, CTX_INTERACTIVE_EDITOR_EDIT_MODE.notEqualsTo(EditMode.LivePreview)),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, EditorContextKeys.isEmbeddedDiffEditor.negate()),
keybinding: {
weight: KeybindingWeight.EditorCore,
primary: KeyCode.UpArrow
@ -175,7 +175,7 @@ export class ArrowOutDownAction extends AbstractInteractiveEditorAction {
super({
id: 'interactiveEditor.arrowOutDown',
title: localize('arrowDown', 'Cursor Down'),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, CTX_INTERACTIVE_EDITOR_EDIT_MODE.notEqualsTo(EditMode.LivePreview)),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, EditorContextKeys.isEmbeddedDiffEditor.negate()),
keybinding: {
weight: KeybindingWeight.EditorCore,
primary: KeyCode.DownArrow
@ -198,11 +198,11 @@ export class FocusInteractiveEditor extends EditorAction2 {
precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_FOCUSED.negate()),
keybinding: [{
weight: KeybindingWeight.EditorCore + 10, // win against core_command
when: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION.isEqualTo('above'), CTX_INTERACTIVE_EDITOR_EDIT_MODE.notEqualsTo(EditMode.LivePreview)),
when: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION.isEqualTo('above'), EditorContextKeys.isEmbeddedDiffEditor.negate()),
primary: KeyCode.DownArrow,
}, {
weight: KeybindingWeight.EditorCore + 10, // win against core_command
when: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION.isEqualTo('below'), CTX_INTERACTIVE_EDITOR_EDIT_MODE.notEqualsTo(EditMode.LivePreview)),
when: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION.isEqualTo('below'), EditorContextKeys.isEmbeddedDiffEditor.negate()),
primary: KeyCode.UpArrow,
}]
});

View file

@ -6,7 +6,7 @@
import { Dimension, h } from 'vs/base/browser/dom';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { assertType } from 'vs/base/common/types';
import { IActiveCodeEditor, ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { Range } from 'vs/editor/common/core/range';
@ -43,7 +43,7 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget {
private _dim: Dimension | undefined;
constructor(
editor: IActiveCodeEditor,
editor: ICodeEditor,
private readonly _textModelv0: ITextModel,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@ -51,6 +51,7 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget {
) {
super(editor, { showArrow: false, showFrame: false, isResizeable: false, isAccessible: true, allowUnlimitedHeight: true, showInHiddenAreas: true, ordinal: 10000 + 1 });
super.create();
assertType(editor.hasModel());
this._inlineDiffDecorations = editor.createDecorationsCollection();
@ -73,6 +74,7 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget {
diffCodeLens: false,
stickyScroll: { enabled: false },
minimap: { enabled: false },
isInEmbeddedEditor: true
}, {
originalEditor: { contributions: diffContributions },
modifiedEditor: { contributions: diffContributions }
@ -350,7 +352,13 @@ export class InteractiveEditorFileCreatePreviewWidget extends ZoneWidget {
super.create();
this._title = instaService.createInstance(ResourceLabel, this._elements.title, { supportIcons: true });
this._previewEditor = instaService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, { scrollBeyondLastLine: false, stickyScroll: { enabled: false }, readOnly: true, minimap: { enabled: false } }, { isSimpleWidget: true, contributions: [] }, parentEditor);
this._previewEditor = instaService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, {
scrollBeyondLastLine: false,
stickyScroll: { enabled: false },
readOnly: true,
minimap: { enabled: false },
scrollbar: { alwaysConsumeMouseWheel: false },
}, { isSimpleWidget: true, contributions: [] }, parentEditor);
const doStyle = () => {
const theme = themeService.getColorTheme();

View file

@ -5,15 +5,25 @@
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { TextEdit } from 'vs/editor/common/languages';
import { ITextModel } from 'vs/editor/common/model';
import { EditMode, IInteractiveEditorSessionProvider, IInteractiveEditorSession, IInteractiveEditorBulkEditResponse, IInteractiveEditorEditResponse, IInteractiveEditorMessageResponse, IInteractiveEditorResponse } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor';
import { Range } from 'vs/editor/common/core/range';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditMode, IInteractiveEditorSessionProvider, IInteractiveEditorSession, IInteractiveEditorBulkEditResponse, IInteractiveEditorEditResponse, IInteractiveEditorMessageResponse, IInteractiveEditorResponse, IInteractiveEditorService } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor';
import { IRange, Range } from 'vs/editor/common/core/range';
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ResourceMap } from 'vs/base/common/map';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IModelService } from 'vs/editor/common/services/model';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Iterable } from 'vs/base/common/iterator';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { isCancellationError } from 'vs/base/common/errors';
export type Recording = {
when: Date;
@ -45,16 +55,19 @@ type TelemetryDataClassification = {
export class Session {
private _lastInput: string | undefined;
private readonly _exchange: SessionExchange[] = [];
private readonly _startTime = new Date();
private readonly _teldata: Partial<TelemetryData>;
constructor(
readonly editMode: EditMode,
readonly model0: ITextModel,
readonly modelN: ITextModel,
readonly editor: ICodeEditor,
readonly textModel0: ITextModel,
readonly textModelN: ITextModel,
readonly provider: IInteractiveEditorSessionProvider,
readonly session: IInteractiveEditorSession,
private readonly _wholeRangeMarkerId: string
) {
this._teldata = {
extension: provider.debugName,
@ -66,6 +79,19 @@ export class Session {
};
}
addInput(input: string): void {
this._lastInput = input;
}
get lastInput() {
return this._lastInput;
}
get wholeRange(): Range {
return this.textModelN.getDecorationRange(this._wholeRangeMarkerId)!;
// return new Range(1, 1, 1, 1);
}
addExchange(exchange: SessionExchange): void {
const newLen = this._exchange.push(exchange);
this._teldata.rounds += `${newLen}|`;
@ -87,11 +113,18 @@ export class Session {
}
asRecording(): Recording {
return {
const result: Recording = {
session: this.session,
when: this._startTime,
exchanges: this._exchange.map(e => ({ prompt: e.prompt, res: e.response.raw }))
exchanges: []
};
for (const exchange of this._exchange) {
const response = exchange.response;
if (response instanceof MarkdownResponse || response instanceof EditResponse) {
result.exchanges.push({ prompt: exchange.prompt, res: response.raw });
}
}
return result;
}
}
@ -99,10 +132,27 @@ export class Session {
export class SessionExchange {
constructor(
readonly prompt: string,
readonly response: MarkdownResponse | EditResponse
readonly response: MarkdownResponse | EditResponse | EmptyResponse | ErrorResponse
) { }
}
export class EmptyResponse {
}
export class ErrorResponse {
readonly message: string;
readonly isCancellation: boolean;
constructor(
readonly error: any
) {
this.message = toErrorMessage(error, false);
this.isCancellation = isCancellationError(error);
}
}
export class MarkdownResponse {
constructor(
readonly localUri: URI,
@ -171,47 +221,111 @@ export const IInteractiveEditorSessionService = createDecorator<IInteractiveEdit
export interface IInteractiveEditorSessionService {
_serviceBrand: undefined;
retrieveSession(editor: ICodeEditor, uri: URI): Session | undefined;
createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange }, token: CancellationToken): Promise<Session | undefined>;
storeSession(editor: ICodeEditor, uri: URI, session: Session): void;
getSession(editor: ICodeEditor, uri: URI): Session | undefined;
releaseSession(editor: ICodeEditor, uri: URI, session: Session): void;
releaseSession(session: Session): void;
//
recordings(): readonly Recording[];
}
type SessionData = {
session: Session;
store: IDisposable;
};
export class InteractiveEditorSessionService implements IInteractiveEditorSessionService {
declare _serviceBrand: undefined;
private readonly _sessions = new Map<ICodeEditor, ResourceMap<Session>>();
private readonly _sessions = new Map<ICodeEditor, ResourceMap<SessionData>>();
private _recordings: Recording[] = [];
constructor(
@IInteractiveEditorService private readonly _interactiveEditorService: IInteractiveEditorService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IModelService private readonly _modelService: IModelService,
@ITextModelService private readonly _textModelService: ITextModelService,
@ILogService private readonly _logService: ILogService,
) { }
storeSession(editor: ICodeEditor, uri: URI, session: Session): void {
async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise<Session | undefined> {
const provider = Iterable.first(this._interactiveEditorService.getAllProvider());
if (!provider) {
this._logService.trace('[IE] NO provider found');
return undefined;
}
const textModel = editor.getModel();
const selection = editor.getSelection();
const raw = await provider.prepareInteractiveEditorSession(textModel, selection, token);
if (!raw) {
this._logService.trace('[IE] NO session', provider.debugName);
return undefined;
}
this._logService.trace('[IE] NEW session', provider.debugName);
this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.debugName}`);
const store = new DisposableStore();
// create: keep a reference to prevent disposal of the "actual" model
const refTextModelN = await this._textModelService.createModelReference(textModel.uri);
store.add(refTextModelN);
// create: keep a snapshot of the "actual" model
const textModel0 = this._modelService.createModel(
createTextBufferFactoryFromSnapshot(textModel.createSnapshot()),
{ languageId: textModel.getLanguageId(), onDidChange: Event.None },
undefined, true
);
store.add(textModel0);
let wholeRange = options.wholeRange;
if (!wholeRange) {
wholeRange = raw.wholeRange ? Range.lift(raw.wholeRange) : editor.getSelection();
}
if (Range.isEmpty(wholeRange)) {
wholeRange = new Range(wholeRange.startLineNumber, 1, wholeRange.endLineNumber, textModel.getLineMaxColumn(wholeRange.endLineNumber));
}
// install a marker for the decoration range
const [wholeRangeDecorationId] = textModel.deltaDecorations([], [{ range: wholeRange, options: { description: 'interactiveEditor/session/wholeRange' } }]);
store.add(toDisposable(() => textModel.deltaDecorations([wholeRangeDecorationId], [])));
const session = new Session(options.editMode, editor, textModel0, textModel, provider, raw, wholeRangeDecorationId);
// store: editor -> uri -> session
let map = this._sessions.get(editor);
if (!map) {
map = new ResourceMap<Session>();
map = new ResourceMap<SessionData>();
this._sessions.set(editor, map);
}
if (map.has(uri)) {
throw new Error(`Session already stored for ${uri}`);
if (map.has(textModel.uri)) {
throw new Error(`Session already stored for ${textModel.uri}`);
}
map.set(uri, session);
map.set(textModel.uri, { session, store });
return session;
}
releaseSession(editor: ICodeEditor, uri: URI, session: Session): void {
releaseSession(session: Session): void {
const { editor, textModelN } = session;
// cleanup
const map = this._sessions.get(editor);
if (map) {
map.delete(uri);
const data = map.get(textModelN.uri);
if (data) {
data.store.dispose();
data.session.session.dispose?.();
map.delete(textModelN.uri);
}
if (map.size === 0) {
this._sessions.delete(editor);
}
@ -227,8 +341,8 @@ export class InteractiveEditorSessionService implements IInteractiveEditorSessio
this._telemetryService.publicLog2<TelemetryData, TelemetryDataClassification>('interactiveEditor/session', session.asTelemetryData());
}
retrieveSession(editor: ICodeEditor, uri: URI): Session | undefined {
return this._sessions.get(editor)?.get(uri);
getSession(editor: ICodeEditor, uri: URI): Session | undefined {
return this._sessions.get(editor)?.get(uri)?.session;
}
// --- debug

View file

@ -0,0 +1,364 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import 'vs/css!./interactiveEditor';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon';
import { ICursorStateComputer, IModelDecorationOptions, IModelDeltaDecoration, IValidEditOperation } from 'vs/editor/common/model';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
import { localize } from 'vs/nls';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { InteractiveEditorFileCreatePreviewWidget, InteractiveEditorLivePreviewWidget } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorLivePreviewWidget';
import { EditResponse, Session } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorSession';
import { InteractiveEditorWidget } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget';
import { CTX_INTERACTIVE_EDITOR_INLNE_DIFF, CTX_INTERACTIVE_EDITOR_DOCUMENT_CHANGED } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor';
export abstract class EditModeStrategy {
dispose(): void { }
abstract checkChanges(response: EditResponse): boolean;
abstract apply(): Promise<void>;
abstract cancel(): Promise<void>;
abstract renderChanges(response: EditResponse, edits: ISingleEditOperation[], changes: LineRangeMapping[]): Promise<void>;
abstract hide(): Promise<void>;
abstract toggleInlineDiff(): void;
}
export class PreviewStrategy extends EditModeStrategy {
private readonly _ctxDocumentChanged: IContextKey<boolean>;
private readonly _listener: IDisposable;
constructor(
private readonly _session: Session,
private readonly _widget: InteractiveEditorWidget,
@IContextKeyService contextKeyService: IContextKeyService,
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
) {
super();
this._ctxDocumentChanged = CTX_INTERACTIVE_EDITOR_DOCUMENT_CHANGED.bindTo(contextKeyService);
this._listener = Event.debounce(_session.textModelN.onDidChangeContent.bind(_session.textModelN), () => { }, 350)(_ => {
this._ctxDocumentChanged.set(!_session.textModelN.equalsTextBuffer(_session.textModel0.getTextBuffer()));
});
}
override dispose(): void {
this._listener.dispose();
this._ctxDocumentChanged.reset();
super.dispose();
}
checkChanges(response: EditResponse): boolean {
if (!response.workspaceEdits || response.singleCreateFileEdit) {
// preview stategy can handle simple workspace edit (single file create)
return true;
}
this._bulkEditService.apply(response.workspaceEdits, { showPreview: true });
return false;
}
async apply() {
if (!(this._session.lastExchange?.response instanceof EditResponse)) {
return;
}
const editResponse = this._session.lastExchange?.response;
if (editResponse.workspaceEdits) {
await this._bulkEditService.apply(editResponse.workspaceEdits);
} else if (!editResponse.workspaceEditsIncludeLocalEdits) {
const { textModelN: modelN } = this._session;
if (modelN.equalsTextBuffer(this._session.textModel0.getTextBuffer())) {
modelN.pushStackElement();
const edits = editResponse.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text));
modelN.pushEditOperations(null, edits, () => null);
modelN.pushStackElement();
}
}
}
override async hide(): Promise<void> {
// nothing to do, input widget will be hidden by controller
}
async cancel(): Promise<void> {
// nothing to do
}
override async renderChanges(response: EditResponse, edits: ISingleEditOperation[], changes: LineRangeMapping[]): Promise<void> {
if (response.localEdits.length > 0) {
this._widget.showEditsPreview(this._session.textModel0, edits, changes);
} else {
this._widget.hideEditsPreview();
}
if (response.singleCreateFileEdit) {
this._widget.showCreatePreview(response.singleCreateFileEdit.uri, await Promise.all(response.singleCreateFileEdit.edits));
} else {
this._widget.hideCreatePreview();
}
}
toggleInlineDiff(): void { }
}
class InlineDiffDecorations {
private readonly _collection: IEditorDecorationsCollection;
private _data: { tracking: IModelDeltaDecoration; decorating: IModelDecorationOptions }[] = [];
private _visible: boolean = false;
constructor(editor: ICodeEditor, visible: boolean = false) {
this._collection = editor.createDecorationsCollection();
this._visible = visible;
}
get visible() {
return this._visible;
}
set visible(value: boolean) {
this._visible = value;
this.update();
}
clear() {
this._collection.clear();
this._data.length = 0;
}
collectEditOperation(op: IValidEditOperation) {
this._data.push(InlineDiffDecorations._asDecorationData(op));
}
update() {
this._collection.set(this._data.map(d => {
const res = { ...d.tracking };
if (this._visible) {
res.options = { ...res.options, ...d.decorating };
}
return res;
}));
}
private static _asDecorationData(edit: IValidEditOperation): { tracking: IModelDeltaDecoration; decorating: IModelDecorationOptions } {
let content = edit.text;
if (content.length > 12) {
content = content.substring(0, 12) + '…';
}
const tracking: IModelDeltaDecoration = {
range: edit.range,
options: {
description: 'interactive-editor-inline-diff',
}
};
const decorating: IModelDecorationOptions = {
description: 'interactive-editor-inline-diff',
className: !edit.range.isEmpty() ? 'interactive-editor-lines-inserted-range' : undefined,
showIfCollapsed: true,
before: {
content,
inlineClassName: 'interactive-editor-lines-deleted-range-inline',
attachedData: edit,
}
};
return { tracking, decorating };
}
}
export class LiveStrategy extends EditModeStrategy {
private static _inlineDiffStorageKey: string = 'interactiveEditor.storage.inlineDiff';
private _inlineDiffEnabled: boolean = false;
private readonly _inlineDiffDecorations: InlineDiffDecorations;
private readonly _ctxInlineDiff: IContextKey<boolean>;
private _lastResponse?: EditResponse;
constructor(
protected readonly _session: Session,
protected readonly _editor: ICodeEditor,
protected readonly _widget: InteractiveEditorWidget,
@IContextKeyService contextKeyService: IContextKeyService,
@IStorageService protected _storageService: IStorageService,
@IBulkEditService protected readonly _bulkEditService: IBulkEditService,
@IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService
) {
super();
this._inlineDiffDecorations = new InlineDiffDecorations(this._editor, this._inlineDiffEnabled);
this._ctxInlineDiff = CTX_INTERACTIVE_EDITOR_INLNE_DIFF.bindTo(contextKeyService);
this._inlineDiffEnabled = _storageService.getBoolean(LiveStrategy._inlineDiffStorageKey, StorageScope.PROFILE, false);
this._ctxInlineDiff.set(this._inlineDiffEnabled);
this._inlineDiffDecorations.visible = this._inlineDiffEnabled;
}
override dispose(): void {
this._inlineDiffEnabled = this._inlineDiffDecorations.visible;
this._storageService.store(LiveStrategy._inlineDiffStorageKey, this._inlineDiffEnabled, StorageScope.PROFILE, StorageTarget.USER);
this._inlineDiffDecorations.clear();
this._ctxInlineDiff.reset();
super.dispose();
}
toggleInlineDiff(): void {
this._inlineDiffEnabled = !this._inlineDiffEnabled;
this._ctxInlineDiff.set(this._inlineDiffEnabled);
this._inlineDiffDecorations.visible = this._inlineDiffEnabled;
this._storageService.store(LiveStrategy._inlineDiffStorageKey, this._inlineDiffEnabled, StorageScope.PROFILE, StorageTarget.USER);
}
checkChanges(response: EditResponse): boolean {
this._lastResponse = response;
if (response.singleCreateFileEdit) {
// preview stategy can handle simple workspace edit (single file create)
return true;
}
if (response.workspaceEdits) {
this._bulkEditService.apply(response.workspaceEdits, { showPreview: true });
return false;
}
return true;
}
async apply() {
if (this._lastResponse?.workspaceEdits) {
await this._bulkEditService.apply(this._lastResponse.workspaceEdits);
}
}
override async hide(): Promise<void> {
this._inlineDiffDecorations.clear();
}
async cancel() {
const { textModelN: modelN, textModel0: model0 } = this._session;
if (modelN.isDisposed() || model0.isDisposed()) {
return;
}
const edits = await this._editorWorkerService.computeMoreMinimalEdits(modelN.uri, [{ range: modelN.getFullModelRange(), text: model0.getValue() }]);
if (edits) {
const operations = edits.map(e => EditOperation.replace(Range.lift(e.range), e.text));
modelN.pushEditOperations(null, operations, () => null);
}
}
override async renderChanges(response: EditResponse, edits: ISingleEditOperation[], textModel0Changes: LineRangeMapping[]) {
const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => {
let last: Position | null = null;
for (const edit of undoEdits) {
last = !last || last.isBefore(edit.range.getEndPosition()) ? edit.range.getEndPosition() : last;
this._inlineDiffDecorations.collectEditOperation(edit);
}
return last && [Selection.fromPositions(last)];
};
this._editor.pushUndoStop();
this._editor.executeEdits('interactive-editor-live', edits, cursorStateComputerAndInlineDiffCollection);
this._editor.pushUndoStop();
this._inlineDiffDecorations.update();
this._updateSummaryMessage(textModel0Changes);
if (response.singleCreateFileEdit) {
this._widget.showCreatePreview(response.singleCreateFileEdit.uri, await Promise.all(response.singleCreateFileEdit.edits));
} else {
this._widget.hideCreatePreview();
}
}
protected _updateSummaryMessage(textModel0Changes: LineRangeMapping[]) {
let linesChanged = 0;
if (textModel0Changes) {
for (const change of textModel0Changes) {
linesChanged += change.changedLineCount;
}
}
let message: string;
if (linesChanged === 0) {
message = localize('lines.0', "Generated reply");
} else if (linesChanged === 1) {
message = localize('lines.1', "Generated reply and changed 1 line");
} else {
message = localize('lines.N', "Generated reply and changed {0} lines", linesChanged);
}
this._widget.updateStatus(message);
}
}
export class LivePreviewStrategy extends LiveStrategy {
private readonly _diffZone: InteractiveEditorLivePreviewWidget;
private readonly _previewZone: InteractiveEditorFileCreatePreviewWidget;
constructor(
session: Session,
editor: ICodeEditor,
widget: InteractiveEditorWidget,
private _getWholeRange: () => Range,
@IContextKeyService contextKeyService: IContextKeyService,
@IStorageService storageService: IStorageService,
@IBulkEditService bulkEditService: IBulkEditService,
@IEditorWorkerService editorWorkerService: IEditorWorkerService,
@IInstantiationService instaService: IInstantiationService,
) {
super(session, editor, widget, contextKeyService, storageService, bulkEditService, editorWorkerService);
this._diffZone = instaService.createInstance(InteractiveEditorLivePreviewWidget, editor, session.textModel0);
this._previewZone = instaService.createInstance(InteractiveEditorFileCreatePreviewWidget, editor);
}
override dispose(): void {
this._diffZone.hide();
this._diffZone.dispose();
this._previewZone.hide();
this._previewZone.dispose();
super.dispose();
}
override async hide(): Promise<void> {
this._diffZone.hide();
super.hide();
}
override async renderChanges(response: EditResponse, edits: ISingleEditOperation[], changes: LineRangeMapping[]) {
this._editor.pushUndoStop();
this._editor.executeEdits('interactive-editor-livePreview', edits);
this._editor.pushUndoStop();
this._diffZone.showDiff(() => this._getWholeRange(), changes);
this._updateSummaryMessage(changes);
if (response.singleCreateFileEdit) {
this._previewZone.showCreation(this._getWholeRange(), response.singleCreateFileEdit.uri, await Promise.all(response.singleCreateFileEdit.edits));
} else {
this._previewZone.hide();
}
}
}

View file

@ -31,7 +31,7 @@ import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style';
import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult, TextEdit } from 'vs/editor/common/languages';
import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { ILanguageSelection } from 'vs/editor/common/languages/language';
import { ILanguageSelection, ILanguageService } from 'vs/editor/common/languages/language';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import { FileKind } from 'vs/platform/files/common/files';
import { IAction } from 'vs/base/common/actions';
@ -99,6 +99,7 @@ const _previewEditorEditorOptions: IDiffEditorConstructionOptions = {
modifiedAriaLabel: localize('original', 'Original'),
diffAlgorithm: 'advanced',
readOnly: true,
isInEmbeddedEditor: true
};
export interface InteractiveEditorWidgetViewState {
@ -167,6 +168,7 @@ export class InteractiveEditorWidget {
constructor(
parentEditor: ICodeEditor,
@IModelService private readonly _modelService: IModelService,
@ILanguageService private readonly _languageService: ILanguageService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ -326,13 +328,13 @@ export class InteractiveEditorWidget {
const editorViewState = this._inputEditor.saveViewState();
return {
editorViewState,
input: this.input,
input: this.value,
placeholder: this.placeholder
};
}
restoreViewState(state: InteractiveEditorWidgetViewState) {
this.input = state.input;
this.value = state.input;
this.placeholder = state.placeholder;
this._inputEditor.restoreViewState(state.editorViewState);
}
@ -345,11 +347,11 @@ export class InteractiveEditorWidget {
}
}
get input(): string {
get value(): string {
return this._inputModel.getValue();
}
set input(value: string) {
set value(value: string) {
this._inputModel.setValue(value);
this._inputEditor.setPosition(this._inputModel.getFullModelRange().getEndPosition());
}
@ -367,14 +369,18 @@ export class InteractiveEditorWidget {
this._onDidChangeHeight.fire();
}
updateMarkdownMessage(message: Node) {
const messageDom = this._elements.message;
reset(messageDom, message);
this._elements.markdownMessage.classList.toggle('hidden', false);
if (messageDom.scrollHeight > messageDom.clientHeight) {
this._ctxMessageCropState.set('cropped');
updateMarkdownMessage(message: Node | undefined) {
this._elements.markdownMessage.classList.toggle('hidden', !message);
if (!message) {
reset(this._elements.message);
} else {
this._ctxMessageCropState.set('not_cropped');
reset(this._elements.message, message);
if (this._elements.message.scrollHeight > this._elements.message.clientHeight) {
this._ctxMessageCropState.set('cropped');
} else {
this._ctxMessageCropState.set('not_cropped');
}
}
this._onDidChangeHeight.fire();
}
@ -387,8 +393,6 @@ export class InteractiveEditorWidget {
setTimeout(() => {
this.updateStatus(statusLabel, { classes, keepMessage: true });
}, ops.resetAfter);
} else if (!isTempMessage && !ops.keepMessage) {
this._elements.markdownMessage.classList.toggle('hidden', true);
}
reset(this._elements.statusLabel, message);
this._elements.statusLabel.className = `label ${(ops.classes ?? []).join(' ')}`;
@ -403,6 +407,9 @@ export class InteractiveEditorWidget {
reset() {
this._ctxInputEmpty.reset();
this.value = '';
this.updateMarkdownMessage(undefined);
reset(this._elements.statusLabel);
this._elements.statusLabel.classList.toggle('hidden', true);
this._elements.statusToolbar.classList.add('hidden');
@ -424,6 +431,11 @@ export class InteractiveEditorWidget {
// --- preview
showEditsPreview(textModelv0: ITextModel, edits: ISingleEditOperation[], changes: LineRangeMapping[]) {
if (changes.length === 0) {
this.hideEditsPreview();
return;
}
this._elements.previewDiff.classList.remove('hidden');
const languageSelection: ILanguageSelection = { languageId: textModelv0.getLanguageId(), onDidChange: Event.None };
@ -445,8 +457,10 @@ export class InteractiveEditorWidget {
modifiedLineRange = new LineRange(newStartLine, modifiedLineRange.endLineNumberExclusive);
originalLineRange = new LineRange(newStartLine, originalLineRange.endLineNumberExclusive);
modifiedLineRange = new LineRange(modifiedLineRange.startLineNumber, modifiedLineRange.endLineNumberExclusive + pad);
originalLineRange = new LineRange(originalLineRange.startLineNumber, originalLineRange.endLineNumberExclusive + pad);
const newEndLineModified = Math.min(modifiedLineRange.endLineNumberExclusive + pad, modified.getLineCount());
modifiedLineRange = new LineRange(modifiedLineRange.startLineNumber, newEndLineModified);
const newEndLineOriginal = Math.min(originalLineRange.endLineNumberExclusive + pad, textModelv0.getLineCount());
originalLineRange = new LineRange(originalLineRange.startLineNumber, newEndLineOriginal);
const hiddenOriginal = invertLineRange(originalLineRange, textModelv0);
const hiddenModified = invertLineRange(modifiedLineRange, modified);
@ -470,7 +484,8 @@ export class InteractiveEditorWidget {
this._previewCreateTitle.element.setFile(uri, { fileKind: FileKind.FILE });
const model = this._modelService.createModel('', null, undefined, true);
const langSelection = this._languageService.createByFilepathOrFirstLine(uri, undefined);
const model = this._modelService.createModel('', langSelection, undefined, true);
model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
this._previewCreateModel.value = model;
this._previewCreateEditor.setModel(model);

View file

@ -5,6 +5,8 @@
import { timeout } from 'vs/base/common/async';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { isEqual } from 'vs/base/common/resources';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { localize } from 'vs/nls';
@ -16,8 +18,10 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { InteractiveEditorController } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController';
import { CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor';
import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellEditState, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
@ -33,6 +37,17 @@ const NOTEBOOK_CURSOR_PAGEUP_SELECT_COMMAND_ID = 'notebook.cell.cursorPageUpSele
const NOTEBOOK_CURSOR_PAGEDOWN_COMMAND_ID = 'notebook.cell.cursorPageDown';
const NOTEBOOK_CURSOR_PAGEDOWN_SELECT_COMMAND_ID = 'notebook.cell.cursorPageDownSelect';
function findTargetCellEditor(context: INotebookCellActionContext, targetCell: ICellViewModel) {
let foundEditor: ICodeEditor | undefined = undefined;
for (const [, codeEditor] of context.notebookEditor.codeEditors) {
if (isEqual(codeEditor.getModel()?.uri, targetCell.uri)) {
foundEditor = codeEditor;
break;
}
}
return foundEditor;
}
registerAction2(class FocusNextCellAction extends NotebookCellAction {
constructor() {
@ -51,6 +66,7 @@ registerAction2(class FocusNextCellAction extends NotebookCellAction {
NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'),
NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'),
),
EditorContextKeys.isEmbeddedDiffEditor.negate()
),
primary: KeyCode.DownArrow,
weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, // code cell keybinding, focus inside editor: lower weight to not override suggest widget
@ -63,7 +79,8 @@ registerAction2(class FocusNextCellAction extends NotebookCellAction {
ContextKeyExpr.and(
NOTEBOOK_CELL_TYPE.isEqualTo('markup'),
NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.isEqualTo(false),
NOTEBOOK_CURSOR_NAVIGATION_MODE)
NOTEBOOK_CURSOR_NAVIGATION_MODE),
EditorContextKeys.isEmbeddedDiffEditor.negate()
),
primary: KeyCode.DownArrow,
weight: KeybindingWeight.WorkbenchContrib, // markdown keybinding, focus on list: higher weight to override list.focusDown
@ -73,6 +90,39 @@ registerAction2(class FocusNextCellAction extends NotebookCellAction {
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
mac: { primary: KeyMod.WinCtrl | KeyMod.CtrlCmd | KeyCode.DownArrow, },
weight: KeybindingWeight.WorkbenchContrib
},
{
when: ContextKeyExpr.and(
NOTEBOOK_EDITOR_FOCUSED,
CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(),
ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true),
ContextKeyExpr.and(
ContextKeyExpr.has(InputFocusedContextKey),
NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'),
NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'),
),
CTX_INTERACTIVE_EDITOR_FOCUSED,
CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST,
EditorContextKeys.isEmbeddedDiffEditor.negate()
),
primary: KeyCode.DownArrow,
weight: KeybindingWeight.EditorCore
},
{
when: ContextKeyExpr.and(
NOTEBOOK_EDITOR_FOCUSED,
CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(),
ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true),
ContextKeyExpr.and(
NOTEBOOK_CELL_TYPE.isEqualTo('markup'),
NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.isEqualTo(false),
NOTEBOOK_CURSOR_NAVIGATION_MODE),
CTX_INTERACTIVE_EDITOR_FOCUSED,
CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST,
EditorContextKeys.isEmbeddedDiffEditor.negate()
),
primary: KeyCode.DownArrow,
weight: KeybindingWeight.EditorCore
}
]
});
@ -92,9 +142,17 @@ registerAction2(class FocusNextCellAction extends NotebookCellAction {
return;
}
const newCell = editor.cellAt(idx + 1);
const newFocusMode = newCell.cellKind === CellKind.Markup && newCell.getEditState() === CellEditState.Preview ? 'container' : 'editor';
await editor.focusNotebookCell(newCell, newFocusMode, { focusEditorLine: 1 });
const focusEditorLine = activeCell.textBuffer.getLineCount();
const targetCell = (context.cell ?? context.selectedCells?.[0]);
const foundEditor: ICodeEditor | undefined = targetCell ? findTargetCellEditor(context, targetCell) : undefined;
if (foundEditor && foundEditor.hasTextFocus() && InteractiveEditorController.get(foundEditor)?.getWidgetPosition()?.lineNumber === focusEditorLine) {
InteractiveEditorController.get(foundEditor)?.focus();
} else {
const newCell = editor.cellAt(idx + 1);
const newFocusMode = newCell.cellKind === CellKind.Markup && newCell.getEditState() === CellEditState.Preview ? 'container' : 'editor';
await editor.focusNotebookCell(newCell, newFocusMode, { focusEditorLine: 1 });
}
}
});
@ -117,6 +175,7 @@ registerAction2(class FocusPreviousCellAction extends NotebookCellAction {
NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'),
NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'),
),
EditorContextKeys.isEmbeddedDiffEditor.negate()
),
primary: KeyCode.UpArrow,
weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, // code cell keybinding, focus inside editor: lower weight to not override suggest widget
@ -130,7 +189,8 @@ registerAction2(class FocusPreviousCellAction extends NotebookCellAction {
NOTEBOOK_CELL_TYPE.isEqualTo('markup'),
NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.isEqualTo(false),
NOTEBOOK_CURSOR_NAVIGATION_MODE
)
),
EditorContextKeys.isEmbeddedDiffEditor.negate()
),
primary: KeyCode.UpArrow,
weight: KeybindingWeight.WorkbenchContrib, // markdown keybinding, focus on list: higher weight to override list.focusDown
@ -155,7 +215,14 @@ registerAction2(class FocusPreviousCellAction extends NotebookCellAction {
const newCell = editor.cellAt(idx - 1);
const newFocusMode = newCell.cellKind === CellKind.Markup && newCell.getEditState() === CellEditState.Preview ? 'container' : 'editor';
await editor.focusNotebookCell(newCell, newFocusMode, { focusEditorLine: newCell.textBuffer.getLineCount() });
const focusEditorLine = newCell.textBuffer.getLineCount();
await editor.focusNotebookCell(newCell, newFocusMode, { focusEditorLine: focusEditorLine });
const foundEditor: ICodeEditor | undefined = findTargetCellEditor(context, newCell);
if (foundEditor && InteractiveEditorController.get(foundEditor)?.getWidgetPosition()?.lineNumber === focusEditorLine) {
InteractiveEditorController.get(foundEditor)?.focus();
}
}
});

View file

@ -151,7 +151,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
@INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService,
) {
super(NotebookTextDiffEditor.ID, telemetryService, themeService, storageService);
this._notebookOptions = new NotebookOptions(this.configurationService, notebookExecutionStateService);
this._notebookOptions = new NotebookOptions(this.configurationService, notebookExecutionStateService, false);
this._register(this._notebookOptions);
const editorOptions = this.configurationService.getValue<ICodeEditorOptions>('editor');
this._fontInfo = FontMeasurements.readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.value));

View file

@ -292,7 +292,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this.isEmbedded = creationOptions.isEmbedded ?? false;
this._readOnly = creationOptions.isReadOnly ?? false;
this._notebookOptions = creationOptions.options ?? new NotebookOptions(this.configurationService, notebookExecutionStateService);
this._notebookOptions = creationOptions.options ?? new NotebookOptions(this.configurationService, notebookExecutionStateService, this._readOnly);
this._register(this._notebookOptions);
this._viewContext = new ViewContext(
this._notebookOptions,
@ -1176,6 +1176,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
}
this.viewModel.updateOptions({ isReadOnly: this._readOnly });
this.notebookOptions.updateOptions(this._readOnly);
// reveal cell if editor options tell to do so
const cellOptions = options?.cellOptions ?? this._parseIndexedCellOptions(options);
@ -1364,6 +1365,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this._viewContext, this.getLayoutInfo(), { isReadOnly: this._readOnly });
this._viewContext.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
this.notebookOptions.updateOptions(this._readOnly);
this._updateForOptions();
this._updateForNotebookConfiguration();

View file

@ -133,6 +133,7 @@ export class NotebookOptions extends Disposable {
constructor(
private readonly configurationService: IConfigurationService,
private readonly notebookExecutionStateService: INotebookExecutionStateService,
private isReadonly: boolean,
private readonly overrides?: { cellToolbarInteraction: string; globalToolbar: boolean; dragAndDropEnabled: boolean }
) {
super();
@ -145,7 +146,7 @@ export class NotebookOptions extends Disposable {
const cellToolbarInteraction = overrides?.cellToolbarInteraction ?? this.configurationService.getValue<string>(NotebookSetting.cellToolbarVisibility);
const compactView = this.configurationService.getValue<boolean | undefined>(NotebookSetting.compactView) ?? true;
const focusIndicator = this._computeFocusIndicatorOption();
const insertToolbarPosition = this._computeInsertToolbarPositionOption();
const insertToolbarPosition = this._computeInsertToolbarPositionOption(this.isReadonly);
const insertToolbarAlignment = this._computeInsertToolbarAlignmentOption();
const showFoldingControls = this._computeShowFoldingControlsOption();
// const { bottomToolbarGap, bottomToolbarHeight } = this._computeBottomToolbarDimensions(compactView, insertToolbarPosition, insertToolbarAlignment);
@ -248,6 +249,22 @@ export class NotebookOptions extends Disposable {
}));
}
updateOptions(isReadonly: boolean) {
if (this.isReadonly !== isReadonly) {
this.isReadonly = isReadonly;
this._updateConfiguration({
affectsConfiguration(configuration: string): boolean {
return configuration === NotebookSetting.insertToolbarLocation;
},
source: ConfigurationTarget.DEFAULT,
affectedKeys: new Set([NotebookSetting.insertToolbarLocation]),
change: { keys: [NotebookSetting.insertToolbarLocation], overrides: [] },
sourceConfig: undefined
});
}
}
private _migrateDeprecatedSetting(deprecatedKey: string, key: string): void {
const deprecatedSetting = this.configurationService.inspect(deprecatedKey);
@ -390,7 +407,7 @@ export class NotebookOptions extends Disposable {
}
if (insertToolbarPosition) {
configuration.insertToolbarPosition = this._computeInsertToolbarPositionOption();
configuration.insertToolbarPosition = this._computeInsertToolbarPositionOption(this.isReadonly);
}
if (globalToolbar && this.overrides?.globalToolbar === undefined) {
@ -479,8 +496,8 @@ export class NotebookOptions extends Disposable {
});
}
private _computeInsertToolbarPositionOption() {
return this.configurationService.getValue<'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'>(NotebookSetting.insertToolbarLocation) ?? 'both';
private _computeInsertToolbarPositionOption(isReadOnly: boolean) {
return isReadOnly ? 'hidden' : this.configurationService.getValue<'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'>(NotebookSetting.insertToolbarLocation) ?? 'both';
}
private _computeInsertToolbarAlignmentOption() {

View file

@ -202,7 +202,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
// recompute
this._ensureOutputsTop();
const notebookLayoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration();
const bottomToolbarDimensions = this.viewContext.notebookOptions.computeBottomToolbarDimensions();
const bottomToolbarDimensions = this.viewContext.notebookOptions.computeBottomToolbarDimensions(this.viewType);
const outputShowMoreContainerHeight = state.outputShowMoreContainerHeight ? state.outputShowMoreContainerHeight : this._layoutInfo.outputShowMoreContainerHeight;
const outputTotalHeight = Math.max(this._outputMinHeight, this.isOutputCollapsed ? notebookLayoutConfiguration.collapsedIndicatorHeight : this._outputsTop!.getTotalSum());
const commentHeight = state.commentHeight ? this._commentHeight : this._layoutInfo.commentHeight;

View file

@ -21,7 +21,7 @@ suite('NotebookCellList', () => {
suiteSetup(() => {
disposables = new DisposableStore();
instantiationService = setupInstantiationService(disposables);
notebookDefaultOptions = new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService));
notebookDefaultOptions = new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false);
topInsertToolbarHeight = notebookDefaultOptions.computeTopInsertToolbarHeight();
});

View file

@ -58,7 +58,7 @@ suite('NotebookViewModel', () => {
test('ctor', function () {
const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService);
const model = new NotebookEditorTestModel(notebook);
const viewContext = new ViewContext(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService)), new NotebookEventDispatcher(), () => ({} as IBaseCellEditorOptions));
const viewContext = new ViewContext(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false), new NotebookEventDispatcher(), () => ({} as IBaseCellEditorOptions));
const viewModel = new NotebookViewModel('notebook', model.notebook, viewContext, null, { isReadOnly: false }, instantiationService, bulkEditService, undoRedoService, textModelService, notebookExecutionStateService);
assert.strictEqual(viewModel.viewType, 'notebook');
});

View file

@ -201,7 +201,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic
}), {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, cellContentMetadata: {}, transientOutputs: false });
const model = new NotebookEditorTestModel(notebook);
const notebookOptions = new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService));
const notebookOptions = new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false);
const viewContext = new ViewContext(notebookOptions, new NotebookEventDispatcher(), () => ({} as IBaseCellEditorOptions));
const viewModel: NotebookViewModel = instantiationService.createInstance(NotebookViewModel, viewType, model.notebook, viewContext, null, { isReadOnly: false });
@ -374,7 +374,7 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe
NotebookCellList,
'NotebookCellList',
DOM.$('container'),
viewContext ?? new ViewContext(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService)), new NotebookEventDispatcher(), () => ({} as IBaseCellEditorOptions)),
viewContext ?? new ViewContext(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false), new NotebookEventDispatcher(), () => ({} as IBaseCellEditorOptions)),
delegate,
[renderer],
instantiationService.get<IContextKeyService>(IContextKeyService),

View file

@ -21,7 +21,7 @@ import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { isWeb, OperatingSystem } from 'vs/base/common/platform';
import { ITunnelService, RemoteTunnel } from 'vs/platform/tunnel/common/tunnel';
import { ITunnelService, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
@ -348,6 +348,10 @@ class OnAutoForwardedAction extends Disposable {
choices.unshift(this.elevateChoice(tunnel));
}
if (tunnel.privacy === TunnelPrivacyId.Private && isWeb && this.tunnelService.canChangePrivacy) {
choices.push(this.makePublicChoice(tunnel));
}
message += this.linkMessage();
this.lastNotification = this.notificationService.prompt(Severity.Info, message, choices, { neverShowAgain: { id: 'remote.tunnelsView.autoForwardNeverShow', isSecondary: true } });
@ -359,6 +363,20 @@ class OnAutoForwardedAction extends Disposable {
});
}
private makePublicChoice(tunnel: RemoteTunnel): IPromptChoice {
return {
label: nls.localize('remote.tunnelsView.makePublic', "Make Public"),
run: async () => {
await this.remoteExplorerService.close({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, TunnelCloseReason.Other);
return this.remoteExplorerService.forward({
remote: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort },
local: tunnel.tunnelLocalPort,
privacy: TunnelPrivacyId.Public,
});
}
};
}
private openBrowserChoice(tunnel: RemoteTunnel): IPromptChoice {
const address = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort);
return {

View file

@ -86,24 +86,19 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
if (isWeb) {
const remoteExtensionTips = this.productService.remoteExtensionTips?.['tunnel'];
if (remoteExtensionTips) {
const metadata: RemoteExtensionMetadata = {
id: remoteExtensionTips.extensionId,
installed: false,
friendlyName: remoteExtensionTips.friendlyName,
remoteCommands: [],
isPlatformCompatible: false,
dependencies: [],
helpLink: remoteExtensionTips.startEntry?.helpLink ?? '',
startConnectLabel: remoteExtensionTips.startEntry?.startConnectLabel ?? '',
startCommand: remoteExtensionTips.startEntry?.startCommand ?? '',
priority: remoteExtensionTips.startEntry?.priority ?? 10
this.remoteExtensionMetadata = remoteExtensionTips ? [{
id: remoteExtensionTips.extensionId,
installed: false,
friendlyName: remoteExtensionTips.friendlyName,
remoteCommands: [],
isPlatformCompatible: false,
dependencies: [],
helpLink: remoteExtensionTips.startEntry?.helpLink ?? '',
startConnectLabel: remoteExtensionTips.startEntry?.startConnectLabel ?? '',
startCommand: remoteExtensionTips.startEntry?.startCommand ?? '',
priority: remoteExtensionTips.startEntry?.priority ?? 10
};
this.remoteExtensionMetadata = [metadata];
} else {
this.remoteExtensionMetadata = [];
}
}] : [];
}
else {
const remoteExtensionTips = { ...this.productService.remoteExtensionTips, ...this.productService.virtualWorkspaceExtensionTips };
@ -325,38 +320,10 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
private async showRemoteTunnelStartActions() {
await this._init();
const computeItems = async () => {
const metadata = this.remoteExtensionMetadata[0];
if (!metadata) {
return [];
}
if (!metadata.installed) {
await this.installAndRunStartCommand(metadata);
}
return this.getRemoteCommandQuickPickItems(metadata.remoteCommands);
};
const quickPick = this.quickInputService.createQuickPick();
quickPick.placeholder = nls.localize('remote.startActions.quickPickPlaceholder', 'Select an option to connect');
quickPick.items = await computeItems();
quickPick.sortByLabel = false;
quickPick.canSelectMany = false;
quickPick.ignoreFocusOut = false;
once(quickPick.onDidAccept)(async () => {
const selectedItems = quickPick.selectedItems;
if (selectedItems.length === 1) {
const selectedItem = selectedItems[0].id!;
this.executeCommandWithTelemetry(selectedItem);
quickPick.dispose();
}
});
quickPick.onDidHide(() => quickPick.dispose());
quickPick.show();
const metadata = this.remoteExtensionMetadata[0];
if (metadata.installed) {
this.executeCommandWithTelemetry(metadata.startCommand);
}
}
private async installAndRunStartCommand(metadata: RemoteExtensionMetadata) {

View file

@ -11,7 +11,7 @@ import { VIEW_PANE_ID, ISCMService, ISCMRepository, ISCMViewService } from 'vs/w
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
import { IStatusbarEntry, IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { EditorResourceAccessor } from 'vs/workbench/common/editor';
@ -167,13 +167,18 @@ export class SCMStatusController implements IWorkbenchContribution {
repoAgnosticActionName = '';
}
disposables.add(this.statusbarService.addEntry({
const statusbarEntry: IStatusbarEntry = {
name: localize('status.scm', "Source Control") + (repoAgnosticActionName ? ` ${repoAgnosticActionName}` : ''),
text: command.title,
ariaLabel: tooltip,
tooltip,
command: command.id ? command : undefined
}, `status.scm.${index}`, MainThreadStatusBarAlignment.LEFT, 10000 - index));
};
disposables.add(index === 0 ?
this.statusbarService.addEntry(statusbarEntry, `status.scm.${index}`, MainThreadStatusBarAlignment.LEFT, 10000) :
this.statusbarService.addEntry(statusbarEntry, `status.scm.${index}`, MainThreadStatusBarAlignment.LEFT, { id: `status.scm.${index - 1}`, alignment: MainThreadStatusBarAlignment.RIGHT, compact: true })
);
}
this.statusBarDisposable = disposables;

View file

@ -38,6 +38,8 @@ export interface ITerminalStatusList {
/**
* Adds a status to the list.
* @param status The status object. Ideally a single status object that does not change will be
* shared as this call will no-op if the status is already set (checked by by object reference).
* @param duration An optional duration in milliseconds of the status, when specified the status
* will remove itself when the duration elapses unless the status gets re-added.
*/
@ -87,6 +89,11 @@ export class TerminalStatusList extends Disposable implements ITerminalStatusLis
const timeout = window.setTimeout(() => this.remove(status), duration);
this._statusTimeouts.set(status.id, timeout);
}
const existingStatus = this._statuses.get(status.id);
if (existingStatus && existingStatus !== status) {
this._onDidRemoveStatus.fire(existingStatus);
this._statuses.delete(existingStatus.id);
}
if (!this._statuses.has(status.id)) {
const oldPrimary = this.primary;
this._statuses.set(status.id, status);
@ -95,11 +102,6 @@ export class TerminalStatusList extends Disposable implements ITerminalStatusLis
if (oldPrimary !== newPrimary) {
this._onDidChangePrimaryStatus.fire(newPrimary);
}
} else {
this._statuses.set(status.id, status);
// It maybe the case that status hasn't changed, there isn't a good way to check this based on
// `ITerminalStatus`, so just fire the event anyway.
this._onDidAddStatus.fire(status);
}
}

View file

@ -130,6 +130,19 @@ suite('Workbench - TerminalStatusList', () => {
strictEqual(list.statuses[1].icon!.id, Codicon.zap.id, 'zap~spin should have animation removed only');
});
test('add should fire onDidRemoveStatus if same status id with a different object reference was added', () => {
const eventCalls: string[] = [];
list.onDidAddStatus(() => eventCalls.push('add'));
list.onDidRemoveStatus(() => eventCalls.push('remove'));
list.add({ id: 'test', severity: Severity.Info });
list.add({ id: 'test', severity: Severity.Info });
deepStrictEqual(eventCalls, [
'add',
'remove',
'add'
]);
});
test('remove', () => {
list.add({ id: 'info', severity: Severity.Info });
list.add({ id: 'warning', severity: Severity.Warning });

View file

@ -186,6 +186,18 @@ suite('ShellIntegrationAddon', () => {
await writeP(xterm, '\x1b]633;D;7\x07');
mock.verify();
});
test('should pass command line sequence to the capability', async () => {
const mock = shellIntegrationAddon.getCommandDetectionMock(xterm);
mock.expects('setCommandLine').once().withExactArgs('', false);
await writeP(xterm, '\x1b]633;E\x07');
mock.verify();
const mock2 = shellIntegrationAddon.getCommandDetectionMock(xterm);
mock2.expects('setCommandLine').twice().withExactArgs('cmd', false);
await writeP(xterm, '\x1b]633;E;cmd\x07');
await writeP(xterm, '\x1b]633;E;cmd;invalid-nonce\x07');
mock2.verify();
});
test('should not activate capability on the cwd sequence (OSC 633 ; P=Cwd=<cwd> ST)', async () => {
strictEqual(capabilities.has(TerminalCapability.CommandDetection), false);
await writeP(xterm, 'foo');

View file

@ -13,6 +13,7 @@ import { ITerminalCapabilityStore, TerminalCapability } from 'vs/platform/termin
import { IBufferLine, IBufferRange, Terminal } from 'xterm';
import { ITerminalBackend, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
import { detectLinks } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing';
import { ILogService } from 'vs/platform/log/common/log';
const enum Constants {
/**
@ -69,6 +70,7 @@ export class TerminalLocalLinkDetector implements ITerminalLinkDetector {
private readonly _capabilities: ITerminalCapabilityStore,
private readonly _processManager: Pick<ITerminalProcessManager, 'initialCwd' | 'os' | 'remoteAuthority' | 'userHome'> & { backend?: Pick<ITerminalBackend, 'getWslPath'> },
private readonly _linkResolver: ITerminalLinkResolver,
@ILogService private readonly _logService: ILogService,
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService
) {
@ -88,7 +90,10 @@ export class TerminalLocalLinkDetector implements ITerminalLinkDetector {
const os = this._processManager.os || OS;
const parsedLinks = detectLinks(text, os);
this._logService.trace('terminalLocaLinkDetector#detect text', text);
this._logService.trace('terminalLocaLinkDetector#detect parsedLinks', parsedLinks);
for (const parsedLink of parsedLinks) {
// Don't try resolve any links of excessive length
if (parsedLink.path.text.length > Constants.MaxResolvedLinkLength) {
continue;
@ -147,6 +152,7 @@ export class TerminalLocalLinkDetector implements ITerminalLinkDetector {
}
}
linkCandidates.push(...specialEndLinkCandidates);
this._logService.trace('terminalLocaLinkDetector#detect linkCandidates', linkCandidates);
// Validate the path and convert to the outgoing type
const simpleLink = await this._validateAndGetLink(undefined, bufferRange, linkCandidates, trimRangeMap);
@ -156,6 +162,7 @@ export class TerminalLocalLinkDetector implements ITerminalLinkDetector {
parsedLink.prefix?.index ?? parsedLink.path.index,
parsedLink.suffix ? parsedLink.suffix.suffix.index + parsedLink.suffix.suffix.text.length : parsedLink.path.index + parsedLink.path.text.length
);
this._logService.trace('terminalLocaLinkDetector#detect verified link', simpleLink);
links.push(simpleLink);
}

View file

@ -19,6 +19,7 @@ import { TerminalLinkResolver } from 'vs/workbench/contrib/terminalContrib/links
import { IFileService } from 'vs/platform/files/common/files';
import { createFileStat } from 'vs/workbench/test/common/workbenchTestServices';
import { URI } from 'vs/base/common/uri';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
const unixLinks: (string | { link: string; resource: URI })[] = [
// Absolute
@ -181,6 +182,7 @@ suite('Workbench - TerminalLocalLinkDetector', () => {
return createFileStat(resource);
}
});
instantiationService.stub(ILogService, new NullLogService());
resolver = instantiationService.createInstance(TerminalLinkResolver);
validResources = [];

View file

@ -14,6 +14,8 @@ import { stripIcons } from 'vs/base/common/iconLabels';
import { Iterable } from 'vs/base/common/iterator';
import { Disposable, DisposableStore, IReference, MutableDisposable } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { isMacintosh } from 'vs/base/common/platform';
import { ThemeIcon } from 'vs/base/common/themables';
import { Constants } from 'vs/base/common/uint';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
@ -21,7 +23,7 @@ import { ContentWidgetPositionPreference, ICodeEditor, IContentWidgetPosition, I
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { overviewRulerError, overviewRulerInfo } from 'vs/editor/common/core/editorColorRegistry';
import { IRange, Range } from 'vs/editor/common/core/range';
import { IRange } from 'vs/editor/common/core/range';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
@ -34,24 +36,22 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { ThemeIcon } from 'vs/base/common/themables';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { EditorLineNumberContextMenu, GutterActionsRegistry } from 'vs/workbench/contrib/codeEditor/browser/editorLineNumberMenu';
import { getTestItemContextOverlay } from 'vs/workbench/contrib/testing/browser/explorerProjections/testItemContextOverlay';
import { testingRunAllIcon, testingRunIcon, testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons';
import { DefaultGutterClickAction, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
import { labelForTestInState, Testing } from 'vs/workbench/contrib/testing/common/constants';
import { DefaultGutterClickAction, TestingConfigKeys, getTestingConfiguration } from 'vs/workbench/contrib/testing/common/configuration';
import { Testing, labelForTestInState } from 'vs/workbench/contrib/testing/common/constants';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { ITestDecoration as IPublicTestDecoration, ITestingDecorationsService, TestDecorations } from 'vs/workbench/contrib/testing/common/testingDecorations';
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
import { isFailedState, maxPriority } from 'vs/workbench/contrib/testing/common/testingStates';
import { buildTestUri, parseTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri';
import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { getContextForTestItem, ITestService, testsInFile } from 'vs/workbench/contrib/testing/common/testService';
import { IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, ITestRunProfile, TestDiffOpType, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { EditorLineNumberContextMenu, GutterActionsRegistry } from 'vs/workbench/contrib/codeEditor/browser/editorLineNumberMenu';
import { isMacintosh } from 'vs/base/common/platform';
import { ITestService, getContextForTestItem, testsInFile } from 'vs/workbench/contrib/testing/common/testService';
import { IRichLocation, ITestMessage, ITestRunProfile, IncrementalTestCollectionItem, InternalTestItem, TestDiffOpType, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { ITestDecoration as IPublicTestDecoration, ITestingDecorationsService, TestDecorations } from 'vs/workbench/contrib/testing/common/testingDecorations';
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
import { isFailedState, maxPriority } from 'vs/workbench/contrib/testing/common/testingStates';
import { TestUriType, buildTestUri, parseTestUri } from 'vs/workbench/contrib/testing/common/testingUri';
const MAX_INLINE_MESSAGE_LENGTH = 128;
@ -423,7 +423,7 @@ export class TestingDecorations extends Disposable implements IEditorContributio
this._register(this.editor.onDidChangeModel(e => this.attachModel(e.newModelUrl || undefined)));
this._register(this.editor.onMouseDown(e => {
if (e.target.position && this.currentUri) {
const modelDecorations = editor.getModel()?.getDecorationsInRange(Range.fromPositions(e.target.position)) ?? [];
const modelDecorations = editor.getModel()?.getLineDecorations(e.target.position.lineNumber) ?? [];
if (!modelDecorations.length) {
return;
}

View file

@ -5,7 +5,7 @@
import { Event, EventMultiplexer } from 'vs/base/common/event';
import {
ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallVSIXOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation
ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallVSIXOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SYNC_CONTEXT
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { DidChangeProfileForServerEvent, DidUninstallExtensionOnServerEvent, IExtensionManagementServer, IExtensionManagementServerService, InstallExtensionOnServerEvent, IWorkbenchExtensionManagementService, UninstallExtensionOnServerEvent } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionType, isLanguagePackExtension, IExtensionManifest, getWorkspaceSupportTypeMessage, TargetPlatform } from 'vs/platform/extensions/common/extensions';
@ -333,7 +333,9 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
}
}
await this.checkForWorkspaceTrust(manifest);
if (!installOptions.context?.[EXTENSION_INSTALL_SYNC_CONTEXT]) {
await this.checkForWorkspaceTrust(manifest);
}
if (!installOptions.donotIncludePackAndDependencies) {
await this.checkInstallingExtensionOnWeb(gallery, manifest);
}

View file

@ -6,6 +6,7 @@
// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.
export const allApiProposals = Object.freeze({
authGetSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authGetSessions.d.ts',
authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts',
codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts',
commentsDraftState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts',
@ -41,7 +42,6 @@ export const allApiProposals = Object.freeze({
findTextInFiles: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts',
formatMultipleRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.formatMultipleRanges.d.ts',
fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts',
getSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.getSessions.d.ts',
handleIssueUri: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.handleIssueUri.d.ts',
idToken: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.idToken.d.ts',
indentSize: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.indentSize.d.ts',

View file

@ -15648,7 +15648,14 @@ declare module 'vscode' {
readonly label: string;
}
/**
* Optional options to be used when calling {@link authentication.getSession} with the flag `forceNewSession`.
*/
export interface AuthenticationForceNewSessionOptions {
/**
* An optional message that will be displayed to the user when we ask to re-authenticate. Providing additional context
* as to why you are asking a user to re-authenticate can help increase the odds that they will accept.
*/
detail?: string;
}

View file

@ -8,6 +8,10 @@ declare module 'vscode' {
// https://github.com/microsoft/vscode/issues/152399
export interface AuthenticationForceNewSessionOptions {
/**
* The session that you are asking to be recreated. The Auth Provider can use this to
* help guide the user to log in to the correct account.
*/
sessionToRecreate?: AuthenticationSession;
}

View file

@ -56,6 +56,13 @@ declare module 'vscode' {
*/
label: string;
/**
* The relative priority of this edit. Higher priority items are shown first in the UI.
*
* Defaults to `0`.
*/
priority?: number;
/**
* The text or snippet to insert at the pasted locations.
*/

View file

@ -13,7 +13,14 @@ declare module 'vscode' {
*
* This id should be unique within the extension but does not need to be unique across extensions.
*/
id: string;
id?: string;
/**
* The relative priority of this edit. Higher priority items are shown first in the UI.
*
* Defaults to `0`.
*/
priority?: number;
/**
* Human readable label that describes the edit.