From 5d13ebf0d2b1afa18e18dde4910cd6fe0b25fd77 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Fri, 28 Feb 2020 00:37:06 -0800 Subject: [PATCH] Merge from vscode e5834d3280fcd04898efeac32b9cf1b893f9b127 (#9385) * Merge from vscode e5834d3280fcd04898efeac32b9cf1b893f9b127 * distro --- build/gulpfile.hygiene.js | 3 +- extensions/git/package.json | 14 +- extensions/git/package.nls.json | 14 +- extensions/git/src/commands.ts | 8 +- .../github-authentication/src/extension.ts | 2 +- .../github-authentication/src/github.ts | 6 +- extensions/image-preview/package.json | 2 +- .../markdown-language-features/package.json | 2 +- extensions/vscode-account/package.json | 3 + extensions/vscode-account/package.nls.json | 4 +- extensions/vscode-account/src/AADHelper.ts | 6 +- extensions/vscode-account/src/extension.ts | 7 +- extensions/vscode-account/yarn.lock | 5 + package.json | 4 +- resources/linux/snap/electron-launch | 20 +- .../ui/codiconLabel/codicon/codicon.css | 7 +- .../ui/codiconLabel/codicon/codicon.ttf | Bin 57012 -> 57380 bytes src/vs/base/browser/ui/list/listView.ts | 2 +- src/vs/base/browser/ui/tree/abstractTree.ts | 2 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 2 +- src/vs/base/common/linkedText.ts | 4 +- .../parts/composite/browser/compositeDnd.ts | 1 + .../parts/quickinput/browser/quickInput.ts | 11 +- .../parts/quickinput/common/quickInput.ts | 6 +- src/vs/base/parts/storage/common/storage.ts | 10 +- .../parts/storage/test/node/storage.test.ts | 19 +- src/vs/base/test/common/linkedText.test.ts | 15 ++ .../processExplorer/processExplorerMain.ts | 6 +- src/vs/code/electron-main/window.ts | 15 +- .../browser/services/bulkEditService.ts | 1 + .../browser/viewParts/minimap/minimap.ts | 8 +- src/vs/editor/browser/widget/diffReview.ts | 23 ++- .../browser/widget/media/diffReview.css | 9 +- src/vs/editor/common/config/editorOptions.ts | 64 +++--- src/vs/editor/common/controller/cursor.ts | 13 +- src/vs/editor/common/modes.ts | 4 +- .../common/standalone/standaloneEnums.ts | 2 +- src/vs/editor/contrib/clipboard/clipboard.ts | 1 + .../contrib/codeAction/codeActionCommands.ts | 2 +- src/vs/editor/contrib/folding/folding.ts | 16 +- .../link/goToDefinitionAtPosition.ts | 3 +- src/vs/editor/contrib/links/getLinks.ts | 4 +- src/vs/editor/contrib/rename/rename.ts | 3 +- .../editor/contrib/suggest/media/suggest.css | 6 +- .../editor/contrib/suggest/suggestWidget.ts | 2 +- .../viewLayout/editorLayoutProvider.test.ts | 12 +- src/vs/monaco.d.ts | 19 +- .../environment/node/environmentService.ts | 2 +- .../notification/common/notification.ts | 31 +++ .../storage/browser/storageService.ts | 29 ++- src/vs/platform/storage/node/storageIpc.ts | 23 ++- .../platform/storage/node/storageService.ts | 10 +- .../undoRedo/common/undoRedoService.ts | 23 ++- .../common/abstractSynchronizer.ts | 10 +- .../userDataSync/common/extensionsSync.ts | 2 +- .../userDataSync/common/globalStateSync.ts | 2 +- .../common/userDataAutoSyncService.ts | 11 +- .../userDataSync/common/userDataSync.ts | 4 +- .../userDataSync/common/userDataSyncIpc.ts | 2 +- .../common/userDataSyncService.ts | 4 +- .../userDataAutoSyncService.ts | 13 +- src/vs/vscode.d.ts | 29 ++- src/vs/vscode.proposed.d.ts | 55 +++--- .../api/browser/mainThreadAuthentication.ts | 56 +++--- .../api/browser/mainThreadWebview.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostAuthentication.ts | 8 +- .../workbench/api/common/extHostTimeline.ts | 14 +- src/vs/workbench/api/common/extHostWebview.ts | 181 +++++++++-------- .../browser/actions/layoutActions.ts | 78 ++++---- .../activitybar/media/activitybarpart.css | 4 + .../workbench/browser/parts/compositeBar.ts | 182 ++++++++++++------ .../browser/parts/compositeBarActions.ts | 8 +- .../browser/parts/editor/editorStatus.ts | 100 +++++++--- .../notifications/notificationsActions.ts | 22 +-- .../notifications/notificationsCenter.ts | 20 +- .../notifications/notificationsCommands.ts | 2 + .../notifications/notificationsStatus.ts | 5 +- .../notifications/notificationsToasts.ts | 39 ++-- .../browser/parts/panel/media/panelpart.css | 4 + .../browser/parts/panel/panelPart.ts | 3 +- .../browser/parts/views/viewPaneContainer.ts | 8 +- src/vs/workbench/common/notifications.ts | 44 ++++- src/vs/workbench/common/views.ts | 19 +- .../bulkEdit/browser/bulkEdit.contribution.ts | 5 +- .../browser/toggleColumnSelection.ts | 63 +++++- .../browser/toggleMultiCursorModifier.ts | 4 +- .../electron-browser/selectionClipboard.ts | 13 +- .../customEditor/browser/customEditors.ts | 6 +- .../customEditor/common/customEditor.ts | 2 +- .../customEditor/common/customEditorModel.ts | 12 +- .../debug/browser/debug.contribution.ts | 2 +- .../browser/debugConfigurationManager.ts | 9 +- .../contrib/debug/browser/debugHover.ts | 2 +- .../contrib/debug/browser/debugSession.ts | 100 +++++----- .../contrib/debug/browser/startView.ts | 52 ++--- .../browser/extensions.contribution.ts | 3 +- .../extensions/browser/extensionsActions.ts | 2 +- .../contrib/files/browser/explorerViewlet.ts | 12 +- .../files/browser/media/explorerviewlet.css | 4 +- .../files/browser/views/openEditorsView.ts | 2 +- .../markers/browser/markersTreeViewer.ts | 3 +- .../contrib/markers/browser/markersView.ts | 1 - .../preferences/browser/settingsTree.ts | 5 +- .../contrib/scm/browser/scmViewlet.ts | 2 +- .../contrib/searchEditor/browser/constants.ts | 3 + .../browser/searchEditor.contribution.ts | 45 +++-- .../searchEditor/browser/searchEditor.ts | 5 +- .../browser/searchEditorActions.ts | 18 ++ .../searchEditor/browser/searchEditorInput.ts | 4 +- .../browser/searchEditorSerialization.ts | 2 +- .../tasks/browser/abstractTaskService.ts | 148 +++++++++++--- .../tasks/browser/providerProgressManager.ts | 61 ++++++ .../contrib/tasks/common/taskConfiguration.ts | 11 ++ .../contrib/terminal/browser/terminalView.ts | 4 +- .../contrib/timeline/browser/timelinePane.ts | 12 +- .../browser/userDataAutoSyncService.ts | 13 +- .../userDataSync/browser/userDataSync.ts | 113 +++++++---- .../browser/userDataSyncTrigger.ts | 39 ++-- .../common/viewsWelcomeContribution.ts | 10 +- .../common/viewsWelcomeExtensionPoint.ts | 15 +- .../bulkEdit/browser/bulkEditService.ts | 10 +- .../common/configurationResolverSchema.ts | 2 +- .../browser/abstractFileDialogService.ts | 4 + .../dialogs/browser/simpleFileDialog.ts | 11 +- .../electron-browser/fileDialogService.ts | 2 +- .../environment/browser/environmentService.ts | 2 +- .../common/extensionManagementService.ts | 2 +- .../progress/browser/progressService.ts | 68 ++++++- .../textfile/common/textFileEditorModel.ts | 23 ++- .../userDataAutoSyncService.ts | 4 +- .../electron-browser/userDataSyncService.ts | 2 +- .../test/common/notifications.test.ts | 20 +- test/automation/package.json | 3 +- test/automation/src/code.ts | 117 +++++------ test/automation/src/playwrightDriver.ts | 12 +- test/automation/yarn.lock | 5 + test/smoke/README.md | 4 +- test/smoke/src/areas/css/css.test.ts | 43 ----- .../src/areas/workbench/localization.test.ts | 6 +- test/smoke/src/main.ts | 40 ++-- test/unit/README.md | 7 +- yarn.lock | 8 +- 143 files changed, 1711 insertions(+), 934 deletions(-) create mode 100644 src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts delete mode 100644 test/smoke/src/areas/css/css.test.ts diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 09269be8da..f5225d9373 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -103,7 +103,8 @@ const indentationFilter = [ '!extensions/mssql/notebooks/**', '!extensions/integration-tests/testData/**', '!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts', - '!extensions/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts' + '!extensions/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts', + '!resources/linux/snap/electron-launch' ]; const copyrightFilter = [ diff --git a/extensions/git/package.json b/extensions/git/package.json index 1eaf39ad44..5246020c6c 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1825,37 +1825,37 @@ }, "viewsWelcome": [ { - "view": "workbench.scm", + "view": "scm", "contents": "%view.workbench.scm.disabled%", "when": "!config.git.enabled" }, { - "view": "workbench.scm", + "view": "scm", "contents": "%view.workbench.scm.missing%", "when": "config.git.enabled && git.missing" }, { - "view": "workbench.scm", + "view": "scm", "contents": "%view.workbench.scm.empty%", "when": "config.git.enabled && !git.missing && workbenchState == empty" }, { - "view": "workbench.scm", + "view": "scm", "contents": "%view.workbench.scm.folder%", "when": "config.git.enabled && !git.missing && workbenchState == folder" }, { - "view": "workbench.scm", + "view": "scm", "contents": "%view.workbench.scm.workspace%", "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0" }, { - "view": "workbench.scm", + "view": "scm", "contents": "%view.workbench.scm.emptyWorkspace%", "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0" }, { - "view": "workbench.explorer.emptyView", + "view": "explorer", "contents": "%view.workbench.cloneRepository%" } ] diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index d6a2792861..c9d641e346 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -150,11 +150,11 @@ "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", "colors.submodule": "Color for submodule resources.", - "view.workbench.scm.missing": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use Git and source control in Azure Data Studio in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.", - "view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use Git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.scm.emptyWorkspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.cloneRepository": "You can also clone a repository from a URL. To learn more about how to use Git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).\n[Clone Repository](command:git.clone)" + "view.workbench.scm.missing": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use git and source control in Azure Data Studio in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.", + "view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.emptyWorkspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.cloneRepository": "You can also clone a repository from a URL. To learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).\n[Clone Repository](command:git.clone)" } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index a44f5c6302..95fb2a01de 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1397,12 +1397,16 @@ export class CommandCenter { opts.signoff = true; } + const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges'); + if ( ( // no changes (noStagedChanges && noUnstagedChanges) // or no staged changes and not `all` || (!opts.all && noStagedChanges) + // no staged changes and no tracked unstaged changes + || (noStagedChanges && smartCommitChanges === 'tracked' && repository.workingTreeGroup.resourceStates.every(r => r.type === Status.UNTRACKED)) ) && !opts.empty ) { @@ -1416,7 +1420,7 @@ export class CommandCenter { return false; } - if (opts.all && config.get<'all' | 'tracked'>('smartCommitChanges') === 'tracked') { + if (opts.all && smartCommitChanges === 'tracked') { opts.all = 'tracked'; } @@ -2353,7 +2357,7 @@ export class CommandCenter { else if (item.previousRef === 'HEAD' && item.ref === '~') { title = localize('git.title.index', '{0} (Index)', basename); } else { - title = localize('git.title.diffRefs', '{0} ({1}) \u27f7 {0} ({2})', basename, item.shortPreviousRef, item.shortRef); + title = localize('git.title.diffRefs', '{0} ({1}) ⟷ {0} ({2})', basename, item.shortPreviousRef, item.shortRef); } return commands.executeCommand('vscode.diff', toGitUri(uri, item.previousRef), item.ref === '' ? uri : toGitUri(uri, item.ref), title); diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts index ba7e99a7cf..c6008efdf8 100644 --- a/extensions/github-authentication/src/extension.ts +++ b/extensions/github-authentication/src/extension.ts @@ -16,7 +16,7 @@ export async function activate(context: vscode.ExtensionContext) { await loginService.initialize(); vscode.authentication.registerAuthenticationProvider({ - id: 'GitHub', + id: 'github', displayName: 'GitHub', onDidChangeSessions: onDidChangeSessions.event, getSessions: () => Promise.resolve(loginService.sessions), diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index d9f3c06d22..4bfa0feff3 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -71,7 +71,7 @@ export class GitHubAuthenticationProvider { id: session.id, accountName: session.accountName, scopes: session.scopes, - accessToken: () => Promise.resolve(session.accessToken) + getAccessToken: () => Promise.resolve(session.accessToken) }; }); } catch (e) { @@ -84,7 +84,7 @@ export class GitHubAuthenticationProvider { private async storeSessions(): Promise { const sessionData: SessionData[] = await Promise.all(this._sessions.map(async session => { - const resolvedAccessToken = await session.accessToken(); + const resolvedAccessToken = await session.getAccessToken(); return { id: session.id, accountName: session.accountName, @@ -111,7 +111,7 @@ export class GitHubAuthenticationProvider { const userInfo = await this._githubServer.getUserInfo(token); return { id: userInfo.id, - accessToken: () => Promise.resolve(token), + getAccessToken: () => Promise.resolve(token), accountName: userInfo.accountName, scopes: scopes }; diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index 998f3799f4..48c7ae314a 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -17,7 +17,7 @@ "Other" ], "activationEvents": [ - "onWebviewEditor:imagePreview.previewEditor", + "onCustomEditor:imagePreview.previewEditor", "onCommand:imagePreview.zoomIn", "onCommand:imagePreview.zoomOut" ], diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 49e81f8af1..7b0cd21765 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -27,7 +27,7 @@ "onCommand:markdown.api.render", "onCommand:notebook.showPreview", "onWebviewPanel:markdown.preview", - "onWebviewEditor:vscode.markdown.preview.editor" + "onCustomEditor:vscode.markdown.preview.editor" ], "contributes": { "commands": [ diff --git a/extensions/vscode-account/package.json b/extensions/vscode-account/package.json index c4df52ce73..cd579aef85 100644 --- a/extensions/vscode-account/package.json +++ b/extensions/vscode-account/package.json @@ -39,5 +39,8 @@ "tslint": "^5.12.1", "@types/node": "^10.12.21", "@types/keytar": "^4.0.1" + }, + "dependencies": { + "vscode-nls": "^4.1.1" } } diff --git a/extensions/vscode-account/package.nls.json b/extensions/vscode-account/package.nls.json index 8211a3f6e9..c0bb4c4a6a 100644 --- a/extensions/vscode-account/package.nls.json +++ b/extensions/vscode-account/package.nls.json @@ -1,6 +1,6 @@ { "displayName": "Microsoft Account", "description": "Microsoft authentication provider", - "signIn": "Sign in", - "signOut": "Sign out" + "signIn": "Sign In", + "signOut": "Sign Out" } diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts index 190bddf312..be451a201a 100644 --- a/extensions/vscode-account/src/AADHelper.ts +++ b/extensions/vscode-account/src/AADHelper.ts @@ -184,7 +184,7 @@ export class AzureActiveDirectoryService { private convertToSession(token: IToken): vscode.AuthenticationSession { return { id: token.sessionId, - accessToken: () => this.resolveAccessToken(token), + getAccessToken: () => this.resolveAccessToken(token), accountName: token.accountName, scopes: token.scope.split(' ') }; @@ -192,7 +192,9 @@ export class AzureActiveDirectoryService { private async resolveAccessToken(token: IToken): Promise { if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) { - Logger.info('Token available from cache'); + token.expiresAt + ? Logger.info(`Token available from cache, expires in ${token.expiresAt - Date.now()} milliseconds`) + : Logger.info('Token available from cache'); return Promise.resolve(token.accessToken); } diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts index 0f14f377de..95b7ecc80e 100644 --- a/extensions/vscode-account/src/extension.ts +++ b/extensions/vscode-account/src/extension.ts @@ -5,6 +5,9 @@ import * as vscode from 'vscode'; import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); export const DEFAULT_SCOPES = 'https://management.core.windows.net/.default offline_access'; @@ -15,7 +18,7 @@ export async function activate(context: vscode.ExtensionContext) { await loginService.initialize(); context.subscriptions.push(vscode.authentication.registerAuthenticationProvider({ - id: 'MSA', + id: 'microsoft', displayName: 'Microsoft', onDidChangeSessions: onDidChangeSessions.event, getSessions: () => Promise.resolve(loginService.sessions), @@ -45,6 +48,7 @@ export async function activate(context: vscode.ExtensionContext) { if (sessions.length === 1) { await loginService.logout(loginService.sessions[0].id); onDidChangeSessions.fire(); + vscode.window.showInformationMessage(localize('signedOut', "Successfully signed out.")); return; } @@ -58,6 +62,7 @@ export async function activate(context: vscode.ExtensionContext) { if (selectedSession) { await loginService.logout(selectedSession.id); onDidChangeSessions.fire(); + vscode.window.showInformationMessage(localize('signedOut', "Successfully signed out.")); return; } })); diff --git a/extensions/vscode-account/yarn.lock b/extensions/vscode-account/yarn.lock index 3acdda242e..4a86ea6a2a 100644 --- a/extensions/vscode-account/yarn.lock +++ b/extensions/vscode-account/yarn.lock @@ -635,6 +635,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +vscode-nls@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" + integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== + which-pm-runs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" diff --git a/package.json b/package.json index 50257172a0..497e8069fc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "azuredatastudio", "version": "1.16.0", - "distro": "4672f37b373e16688fc9cda03d25a138669718e9", + "distro": "4e85ca0eb0c1b17aceab11996f7c2cf460868e8a", "author": { "name": "Microsoft Corporation" }, @@ -184,7 +184,7 @@ "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", "vsce": "1.48.0", - "vscode-debugprotocol": "1.39.0-pre.0", + "vscode-debugprotocol": "1.39.0", "vscode-nls-dev": "^3.3.1", "webpack": "^4.16.5", "webpack-cli": "^3.3.8", diff --git a/resources/linux/snap/electron-launch b/resources/linux/snap/electron-launch index 2a1c439518..9f4eb6a23b 100644 --- a/resources/linux/snap/electron-launch +++ b/resources/linux/snap/electron-launch @@ -2,7 +2,7 @@ # On Fedora $SNAP is under /var and there is some magic to map it to /snap. # We need to handle that case and reset $SNAP -SNAP=$(echo $SNAP | sed -e "s|/var/lib/snapd||g") +SNAP=$(echo "$SNAP" | sed -e "s|/var/lib/snapd||g") if [ "$SNAP_ARCH" == "amd64" ]; then ARCH="x86_64-linux-gnu" @@ -14,21 +14,21 @@ else ARCH="$SNAP_ARCH-linux-gnu" fi -export XDG_CACHE_HOME=$SNAP_USER_COMMON/.cache -if [[ -d $SNAP_USER_DATA/.cache && ! -e $XDG_CACHE_HOME ]]; then +GDK_CACHE_DIR="$SNAP_USER_COMMON/.cache" +if [[ -d "$SNAP_USER_DATA/.cache" && ! -e "$GDK_CACHE_DIR" ]]; then # the .cache directory used to be stored under $SNAP_USER_DATA, migrate it - mv $SNAP_USER_DATA/.cache $SNAP_USER_COMMON/ + mv "$SNAP_USER_DATA/.cache" "$SNAP_USER_COMMON/" fi -mkdir -p $XDG_CACHE_HOME +[ ! -d "$GDK_CACHE_DIR" ] && mkdir -p "$GDK_CACHE_DIR" # Gdk-pixbuf loaders -export GDK_PIXBUF_MODULE_FILE=$XDG_CACHE_HOME/gdk-pixbuf-loaders.cache -export GDK_PIXBUF_MODULEDIR=$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/2.10.0/loaders -if [ -f $SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders ]; then - $SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders > $GDK_PIXBUF_MODULE_FILE +export GDK_PIXBUF_MODULE_FILE="$GDK_CACHE_DIR/gdk-pixbuf-loaders.cache" +export GDK_PIXBUF_MODULEDIR="$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/2.10.0/loaders" +if [ -f "$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" ]; then + "$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" > "$GDK_PIXBUF_MODULE_FILE" fi # Create $XDG_RUNTIME_DIR if not exists (to be removed when https://pad.lv/1656340 is fixed) -[ -n "$XDG_RUNTIME_DIR" ] && mkdir -p $XDG_RUNTIME_DIR -m 700 +[ -n "$XDG_RUNTIME_DIR" ] && mkdir -p "$XDG_RUNTIME_DIR" -m 700 exec "$@" diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index a17c28cf44..2635caa6e1 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -5,7 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?279add2ec8b3d516ca20a123230cbf9f") format("truetype"); + src: url("./codicon.ttf?b5dd8f5aa953889dc1f4c9fa9b44d3dd") format("truetype"); } .codicon[class*='codicon-'] { @@ -415,5 +415,6 @@ .codicon-group-by-ref-type:before { content: "\eb97" } .codicon-ungroup-by-ref-type:before { content: "\eb98" } .codicon-bell-dot:before { content: "\f101" } -.codicon-debug-alt-2:before { content: "\f102" } -.codicon-debug-alt:before { content: "\f103" } +.codicon-bell-progress:before { content: "\f102" } +.codicon-debug-alt-2:before { content: "\f103" } +.codicon-debug-alt:before { content: "\f104" } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf index df86f7d4d9a21847df1652c1782ecf6f8aa0eddb..845205f5b3a4132caa8bdf4b586532cfb8a1cd19 100644 GIT binary patch delta 5308 zcmYM%33$}ixd!m}WI{HwlYN~mBq1aOLYP1d5JG?uAjBX6sUkZO6G1>kLD#{Fx49sA3^1Djw9W0Dy~Le7a~cSH9Y|ciu;cCp9}Spx6v*5KbeFZypFJ-vEaw1U z%hw>#&L_hE*ejj)_fcVc=kk@i{k#Wr76?7wv3Tz6&fF*Y{poc;NJr=Fl}kce@D{(G z#uxc-pWQh>@1=}U_W|#n;0vObEMB(!!Do}IfcN)_o?q=K8`?$_bmVMd(4oo9P_FP-F?)Y4ShT$w5unq&yWk( zej-B7!*}go8EX{ayMWLT?{mcSzYnk*>?Zq;Yj&I6wu_-%Ux!r(e~7=}Bp$#KlZ0R4 zGIrw&RGD%(T*PNsgd)6++1P>~qulgC8)8fumSZmdh+p7!ykWBNDDK5~5ilW`ZxZnp zo-z^m+C-TI6K%rqkTE8f@5zTy^g3 z6=}#s7P66pT=ir?O2Q@xC2YE40mD$?!rpkja9e@ ztFZ<_tmSL&!#doL2eBR-un`Yq6CS~4Jch^7g(t8T-^VsQiKp;CcpCqUAK)20i|zOk zUc^h-iCy>!UdB)H3cAsQpJ5MPtHECUoJ;LZ{1W?d0KdjT{D#Zv5Z=MN_$?0OJ-m;j zIELTh_c)F}-~*h%pYUg#>iQ%iJ!tU>KE(z66PNHWe2y#lH~xb!@eRJkRRiNQp{AD! zH<6~di7{~|*`%0MlV;LQhRHP9CdcF&zsWQCrqJ{?MW)!4n0}_z^fv=cg&AlDnMyO* zRN*;1j~B25qfn2Dn1p7GG4Ys?gBFZOIx_Go7UDL1&lDgQakvh(T&ZEtKO4y<0bXkU zi{0kk^65a=D^aOjc(8MXOAvOha52Kp6D~{G`GO;?3xrD*cA;?5!nO;SFYK+tg$%n$ zxTIlk6L2Tm4j|~=aGk?;3RgVr?ZUMWyI8mjV3!E@1MD5bodLU4xJO`@33m(Za^b## zy;Hb@U{?srxt{D@d_de*uq%Zd4EAn84C^Z4rh~mlxcy*P3pXO{y}~UCyGFPN~p18AN9~1;w*9&($toJp7cZoKLce(9GVK0Dv zShy!(HwpU#?9JXUBz(=fSqxj2eN0RO>*HdgS-XTS1=jns5*}iGLX2VEDkiqeAMb_D8~g1pA_}Gr_(j>`|~gh209)yQsvz1-nbw!C-&l ztuWrOnZdp+Y-_MT6*f57SA?w&_ElligY6c!KiJ*EMhM#@Y>BWx6E;WKJpw=LYr=-f z73N)eV(*0Yt~{}e!tNFJQ`nyiJ1guyVULAcOAV}pkMwXjvg9uzih*xv}-H|$%&Mh^S7u%*Ku5;k|(cZ6*o_OP(w!@ehM{jf)b zLx9Wweclk~0qjxXWPm*;oDs0U6HW`*-wWpk>~Y})f&GJUmcV`>oGP&1ek0Bo*b~A@ z1N$cla_GRG6pkO*KMMyD>?z?$g8hqdIKh4>98<7=6%H)e)56gO`#0ebgFPdd#rk*Q zV1qsDUH`Luck}q*AA$v}=Y(Sq_9Ni{ggq}Dg|HtB2Og{yjz?H09F(x12uCLDr^4Y0 zdqJ?2*WdnAI8k9g6V6uHi^3@jdr3HNVgDtZys(#rGZ^-B;WUQ5BAm;x{}xVY*e`^$ z8umXF_XH%J)u;3|co4{oq9{=roVgCN`xVI+hL2*V*jfXOj`r~Sjc3^tP=(2StkiTW(_v;hG1D+glQS> zdSPycYZWGFxGBOc4R?buRl`jc=4-e%VbX@1Cd}M$(}n39?nVhRh{MegMsm3C2*Wwt zO~RNCceCIs>r63x-|iMMKGs=cLRn{v>BTxnOgQUYF_El1+ITlGUc;C#CWdu^m^jvj zVvD@Sp~_S$JfFdt7*UgXM}(oe;aq z(({5ReO0~%z5~9qp(UXQLeKV^+3Q5uj_|eN=fl5^m>4l9Vr9f55!)l)iZ~TvBV!|L zBNs*XL{&z$M;+)L(mSj7y58rbW25V%S4HoQJ`*!5W^GJ&%>LNs*nM&NaoggK#$Abz zkI#>x{D|JWe>9nG>jcF&+8`ED*KbKLLu{Sd+ zb7tm_ti-J5taVx2vn#XPvR7q4oqZ{1K+e3J4|6x=p7xje_vAI_eVIQZ|4PBW!sf!Q zg*yu`_s!}%r*H6ZQEX9N(IZ8di%W~w6@OS#S+cgIr(blxBc-9GD@xDyZ|J|Fe|MR` zthVf?^62uC@|N-q<(CKSsEDW-QL(6EbH(0@3j^m2Ts?5tz)ORM4q7#+XOOMTs%))% zs`A~z(+9s&)l>D^kXb_x1`tRJGzGQ!}@BJH7Vw@XF!4Mr4dwGUD(^jI15Gy)L$HWnFjOsZm*@7LD36>TrEj{qp+P zN0*G=G5S)&s)l_H-;P-{X3yBb*coG2kJ~iv-Erp{6B`4K9gTa&cZ}aU{zQ|%sj+Ew z)Apt_6JDHfZerHN_K9026;7HnY4XYDu;zy5LzBZMw@ltP*|sFL%xl@&(tUl>^~+mJ zTRU3Uv~F&FuC-@M&XfgHelg|J4UIP(oti&&`qZbVo^PvZo7MI}+rhRkrmdRx^z`M^ z58arudt2tMp~+!fc<}A??c}Mhktu!#b8u%@eD3(d)Ie2ncuG=uadlBxcxs?BwO3Vj zYFJWgc+rs5@RZ8x;;{VUuDQ9R^VVnh{Z+}y2?-TTD++Qa=3YfEA6EJ2|M!Wm?%eu< zjQ>7*JHv0h7qkz%_UeL)Ve2w-y(iG^f8k{_WcQ9E?I9r{fv(tNKaP!=Gryywe96+q Q3zyDcwruws$G(dGKW`nJnE(I) delta 5119 zcmYM%4Sde!9tQC1-ZR_meY1_d7_%8>#%3F4W|+Ls`%8K=FKfwLXd(3=m9%PENu7?< zk)%=yITDhjbV^G?l2nrJTSBG6;aq>$`Fswa@ALaVvw5Cp|NH*^u6y-P-=^)p+H`+C zU}^x2nlQCu#-UwtF9JcEfY>>=PMKTTYf#M!Aeq)5ubNabvGK22oB6%Rsj!M)2tL%H z39q+NM%C2Wb6)SvE0_~NVEvTo6Dqp+!}+s`*+9VLsTFf(1PsO={=4@x&8JmNos{-c zQjdke$8-1%p);n>n*Dg*=)J(c#Xxks$G4?-&xi5;L%_bYJN1^R;@=VGJLSE`ky$hU z_WOq@olU(Cj#rX1T`u+eeJ(Pz)AS7U1hU|0>0A- z4A{yaisoM*Uu=o3v3p&)t92{R`IZHD@~^~Ee2M$;iD`tL_!S%Q2RfQ;Se(T#xD74& zxyGRmFC*K;Vk9C=Tg<|E9K?3~3tLPw9>sh-fC6JM(Zt{)o;Ja_WEz@i6J{DVxC`nP`R9XoD=Y zMLXo6Jvty4dB{g0I-xVVpf|2VAM`^xhF~a$VK_!$6h>nVuE$v1fE#fWZpJOBzywUf zt*FB7n1ZR8hUu7rJ1`ToaVM&A7v^9t=HYJKgL~oU_bk9dKNevz?#F|82utxW9>HU% z#p76pC-5Ye<0-7b(|87d!?XB1{(8n0~@gkZ(=jv!dAS^ zb+rTU;9cy(d)STl@c}->UVMaq<74c@r>Mtg*pJU~0AXL?5Dw!AzQWgaXF?MF4yW-m z&fpx*<2U?{3-}X%;WGY%E4XR`jL!s`AQNIjO}L3PaVFj*n8v1wNi<0&#WXdkrkQDO z(oDL^FfC1{X=PfQHYUroGdZTc>0okAp6Q5xVii{71(c!(24Eltq0B^~KL#hDFPb0` z>rsixs4*=Ni6V5}_*}yX|iGd7a;6J;ZlU1B>052 zQn)-}Zxt?7*ecjH+yk(8 z2zLYQO!4-+oh95Mu(P>j_yBGe*gJ*W2DVz@z3^Satpv;7;Nhl%ohyi7ohRIAuy+f$ z9PE7I=7YUQxD8?N6>RtO;(xd|VHXItur3ttSJ*|uoeS&zx`}%jmd(k--3)ubz`K>+ zbK(w%eNf=tt|h`<54)87d|(5BeOS0LV7VqdYznZC2`;hLifPFDxR_|xWn#iuz279U zjle!3@Lu#u;roGIE^IQWYaQ{3KZ?(v7xp~Z)xtgl`+~6Jz^)PY9@rO!T?lrquphBC z3p*36cPEKG3icIYw}M?K>|3y}0)Fp-Ee!TGVKak$UD(!O*9#jQ>>Gk4)(ryh4s8^q zuySU3(3JH}K`QHJK{HnGZWEe&YtXyhgf!N-1T9!O2Ruk;eOr*hx=oPDx?R|FVRs0- zFYG(QJ`B54*pXr174~M>T^_DKwrSY+gbf>Zx3G1?zAtRzuzQ5<9QFfYV~71v*y3UL z3Y$Ib$HKM``-yM>!0r={0+1@@3|%D^5L&KuY-#m~_Ldqg;dV80TMBiN(D!36uYa74i#6AmlbZ-iqD_PB6> z!G0?oWw74~hZ^kn!tn-s!n^+`_~|F`$q&Mr2YXUD{a}9-&Oz8y!ifm`lW^|ATH%z0 zb;5ZGds;X-VSg6RP}nnqJ9z)?FT%kJdsaANVb2MNE$n&W*oFO7IDlb)6OLlo--SaN z_JVLc!~P*0)UbaFM>gzVF9i0IT>E5~c}-Zr`X9kU)+@qs4|`QO_`wf< z4I==Y5rzS{0AVbE^9ch2{4S7Bh*<%yfiN||1qt&5T(B@nz=a4i1zba6x_}E6<_x$n zVd8)b7iJH*2w@6=i@b&(cLgt^#OuH=S{PE`VuW!8E>;*^;2H@d3|yQr%)rG9o?}fA z1{}D?!l(n+L>PMD68ZWrk)M7oYmzVt!6geb5nPHe9lve)}S^EgSW9=*Wp0%Ig1Z#i653B-@!;KauZ@4kS3=Vg_ z_!-CH#tMTu+zrBr4tJw4ti#A|`-!oERT#g_uCr@nV8lCx{7Qo#@^F ziA+qPd@@N)IBTVtNY-1$#IaV1iD$h{OakjmpW zF-=)#h)HF=LrgQ)nPQr=&JvTxI$KOS>z!gUSgXafWW7sFChHtAtyt%hpAV)rFXoA9 z!+N)vEY|sAynTC*m>kx7#dzD+`w(QjZM#5BF6%-ud8~_sXC=7%gr_F%|HXVlJV3$S zFFZ=YJs><(!96G_WnCgXXu&-sJaWM;6&}9e9u^+M;A#YAtd9tfW^j*Q!?)d^7mo=B zv(^d^Zg9(lM>x1T;b9K$3E{C0?n&VR4{o{es0a6yzrfS?T9-OpAtVLeop+__`?Z73Du3!xTx`h#wVKOHCdGykXV}d zOj2CZ#H20BNS>0sHu-4sm6V#4^(hBZE;Jq3bV<`4shv|#HY;kjxOsH*wP_>LR$|q$ZRfRJ-ga|#W%i=%m$ScXSDu5M@i{AUcIVjkaqauI-`4(MhoBAvJ1pyPAU86% zEVnv$UG8sr8F?EzhUQ1+_s`#2kXkUdU|PZIf-{9Vh5ZZX6|U^mqElt3i=8Vw*ZaGa zby?hHT~T;ZPEm2!$gWGfZYVA)uIU!kZFaYhN-|5vmMky1(!Hwt{?hQ$!qV!}-KA%G zWcHZYV_(nMo{M^+*T7x}$`Z=Plv-MhB;rrrmyJJ~0rPidbieQNvc=^Nd5Pv5is z^7_r|x2oU4epmVz4+t1gI-q*Mt^sET-aByHp!`9D#|>Ip-lBY5`OD?!2bT?=Hh9P2 zvqQp%OdGOq=;EP!h8`Yjhh82QIc(0box__9A2@u~h^!IwM{F2zVPwydPmJ6;^4O@< vQNu implements ISpliceable, IDisposable { if (!item.row) { item.row = this.cache.alloc(item.templateId); - const role = this.ariaProvider.getRole ? this.ariaProvider.getRole(item.element) : 'treeitem'; + const role = this.ariaProvider.getRole ? this.ariaProvider.getRole(item.element) : 'listitem'; item.row!.domNode!.setAttribute('role', role); const checked = this.ariaProvider.isChecked ? this.ariaProvider.isChecked(item.element) : undefined; if (typeof checked !== 'undefined') { diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index e9c7c135d4..d44347991c 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -196,7 +196,7 @@ function asListOptions(modelProvider: () => ITreeModel { return options.ariaProvider!.getRole!(node.element); - } : undefined + } : () => 'treeitem' } }; } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index daa30e2c20..b57f50f9af 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -267,7 +267,7 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt }, getRole: options.ariaProvider!.getRole ? (el) => { return options.ariaProvider!.getRole!(el.element as T); - } : undefined, + } : () => 'treeitem', isChecked: options.ariaProvider!.isChecked ? (e) => { return options.ariaProvider?.isChecked!(e.element as T); } : undefined diff --git a/src/vs/base/common/linkedText.ts b/src/vs/base/common/linkedText.ts index 83d9d199d9..b2a367403c 100644 --- a/src/vs/base/common/linkedText.ts +++ b/src/vs/base/common/linkedText.ts @@ -23,7 +23,7 @@ export class LinkedText { } } -const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi; +const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: ("|')([^\3]+)(\3))?\)/gi; export function parseLinkedText(text: string): LinkedText { const result: LinkedTextNode[] = []; @@ -36,7 +36,7 @@ export function parseLinkedText(text: string): LinkedText { result.push(text.substring(index, match.index)); } - const [, label, href, title] = match; + const [, label, href, , title] = match; if (title) { result.push({ label, href, title }); diff --git a/src/vs/base/parts/composite/browser/compositeDnd.ts b/src/vs/base/parts/composite/browser/compositeDnd.ts index 88571b4e99..a394a43926 100644 --- a/src/vs/base/parts/composite/browser/compositeDnd.ts +++ b/src/vs/base/parts/composite/browser/compositeDnd.ts @@ -21,4 +21,5 @@ export class CompositeDragAndDropData implements IDragAndDropData { export interface ICompositeDragAndDrop { drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): void; onDragOver(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean; + onDragEnter(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean; } diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 15932a96c9..faa1d10b34 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -397,7 +397,7 @@ class QuickPick extends QuickInput implements IQuickPi private _valueSelection: Readonly<[number, number]> | undefined; private valueSelectionUpdated = true; private _validationMessage: string | undefined; - private _ok = false; + private _ok: boolean | 'default' = 'default'; private _customButton = false; private _customButtonLabel: string | undefined; private _customButtonHover: string | undefined; @@ -566,7 +566,7 @@ class QuickPick extends QuickInput implements IQuickPi return this._ok; } - set ok(showOkButton: boolean) { + set ok(showOkButton: boolean | 'default') { this._ok = showOkButton; this.update(); } @@ -575,6 +575,10 @@ class QuickPick extends QuickInput implements IQuickPi return this.visible ? this.ui.inputBox.hasFocus() : false; } + public focusOnInput() { + this.ui.inputBox.setFocus(); + } + onDidChangeSelection = this.onDidChangeSelectionEmitter.event; onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event; @@ -753,7 +757,8 @@ class QuickPick extends QuickInput implements IQuickPi if (!this.visible) { return; } - this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, description: !!this.description, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: this.ok, list: true, message: !!this.validationMessage, customButton: this.customButton } : { title: !!this.title || !!this.step, description: !!this.description, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok }); + const ok = this.ok === 'default' ? this.canSelectMany : this.ok; + this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, description: !!this.description, checkAll: true, inputBox: true, visibleCount: true, count: true, ok, list: true, message: !!this.validationMessage, customButton: this.customButton } : { title: !!this.title || !!this.step, description: !!this.description, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok }); super.update(); if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index e0abaa442e..5503047f80 100644 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -113,7 +113,7 @@ export interface IInputOptions { placeHolder?: string; /** - * set to true to show a password prompt that will not show the typed value + * Controls if a password input is shown. Password input hides the typed text. */ password?: boolean; @@ -162,7 +162,7 @@ export interface IQuickPick extends IQuickInput { readonly onDidAccept: Event; - ok: boolean; + ok: boolean | 'default'; readonly onDidCustom: Event; @@ -209,6 +209,8 @@ export interface IQuickPick extends IQuickInput { validationMessage: string | undefined; inputHasFocus(): boolean; + + focusOnInput(): void; } export interface IInputBox extends IQuickInput { diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index 9e4106b7d4..93dd13c275 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -27,7 +27,8 @@ export interface IUpdateRequest { } export interface IStorageItemsChangeEvent { - items: Map; + changed?: Map; + deleted?: Set; } export interface IStorageDatabase { @@ -104,10 +105,11 @@ export class Storage extends Disposable implements IStorage { // items that change external require us to update our // caches with the values. we just accept the value and // emit an event if there is a change. - e.items.forEach((value, key) => this.accept(key, value)); + e.changed?.forEach((value, key) => this.accept(key, value)); + e.deleted?.forEach(key => this.accept(key, undefined)); } - private accept(key: string, value: string): void { + private accept(key: string, value: string | undefined): void { if (this.state === StorageState.Closed) { return; // Return early if we are already closed } @@ -315,4 +317,4 @@ export class InMemoryStorageDatabase implements IStorageDatabase { close(): Promise { return Promise.resolve(); } -} \ No newline at end of file +} diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index 3d8b8397c3..df820a4330 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -124,28 +124,27 @@ suite('Storage Library', () => { changes.clear(); // Nothing happens if changing to same value - const change = new Map(); - change.set('foo', 'bar'); - database.fireDidChangeItemsExternal({ items: change }); + const changed = new Map(); + changed.set('foo', 'bar'); + database.fireDidChangeItemsExternal({ changed }); equal(changes.size, 0); // Change is accepted if valid - change.set('foo', 'bar1'); - database.fireDidChangeItemsExternal({ items: change }); + changed.set('foo', 'bar1'); + database.fireDidChangeItemsExternal({ changed }); ok(changes.has('foo')); equal(storage.get('foo'), 'bar1'); changes.clear(); // Delete is accepted - change.set('foo', undefined!); - database.fireDidChangeItemsExternal({ items: change }); + const deleted = new Set(['foo']); + database.fireDidChangeItemsExternal({ deleted }); ok(changes.has('foo')); - equal(storage.get('foo', null!), null); + equal(storage.get('foo', undefined), undefined); changes.clear(); // Nothing happens if changing to same value - change.set('foo', undefined!); - database.fireDidChangeItemsExternal({ items: change }); + database.fireDidChangeItemsExternal({ deleted }); equal(changes.size, 0); await storage.close(); diff --git a/src/vs/base/test/common/linkedText.test.ts b/src/vs/base/test/common/linkedText.test.ts index f7fe6ff5bf..e63703d4c0 100644 --- a/src/vs/base/test/common/linkedText.test.ts +++ b/src/vs/base/test/common/linkedText.test.ts @@ -21,6 +21,21 @@ suite('LinkedText', () => { { label: 'link text', href: 'http://link.href', title: 'and a title' }, '.' ]); + assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href \'and a title\').').nodes, [ + 'Some message with ', + { label: 'link text', href: 'http://link.href', title: 'and a title' }, + '.' + ]); + assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a \'title\'").').nodes, [ + 'Some message with ', + { label: 'link text', href: 'http://link.href', title: 'and a \'title\'' }, + '.' + ]); + assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href \'and a "title"\').').nodes, [ + 'Some message with ', + { label: 'link text', href: 'http://link.href', title: 'and a "title"' }, + '.' + ]); assert.deepEqual(parseLinkedText('Some message with [link text](random stuff).').nodes, [ 'Some message with [link text](random stuff).' ]); diff --git a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts index 9dbd503f55..94c6974f94 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts @@ -75,7 +75,11 @@ function getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, in // Recurse into children if any if (Array.isArray(item.children)) { - item.children.forEach(child => getProcessItem(processes, child, indent + 1, isLocal)); + item.children.forEach(child => { + if (child) { + getProcessItem(processes, child, indent + 1, isLocal); + } + }); } } diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index be27392a3f..d21e0c30cc 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -32,6 +32,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; const RUN_TEXTMATE_IN_WORKER = false; @@ -100,7 +101,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IDialogMainService private readonly dialogMainService: IDialogMainService + @IDialogMainService private readonly dialogMainService: IDialogMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService ) { super(); @@ -244,6 +246,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { get isExtensionTestHost(): boolean { return !!(this.config && this.config.extensionTestsPath); } + get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.config?.debugId; } + setRepresentedFilename(filename: string): void { if (isMacintosh) { this.win.setRepresentedFilename(filename); @@ -447,6 +451,15 @@ export class CodeWindow extends Disposable implements ICodeWindow { private onWindowError(error: WindowError): void { this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive'); + // If we run extension tests from CLI, showing a dialog is not + // very helpful in this case. Rather, we bring down the test run + // to signal back a failing run. + if (this.isExtensionDevelopmentTestFromCli) { + this.lifecycleMainService.kill(1); + return; + } + + // Telemetry type WindowErrorClassification = { type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; }; diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index e7dc08a8f1..c44e52cb6a 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -16,6 +16,7 @@ export interface IBulkEditOptions { progress?: IProgress; showPreview?: boolean; label?: string; + quotableLabel?: string; } export interface IBulkEditResult { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index f33577eb80..5b54d1ef89 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -46,7 +46,7 @@ class MinimapOptions { public readonly renderMinimap: RenderMinimap; - public readonly mode: 'actual' | 'cover' | 'contain'; + public readonly size: 'proportional' | 'fill' | 'fit'; public readonly minimapHeightIsEditorHeight: boolean; @@ -108,7 +108,7 @@ class MinimapOptions { const minimapOpts = options.get(EditorOption.minimap); this.renderMinimap = layoutInfo.renderMinimap | 0; - this.mode = minimapOpts.mode; + this.size = minimapOpts.size; this.minimapHeightIsEditorHeight = layoutInfo.minimapHeightIsEditorHeight; this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); this.showSlider = minimapOpts.showSlider; @@ -144,7 +144,7 @@ class MinimapOptions { public equals(other: MinimapOptions): boolean { return (this.renderMinimap === other.renderMinimap - && this.mode === other.mode + && this.size === other.size && this.minimapHeightIsEditorHeight === other.minimapHeightIsEditorHeight && this.scrollBeyondLastLine === other.scrollBeyondLastLine && this.showSlider === other.showSlider @@ -1111,7 +1111,7 @@ class InnerMinimap extends Disposable { if (!this._lastRenderData) { return; } - if (this._model.options.minimapHeightIsEditorHeight) { + if (this._model.options.size !== 'proportional') { if (e.leftButton && this._lastRenderData) { // pretend the click occured in the center of the slider const position = dom.getDomNodePagePosition(this._slider.domNode); diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index ce79be923e..6c9954a9f8 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -617,10 +617,11 @@ export class DiffReview extends Disposable { header.setAttribute('role', 'listitem'); container.appendChild(header); + const lineHeight = modifiedOptions.get(EditorOption.lineHeight); let modLine = minModifiedLine; for (let i = 0, len = diffs.length; i < len; i++) { const diffEntry = diffs[i]; - DiffReview._renderSection(container, diffEntry, modLine, this._width, originalOptions, originalModel, originalModelOpts, modifiedOptions, modifiedModel, modifiedModelOpts); + DiffReview._renderSection(container, diffEntry, modLine, lineHeight, this._width, originalOptions, originalModel, originalModelOpts, modifiedOptions, modifiedModel, modifiedModelOpts); if (diffEntry.modifiedLineStart !== 0) { modLine = diffEntry.modifiedLineEnd; } @@ -632,7 +633,7 @@ export class DiffReview extends Disposable { } private static _renderSection( - dest: HTMLElement, diffEntry: DiffEntry, modLine: number, width: number, + dest: HTMLElement, diffEntry: DiffEntry, modLine: number, lineHeight: number, width: number, originalOptions: IComputedEditorOptions, originalModel: ITextModel, originalModelOpts: TextModelResolvedOptions, modifiedOptions: IComputedEditorOptions, modifiedModel: ITextModel, modifiedModelOpts: TextModelResolvedOptions ): void { @@ -641,17 +642,18 @@ export class DiffReview extends Disposable { let rowClassName: string = 'diff-review-row'; let lineNumbersExtraClassName: string = ''; - let spacerClassName: string = 'diff-review-spacer'; + const spacerClassName: string = 'diff-review-spacer'; + let spacerCodiconName: string | null = null; switch (type) { case DiffEntryType.Insert: rowClassName = 'diff-review-row line-insert'; lineNumbersExtraClassName = ' char-insert'; - spacerClassName = 'diff-review-spacer insert-sign'; + spacerCodiconName = 'codicon codicon-add'; break; case DiffEntryType.Delete: rowClassName = 'diff-review-row line-delete'; lineNumbersExtraClassName = ' char-delete'; - spacerClassName = 'diff-review-spacer delete-sign'; + spacerCodiconName = 'codicon codicon-remove'; break; } @@ -686,6 +688,7 @@ export class DiffReview extends Disposable { let cell = document.createElement('div'); cell.className = 'diff-review-cell'; + cell.style.height = `${lineHeight}px`; row.appendChild(cell); const originalLineNumber = document.createElement('span'); @@ -713,7 +716,15 @@ export class DiffReview extends Disposable { const spacer = document.createElement('span'); spacer.className = spacerClassName; - spacer.innerHTML = '  '; + + if (spacerCodiconName) { + const spacerCodicon = document.createElement('span'); + spacerCodicon.className = spacerCodiconName; + spacerCodicon.innerHTML = '  '; + spacer.appendChild(spacerCodicon); + } else { + spacer.innerHTML = '  '; + } cell.appendChild(spacer); let lineContent: string; diff --git a/src/vs/editor/browser/widget/media/diffReview.css b/src/vs/editor/browser/widget/media/diffReview.css index f4ed5d7364..07c5ee5dd4 100644 --- a/src/vs/editor/browser/widget/media/diffReview.css +++ b/src/vs/editor/browser/widget/media/diffReview.css @@ -37,13 +37,14 @@ width: 100%; } -.monaco-diff-editor .diff-review-cell { - display: table-cell; -} - .monaco-diff-editor .diff-review-spacer { display: inline-block; width: 10px; + vertical-align: middle; +} + +.monaco-diff-editor .diff-review-spacer > .codicon { + font-size: 9px !important; } .monaco-diff-editor .diff-review-actions { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 0083d06b3a..bae7e535c4 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -510,7 +510,7 @@ export interface IEditorOptions { * Controls whether clicking on the empty content after a folded line will unfold the line. * Defaults to false. */ - unfoldOnClickInEmptyContent?: boolean; + unfoldOnClickAfterEndOfLine?: boolean; /** * Enable highlighting of matching brackets. * Defaults to 'always'. @@ -1808,7 +1808,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption= 2 ? Math.round(minimap.scale * 2) : minimap.scale); const minimapMaxColumn = minimap.maxColumn | 0; - const minimapMode = minimap.mode; + const minimapSize = minimap.size; const scrollbar = options.get(EditorOption.scrollbar); const verticalScrollbarWidth = scrollbar.verticalScrollbarSize | 0; @@ -1872,7 +1872,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption minimapCanvasInnerHeight) { + if (minimapSize === 'fill' || effectiveMinimapHeight > minimapCanvasInnerHeight) { minimapHeightIsEditorHeight = true; const configuredFontScale = minimapScale; minimapLineHeight = Math.min(lineHeight * pixelRatio, Math.max(1, Math.floor(1 / desiredRatio))); @@ -2080,7 +2080,7 @@ export interface IEditorMinimapOptions { * Control the minimap rendering mode. * Defaults to 'actual'. */ - mode?: 'actual' | 'cover' | 'contain'; + size?: 'proportional' | 'fill' | 'fit'; /** * Control the rendering of the minimap slider. * Defaults to 'mouseover'. @@ -2109,7 +2109,7 @@ class EditorMinimap extends BaseEditorOption(input.mode, this.defaultValue.mode, ['actual', 'cover', 'contain']), + size: EditorStringEnumOption.stringSet<'proportional' | 'fill' | 'fit'>(input.size, this.defaultValue.size, ['proportional', 'fill', 'fit']), side: EditorStringEnumOption.stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']), showSlider: EditorStringEnumOption.stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']), renderCharacters: EditorBooleanOption.boolean(input.renderCharacters, this.defaultValue.renderCharacters), @@ -2840,9 +2841,14 @@ export interface ISuggestOptions { */ showSnippets?: boolean; /** - * Controls the visibility of the status bar at the bottom of the suggest widget. + * Status bar related settings. */ - hideStatusBar?: boolean; + statusBar?: { + /** + * Controls the visibility of the status bar at the bottom of the suggest widget. + */ + visible?: boolean; + } } export type InternalSuggestOptions = Readonly>; @@ -2884,7 +2890,9 @@ class EditorSuggest extends BaseEditorOption; + getAccessToken(): Thenable; accountName: string; } diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 86b0451c97..b923658d2c 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -199,7 +199,7 @@ export enum EditorOption { folding = 31, foldingStrategy = 32, foldingHighlight = 33, - unfoldOnClickInEmptyContent = 34, + unfoldOnClickAfterEndOfLine = 34, fontFamily = 35, fontInfo = 36, fontLigatures = 37, diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 8f573c6efc..7b3d5dc929 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -161,6 +161,7 @@ class ExecCommandPasteAction extends ExecCommandAction { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_V, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, + linux: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, weight: KeybindingWeight.EditorContrib }; // Do not bind paste keybindings in the browser, diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 611812ad3d..8df615a31a 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -164,7 +164,7 @@ export async function applyCodeAction( }); if (action.edit) { - await bulkEditService.apply(action.edit, { editor }); + await bulkEditService.apply(action.edit, { editor, label: action.title }); } if (action.command) { diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 04f6a6abb5..b1f3e93c83 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -62,7 +62,7 @@ export class FoldingController extends Disposable implements IEditorContribution private readonly editor: ICodeEditor; private _isEnabled: boolean; private _useFoldingProviders: boolean; - private _unfoldOnClickInEmptyContent: boolean; + private _unfoldOnClickAfterEndOfLine: boolean; private readonly foldingDecorationProvider: FoldingDecorationProvider; @@ -92,7 +92,7 @@ export class FoldingController extends Disposable implements IEditorContribution const options = this.editor.getOptions(); this._isEnabled = options.get(EditorOption.folding); this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation'; - this._unfoldOnClickInEmptyContent = options.get(EditorOption.unfoldOnClickInEmptyContent); + this._unfoldOnClickAfterEndOfLine = options.get(EditorOption.unfoldOnClickAfterEndOfLine); this.foldingModel = null; this.hiddenRangeModel = null; @@ -114,8 +114,7 @@ export class FoldingController extends Disposable implements IEditorContribution this._register(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { if (e.hasChanged(EditorOption.folding)) { - const options = this.editor.getOptions(); - this._isEnabled = options.get(EditorOption.folding); + this._isEnabled = this.editor.getOptions().get(EditorOption.folding); this.foldingEnabled.set(this._isEnabled); this.onModelChanged(); } @@ -126,12 +125,11 @@ export class FoldingController extends Disposable implements IEditorContribution this.onModelContentChanged(); } if (e.hasChanged(EditorOption.foldingStrategy)) { - const options = this.editor.getOptions(); - this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation'; + this._useFoldingProviders = this.editor.getOptions().get(EditorOption.foldingStrategy) !== 'indentation'; this.onFoldingStrategyChanged(); } - if (e.hasChanged(EditorOption.unfoldOnClickInEmptyContent)) { - this._unfoldOnClickInEmptyContent = options.get(EditorOption.unfoldOnClickInEmptyContent); + if (e.hasChanged(EditorOption.unfoldOnClickAfterEndOfLine)) { + this._unfoldOnClickAfterEndOfLine = this.editor.getOptions().get(EditorOption.unfoldOnClickAfterEndOfLine); } })); this.onModelChanged(); @@ -370,7 +368,7 @@ export class FoldingController extends Disposable implements IEditorContribution iconClicked = true; break; case MouseTargetType.CONTENT_EMPTY: { - if (this._unfoldOnClickInEmptyContent && this.hiddenRangeModel.hasRanges()) { + if (this._unfoldOnClickAfterEndOfLine && this.hiddenRangeModel.hasRanges()) { const data = e.target.detail as IEmptyContentData; if (!data.isAfterLines) { break; diff --git a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts index 6a26beb2be..348e3c8209 100644 --- a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts @@ -338,9 +338,8 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri private gotoDefinition(position: Position, openToSide: boolean): Promise { this.editor.setPosition(position); - const definitionLinkOpensInPeek = this.editor.getOption(EditorOption.definitionLinkOpensInPeek); return this.editor.invokeWithinContext((accessor) => { - const canPeek = definitionLinkOpensInPeek && !this.isInPeekEditor(accessor); + const canPeek = !openToSide && this.editor.getOption(EditorOption.definitionLinkOpensInPeek) && !this.isInPeekEditor(accessor); const action = new DefinitionAction({ openToSide, openInPeek: canPeek, muteMessage: true }, { alias: '', label: '', id: '', precondition: undefined }); return action.run(accessor, this.editor); }); diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index 2dbd50bf8a..390b134599 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -77,8 +77,8 @@ export class LinksList extends Disposable { const newLinks = list.links.map(link => new Link(link, provider)); links = LinksList._union(links, newLinks); // register disposables - if (isDisposable(provider)) { - this._register(provider); + if (isDisposable(list)) { + this._register(list); } } this.links = links; diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 13e857c231..9411fdaac4 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -206,7 +206,8 @@ class RenameController implements IEditorContribution { this._bulkEditService.apply(renameResult, { editor: this.editor, showPreview: inputFieldResult.wantsPreview, - label: nls.localize('label', "Renaming '{0}'", loc?.text) + label: nls.localize('label', "Renaming '{0}'", loc?.text), + quotableLabel: nls.localize('quotableLabel', "Renaming {0}", loc?.text), }).then(result => { if (result.ariaSummary) { alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, inputFieldResult.newName, result.ariaSummary)); diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index aff07a2b39..c2f7e4be76 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -234,7 +234,7 @@ flex-shrink: 0; } .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents > .main > .left > .monaco-icon-label { - max-width: 80%; + max-width: 100%; } .monaco-editor .suggest-widget .monaco-list .monaco-list-row.string-label > .contents > .main > .left > .monaco-icon-label { flex-shrink: 1; @@ -392,6 +392,10 @@ word-wrap: break-word; } +.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs .codicon { + vertical-align: sub; +} + .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > p:empty { display: none; } diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index bc5fd02f1c..a730cf819f 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -552,7 +552,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate toggleClass(this.element, 'with-status-bar', !this.editor.getOption(EditorOption.suggest).hideStatusBar); + const applyStatusBarStyle = () => toggleClass(this.element, 'with-status-bar', this.editor.getOption(EditorOption.suggest).statusBar.visible); applyStatusBarStyle(); this.statusBarElement = append(this.element, $('.suggest-status-bar')); diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index ad65530114..cd362822bf 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -33,7 +33,7 @@ interface IEditorLayoutProviderOpts { readonly minimapSide: 'left' | 'right'; readonly minimapRenderCharacters: boolean; readonly minimapMaxColumn: number; - minimapMode?: 'actual' | 'cover' | 'contain'; + minimapSize?: 'proportional' | 'fill' | 'fit'; readonly pixelRatio: number; } @@ -47,7 +47,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { options._write(EditorOption.folding, false); const minimapOptions: EditorMinimapOptions = { enabled: input.minimap, - mode: input.minimapMode || 'actual', + size: input.minimapSize || 'proportional', side: input.minimapSide, renderCharacters: input.minimapRenderCharacters, maxColumn: input.minimapMaxColumn, @@ -978,7 +978,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { minimapSide: 'right', minimapRenderCharacters: true, minimapMaxColumn: 150, - minimapMode: 'cover', + minimapSize: 'fill', pixelRatio: 2, }, { width: 1000, @@ -1042,7 +1042,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { minimapSide: 'right', minimapRenderCharacters: true, minimapMaxColumn: 150, - minimapMode: 'cover', + minimapSize: 'fill', pixelRatio: 2, }, { width: 1000, @@ -1106,7 +1106,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { minimapSide: 'right', minimapRenderCharacters: true, minimapMaxColumn: 150, - minimapMode: 'contain', + minimapSize: 'fit', pixelRatio: 2, }, { width: 1000, @@ -1170,7 +1170,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { minimapSide: 'right', minimapRenderCharacters: true, minimapMaxColumn: 150, - minimapMode: 'contain', + minimapSize: 'fit', pixelRatio: 2, }, { width: 1000, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index d1c3fa64f9..4d57e4101c 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3035,7 +3035,7 @@ declare namespace monaco.editor { * Controls whether clicking on the empty content after a folded line will unfold the line. * Defaults to false. */ - unfoldOnClickInEmptyContent?: boolean; + unfoldOnClickAfterEndOfLine?: boolean; /** * Enable highlighting of matching brackets. * Defaults to 'always'. @@ -3448,7 +3448,7 @@ declare namespace monaco.editor { * Control the minimap rendering mode. * Defaults to 'actual'. */ - mode?: 'actual' | 'cover' | 'contain'; + size?: 'proportional' | 'fill' | 'fit'; /** * Control the rendering of the minimap slider. * Defaults to 'mouseover'. @@ -3758,9 +3758,14 @@ declare namespace monaco.editor { */ showSnippets?: boolean; /** - * Controls the visibility of the status bar at the bottom of the suggest widget. + * Status bar related settings. */ - hideStatusBar?: boolean; + statusBar?: { + /** + * Controls the visibility of the status bar at the bottom of the suggest widget. + */ + visible?: boolean; + }; } export type InternalSuggestOptions = Readonly>; @@ -3829,7 +3834,7 @@ declare namespace monaco.editor { folding = 31, foldingStrategy = 32, foldingHighlight = 33, - unfoldOnClickInEmptyContent = 34, + unfoldOnClickAfterEndOfLine = 34, fontFamily = 35, fontInfo = 36, fontLigatures = 37, @@ -3945,7 +3950,7 @@ declare namespace monaco.editor { folding: IEditorOption; foldingStrategy: IEditorOption; foldingHighlight: IEditorOption; - unfoldOnClickInEmptyContent: IEditorOption; + unfoldOnClickAfterEndOfLine: IEditorOption; fontFamily: IEditorOption; fontInfo: IEditorOption; fontLigatures2: IEditorOption; @@ -5451,7 +5456,7 @@ declare namespace monaco.languages { preselect?: boolean; /** * A string or snippet that should be inserted in a document when selecting - * this completion. When `falsy` the [label](#CompletionItem.label) + * this completion. * is used. */ insertText: string; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 926a551601..10595116c9 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -112,7 +112,7 @@ export class EnvironmentService implements IEnvironmentService { get settingsResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'settings.json'); } @memoize - get userDataSyncHome(): URI { return resources.joinPath(this.userRoamingDataHome, '.sync'); } + get userDataSyncHome(): URI { return resources.joinPath(this.userRoamingDataHome, 'sync'); } @memoize get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'settings.json'); } diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 617fb78ce3..9ab153e5bd 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -101,6 +101,12 @@ export interface INotification extends INotificationProperties { * this usecase and much easier to use! */ actions?: INotificationActions; + + /** + * The initial set of progress properties for the notification. To update progress + * later on, access the `INotificationHandle.progress` property. + */ + progress?: INotificationProgressProperties; } export interface INotificationActions { @@ -119,6 +125,24 @@ export interface INotificationActions { secondary?: ReadonlyArray; } +export interface INotificationProgressProperties { + + /** + * Causes the progress bar to spin infinitley. + */ + infinite?: boolean; + + /** + * Indicate the total amount of work. + */ + total?: number; + + /** + * Indicate that a specific chunk of work is done. + */ + worked?: number; +} + export interface INotificationProgress { /** @@ -149,6 +173,13 @@ export interface INotificationHandle { */ readonly onDidClose: Event; + /** + * Will be fired whenever the visibility of the notification changes. + * A notification can either be visible as toast or inside the notification + * center if it is visible. + */ + readonly onDidChangeVisibility: Event; + /** * Allows to indicate progress on the notification even after the * notification is already visible. diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index 9db829273f..fd0f260b8b 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -240,9 +240,36 @@ export class FileStorageDatabase extends Disposable implements IStorageDatabase private async onDidStorageChangeExternal(): Promise { const items = await this.doGetItemsFromFile(); + // pervious cache, diff for changes + let changed = new Map(); + let deleted = new Set(); + if (this.cache) { + items.forEach((value, key) => { + const existingValue = this.cache?.get(key); + if (existingValue !== value) { + changed.set(key, value); + } + }); + + this.cache.forEach((_, key) => { + if (!items.has(key)) { + deleted.add(key); + } + }); + } + + // no previous cache, consider all as changed + else { + changed = items; + } + + // Update cache this.cache = items; - this._onDidChangeItemsExternal.fire({ items }); + // Emit as event as needed + if (changed.size > 0 || deleted.size > 0) { + this._onDidChangeItemsExternal.fire({ changed, deleted }); + } } async getItems(): Promise> { diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index 2c78b05d3c..76858dcde2 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -24,15 +24,16 @@ interface ISerializableUpdateRequest { } interface ISerializableItemsChangeEvent { - items: Item[]; + changed?: Item[]; + deleted?: Key[]; } export class GlobalStorageDatabaseChannel extends Disposable implements IServerChannel { private static readonly STORAGE_CHANGE_DEBOUNCE_TIME = 100; - private readonly _onDidChangeItems: Emitter = this._register(new Emitter()); - readonly onDidChangeItems: Event = this._onDidChangeItems.event; + private readonly _onDidChangeItems = this._register(new Emitter()); + readonly onDidChangeItems = this._onDidChangeItems.event; private whenReady: Promise; @@ -99,15 +100,18 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC } private serializeEvents(events: IStorageChangeEvent[]): ISerializableItemsChangeEvent { - const items = new Map(); + const changed = new Map(); + const deleted = new Set(); events.forEach(event => { const existing = this.storageMainService.get(event.key); if (typeof existing === 'string') { - items.set(event.key, existing); + changed.set(event.key, existing); + } else { + deleted.add(event.key); } }); - return { items: mapToSerializable(items) }; + return { changed: mapToSerializable(changed), deleted: values(deleted) }; } listen(_: unknown, event: string): Event { @@ -170,8 +174,11 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS } private onDidChangeItemsOnMain(e: ISerializableItemsChangeEvent): void { - if (Array.isArray(e.items)) { - this._onDidChangeItemsExternal.fire({ items: serializableToMap(e.items) }); + if (Array.isArray(e.changed) || Array.isArray(e.deleted)) { + this._onDidChangeItemsExternal.fire({ + changed: e.changed ? serializableToMap(e.changed) : undefined, + deleted: e.deleted ? new Set(e.deleted) : undefined + }); } } diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 33517db849..c892193d53 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage } from 'vs/platform/storage/common/storage'; import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage'; @@ -25,11 +25,11 @@ export class NativeStorageService extends Disposable implements IStorageService private static readonly WORKSPACE_STORAGE_NAME = 'state.vscdb'; private static readonly WORKSPACE_META_NAME = 'workspace.json'; - private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); - readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; + private readonly _onDidChangeStorage = this._register(new Emitter()); + readonly onDidChangeStorage = this._onDidChangeStorage.event; - private readonly _onWillSaveState: Emitter = this._register(new Emitter()); - readonly onWillSaveState: Event = this._onWillSaveState.event; + private readonly _onWillSaveState = this._register(new Emitter()); + readonly onWillSaveState = this._onWillSaveState.event; private globalStorage: IStorage; diff --git a/src/vs/platform/undoRedo/common/undoRedoService.ts b/src/vs/platform/undoRedo/common/undoRedoService.ts index 77e5315055..809f398dfd 100644 --- a/src/vs/platform/undoRedo/common/undoRedoService.ts +++ b/src/vs/platform/undoRedo/common/undoRedoService.ts @@ -260,7 +260,7 @@ export class UndoRedoService implements IUndoRedoService { this._splitPastWorkspaceElement(element, element.removedResources.set); const message = nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.removedResources.createMessage()); this._notificationService.info(message); - return; + return this.undo(resource); } // this must be the last past element in all the impacted resources! @@ -281,18 +281,25 @@ export class UndoRedoService implements IUndoRedoService { const paths = cannotUndoDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path); const message = nls.localize('cannotWorkspaceUndoDueToChanges', "Could not undo '{0}' across all files because changes were made to {1}", element.label, paths.join(', ')); this._notificationService.info(message); - return; + return this.undo(resource); } return this._dialogService.show( Severity.Info, nls.localize('confirmWorkspace', "Would you like to undo '{0}' across all files?", element.label), [ - nls.localize('ok', "Yes, change {0} files.", affectedEditStacks.length), - nls.localize('nok', "No, change only this file.") - ] + nls.localize('ok', "Undo In {0} Files", affectedEditStacks.length), + nls.localize('nok', "Undo This File"), + nls.localize('cancel', "Cancel"), + ], + { + cancelId: 2 + } ).then((result) => { - if (result.choice === 0) { + if (result.choice === 2) { + // cancel + return; + } else if (result.choice === 0) { for (const editStack of affectedEditStacks) { editStack.past.pop(); editStack.future.push(element); @@ -344,7 +351,7 @@ export class UndoRedoService implements IUndoRedoService { this._splitFutureWorkspaceElement(element, element.removedResources.set); const message = nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.removedResources.createMessage()); this._notificationService.info(message); - return; + return this.redo(resource); } // this must be the last future element in all the impacted resources! @@ -365,7 +372,7 @@ export class UndoRedoService implements IUndoRedoService { const paths = cannotRedoDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path); const message = nls.localize('cannotWorkspaceRedoDueToChanges', "Could not redo '{0}' across all files because changes were made to {1}", element.label, paths.join(', ')); this._notificationService.info(message); - return; + return this.redo(resource); } for (const editStack of affectedEditStacks) { diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 9fee079ba7..d80baa7804 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -68,7 +68,7 @@ export abstract class AbstractSynchroniser extends Disposable { ) { super(); this.syncFolder = joinPath(environmentService.userDataSyncHome, source); - this.lastSyncResource = joinPath(this.syncFolder, `.lastSync${source}.json`); + this.lastSyncResource = joinPath(this.syncFolder, `lastSync${source}.json`); this.cleanUpDelayer = new ThrottledDelayer(50); this.cleanUpBackup(); } @@ -202,7 +202,7 @@ export abstract class AbstractSynchroniser extends Disposable { } protected async backupLocal(content: VSBuffer): Promise { - const resource = joinPath(this.syncFolder, toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')); + const resource = joinPath(this.syncFolder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); try { await this.fileService.writeFile(resource, content); } catch (e) { @@ -213,9 +213,13 @@ export abstract class AbstractSynchroniser extends Disposable { private async cleanUpBackup(): Promise { try { + if (!(await this.fileService.exists(this.syncFolder))) { + return; + } const stat = await this.fileService.resolve(this.syncFolder); if (stat.children) { - const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)).sort(); + const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort(); + console.log(all.map(a => a.name)); const backUpMaxAge = 1000 * 60 * 60 * 24 * (this.configurationService.getValue('sync.localBackupDuration') || 30 /* Default 30 days */); let toDelete = all.filter(stat => { const ctime = stat.ctime || new Date( diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index a5f4ff944b..e0433b82b9 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -194,7 +194,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (added.length || removed.length || updated.length) { // back up all disabled or market place extensions const backUpExtensions = localExtensions.filter(e => e.disabled || !!e.identifier.uuid); - await this.backupLocal(VSBuffer.fromString(JSON.stringify(backUpExtensions))); + await this.backupLocal(VSBuffer.fromString(JSON.stringify(backUpExtensions, null, '\t'))); skippedExtensions = await this.updateLocalExtensions(added, removed, updated, skippedExtensions); } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 3f4910fbf1..8e369b2243 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -165,7 +165,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (local) { // update local this.logService.trace('UI State: Updating local ui state...'); - await this.backupLocal(VSBuffer.fromString(JSON.stringify(localUserData))); + await this.backupLocal(VSBuffer.fromString(JSON.stringify(localUserData, null, '\t'))); await this.writeLocalGlobalState(local); this.logService.info('UI State: Updated local ui state'); } diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index cc3e2ada6d..d05ddd7a25 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -8,6 +8,11 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +type AutoSyncTriggerClassification = { + source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService { @@ -25,6 +30,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); this.updateEnablement(false, true); @@ -32,7 +38,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto this._register(Event.any(authTokenService.onDidChangeToken)(() => this.updateEnablement(true, true))); this._register(Event.any(userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true, true))); this._register(this.userDataSyncEnablementService.onDidChangeEnablement(() => this.updateEnablement(true, false))); - this._register(this.userDataSyncEnablementService.onDidChangeResourceEnablement(() => this.triggerAutoSync())); + this._register(this.userDataSyncEnablementService.onDidChangeResourceEnablement(() => this.triggerAutoSync(['resourceEnablement']))); } private async updateEnablement(stopIfDisabled: boolean, auto: boolean): Promise { @@ -99,7 +105,8 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto this.successiveFailures = 0; } - async triggerAutoSync(): Promise { + async triggerAutoSync(sources: string[]): Promise { + sources.forEach(source => this.telemetryService.publicLog2<{ source: string }, AutoSyncTriggerClassification>('sync/triggerAutoSync', { source })); if (this.enabled) { return this.syncDelayer.trigger(() => { this.logService.info('Auto Sync: Triggered.'); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index d978311e27..f6147a7009 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -278,7 +278,7 @@ export interface IUserDataSyncService { readonly conflictsSources: SyncSource[]; readonly onDidChangeConflicts: Event; - readonly onDidChangeLocal: Event; + readonly onDidChangeLocal: Event; readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]>; readonly lastSyncTime: number | undefined; @@ -299,7 +299,7 @@ export const IUserDataAutoSyncService = createDecorator; - triggerAutoSync(): Promise; + triggerAutoSync(sources: string[]): Promise; } export const IUserDataSyncUtilService = createDecorator('IUserDataSyncUtilService'); diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index e32aa9c2a4..3a0b48a8e5 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -86,7 +86,7 @@ export class UserDataAutoSyncChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { - case 'triggerAutoSync': return this.service.triggerAutoSync(); + case 'triggerAutoSync': return this.service.triggerAutoSync(args[0]); } throw new Error('Invalid call'); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index f4f8d568e6..73b28d8604 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -34,7 +34,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - readonly onDidChangeLocal: Event; + readonly onDidChangeLocal: Event; private _conflictsSources: SyncSource[] = []; get conflictsSources(): SyncSource[] { return this._conflictsSources; } @@ -74,7 +74,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } this._lastSyncTime = this.storageService.getNumber(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL, undefined); - this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); + this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeLocal, () => s.source))); } async pull(): Promise { diff --git a/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts index 312eba6a8c..8dac6aa191 100644 --- a/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -8,6 +8,7 @@ import { Event } from 'vs/base/common/event'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { UserDataAutoSyncService as BaseUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { @@ -17,15 +18,15 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { @IElectronService electronService: IElectronService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IAuthenticationTokenService authTokenService: IAuthenticationTokenService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService); + super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService, telemetryService); - // Sync immediately if there is a local change. - this._register(Event.debounce(Event.any( - electronService.onWindowFocus, - electronService.onWindowOpen, + this._register(Event.debounce(Event.any( + Event.map(electronService.onWindowFocus, () => 'windowFocus'), + Event.map(electronService.onWindowOpen, () => 'windowOpen'), userDataSyncService.onDidChangeLocal, - ), () => undefined, 500)(() => this.triggerAutoSync())); + ), (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerAutoSync(sources))); } } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 9ccc6bac79..3ae0fc1e87 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1500,7 +1500,7 @@ declare module 'vscode' { /** * A file system watcher notifies about changes to files and folders - * on disk. + * on disk or from other [FileSystemProviders](#FileSystemProvider). * * To get an instance of a `FileSystemWatcher` use * [createFileSystemWatcher](#workspace.createFileSystemWatcher). @@ -1806,7 +1806,7 @@ declare module 'vscode' { placeHolder?: string; /** - * Set to `true` to show a password prompt that will not show the typed value. + * Controls if a password input is shown. Password input hides the typed text. */ password?: boolean; @@ -2020,7 +2020,7 @@ declare module 'vscode' { * Base kind for source actions: `source` * * Source code actions apply to the entire file. They must be explicitly requested and will not show in the - * normal [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) menu. Source actions + * normal [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) menu. Source actions * can be run on save using `editor.codeActionsOnSave` and are also shown in the `source` context menu. */ static readonly Source: CodeActionKind; @@ -2086,7 +2086,7 @@ declare module 'vscode' { /** * Requested kind of actions to return. * - * Actions not of this kind are filtered out before being shown by the lightbulb. + * Actions not of this kind are filtered out before being shown by the [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action). */ readonly only?: CodeActionKind; } @@ -2138,7 +2138,15 @@ declare module 'vscode' { /** * Marks that the code action cannot currently be applied. * - * Disabled code actions will be surfaced in the refactor UI but cannot be applied. + * - Disabled code actions are not shown in automatic [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) + * code action menu. + * + * - Disabled actions are shown as faded out in the code action menu when the user request a more specific type + * of code action, such as refactorings. + * + * - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) + * that auto applies a code action and only a disabled code actions are returned, VS Code will show the user a + * message with `reason` in the editor. */ disabled?: { /** @@ -2163,7 +2171,7 @@ declare module 'vscode' { /** * The code action interface defines the contract between extensions and - * the [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature. + * the [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature. * * A code action can be any command that is [known](#commands.getCommands) to the system. */ @@ -2495,16 +2503,18 @@ declare module 'vscode' { /** * The evaluatable expression provider interface defines the contract between extensions and - * the debug hover. + * the debug hover. In this contract the provider returns an evaluatable expression for a given position + * in a document and VS Code evaluates this expression in the active debug session and shows the result in a debug hover. */ export interface EvaluatableExpressionProvider { /** * Provide an evaluatable expression for the given document and position. + * VS Code will evaluate this expression in the active debug session and will show the result in the debug hover. * The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression. * - * @param document The document in which the debug hover is opened. - * @param position The position in the document where the debug hover is opened. + * @param document The document for which the debug hover is about to appear. + * @param position The line and character position in the document where the debug hover is about to appear. * @param token A cancellation token. * @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be * signaled by returning `undefined` or `null`. @@ -9174,6 +9184,7 @@ declare module 'vscode' { /** * Register a provider that locates evaluatable expressions in text documents. + * VS Code will evaluate the expression in the active debug session and will show the result in the debug hover. * * If multiple providers are registered for a language an arbitrary provider will be used. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 8ef1701611..0b2cdb7fe5 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -20,7 +20,7 @@ declare module 'vscode' { export interface AuthenticationSession { id: string; - accessToken(): Promise; + getAccessToken(): Thenable; accountName: string; scopes: string[] } @@ -58,13 +58,13 @@ declare module 'vscode' { /** * Returns an array of current sessions. */ - getSessions(): Promise>; + getSessions(): Thenable>; /** * Prompts a user to login. */ - login(scopes: string[]): Promise; - logout(sessionId: string): Promise; + login(scopes: string[]): Thenable; + logout(sessionId: string): Thenable; } export namespace authentication { @@ -1273,14 +1273,14 @@ declare module 'vscode' { * in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather * than cancelling it to ensure that VS Code has some valid backup. */ - backup(cancellation: CancellationToken): Thenable; + backup(cancellation: CancellationToken): Thenable; } /** * Represents a custom document for a custom webview editor. * - * Custom documents are only used within a given `WebviewCustomEditorProvider`. The lifecycle of a - * `WebviewEditorCustomDocument` is managed by VS Code. When more more references remain to a given `WebviewEditorCustomDocument` + * Custom documents are only used within a given `CustomEditorProvider`. The lifecycle of a + * `CustomDocument` is managed by VS Code. When more more references remain to a given `CustomDocument` * then it is disposed of. * * @param UserDataType Type of custom object that extensions can store on the document. @@ -1297,7 +1297,7 @@ declare module 'vscode' { readonly uri: Uri; /** - * Event fired when there are no more references to the `WebviewEditorCustomDocument`. + * Event fired when there are no more references to the `CustomDocument`. */ readonly onDidDispose: Event; @@ -1313,7 +1313,7 @@ declare module 'vscode' { /** * Provider for webview editors that use a custom data model. * - * Custom webview editors use [`WebviewEditorCustomDocument`](#WebviewEditorCustomDocument) as their data model. + * Custom webview editors use [`CustomDocument`](#CustomDocument) as their data model. * This gives extensions full control over actions such as edit, save, and backup. * * You should use custom text based editors when dealing with binary files or more complex scenarios. For simple text @@ -1321,24 +1321,26 @@ declare module 'vscode' { */ export interface CustomEditorProvider { /** - * Create the model for a given + * Resolve the model for a given resource. * - * @param document Resource being resolved. + * @param document Document to resolve. + * + * @return The capabilities of the resolved document. */ resolveCustomDocument(document: CustomDocument): Thenable; /** * Resolve a webview editor for a given resource. * - * To resolve a webview editor, a provider must fill in its initial html content and hook up all + * To resolve a webview editor, the provider must fill in its initial html content and hook up all * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. * - * @param document Document for resource being resolved. - * @param webview Webview being resolved. The provider should take ownership of this webview. + * @param document Document for the resource being resolved. + * @param webviewPanel Webview to resolve. The provider should take ownership of this webview. * * @return Thenable indicating that the webview editor has been resolved. */ - resolveCustomEditor(document: CustomDocument, webview: WebviewPanel): Thenable; + resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel): Thenable; } /** @@ -1349,7 +1351,7 @@ declare module 'vscode' { * undo and backup. The provider is responsible for synchronizing text changes between the webview and the `TextDocument`. * * You should use text based webview editors when dealing with text based file formats, such as `xml` or `json`. - * For binary files or more specialized use cases, see [WebviewCustomEditorProvider](#WebviewCustomEditorProvider). + * For binary files or more specialized use cases, see [CustomEditorProvider](#CustomEditorProvider). */ export interface CustomTextEditorProvider { /** @@ -1359,11 +1361,11 @@ declare module 'vscode' { * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. * * @param document Resource being resolved. - * @param webview Webview being resolved. The provider should take ownership of this webview. + * @param webviewPanel Webview to resolve. The provider should take ownership of this webview. * * @return Thenable indicating that the webview editor has been resolved. */ - resolveCustomTextEditor(document: TextDocument, webview: WebviewPanel): Thenable; + resolveCustomTextEditor(document: TextDocument, webviewPanel: WebviewPanel): Thenable; } namespace window { @@ -1680,9 +1682,10 @@ declare module 'vscode' { * * The documentation is shown in the code actions menu if either: * - * - Code actions of `kind` are requested by VS Code. Note that in this case, we always pick the most specific - * documentation. For example, if documentation for both `Refactor` and `RefactorExtract` is provided, and we - * request code actions for `RefactorExtract`, we prefer the more specific documentation for `RefactorExtract`. + * - Code actions of `kind` are requested by VS Code. In this case, VS Code will show the documentation that + * most closely matches the requested code action kind. For example, if a provider has documentation for + * both `Refactor` and `RefactorExtract`, when the user requests code actions for `RefactorExtract`, + * VS Code will use the documentation for `RefactorExtract` intead of the documentation for `Refactor`. * * - Any code actions of `kind` are returned by the provider. */ @@ -1703,7 +1706,10 @@ declare module 'vscode' { */ export interface OpenDialogOptions { /** - * Dialog title + * Dialog title. + * + * Depending on the underlying operating system this parameter might be ignored, since some + * systems do not present title on open dialogs. */ title?: string; } @@ -1713,7 +1719,10 @@ declare module 'vscode' { */ export interface SaveDialogOptions { /** - * Dialog title + * Dialog title. + * + * Depending on the underlying operating system this parameter might be ignored, since some + * systems do not present title on save dialogs. */ title?: string; } diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 8f583d34e8..7beafd7adb 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -25,7 +25,7 @@ export class MainThreadAuthenticationProvider { return { id: session.id, accountName: session.accountName, - accessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id) + getAccessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id) }; }); } @@ -35,7 +35,7 @@ export class MainThreadAuthenticationProvider { return { id: session.id, accountName: session.accountName, - accessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id) + getAccessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id) }; }); } @@ -75,48 +75,52 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu async $getSessionsPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise { const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL); if (alwaysAllow) { - return true; + return alwaysAllow === 'true'; } - const { choice } = await this.dialogService.show( + const { choice, checkboxChecked } = await this.dialogService.show( Severity.Info, nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information from {1}.", extensionName, providerName), - [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow"), nls.localize('alwaysAllow', "Always Allow"),], - { cancelId: 0 } + [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")], + { + cancelId: 0, + checkbox: { + label: nls.localize('neverAgain', "Don't Show Again") + } + } ); - switch (choice) { - case 1/** Allow */: - return true; - case 2 /** Always Allow */: - this.storageService.store(`${extensionId}-${providerId}`, 'true', StorageScope.GLOBAL); - return true; - default: - return false; + const allow = choice === 1; + if (checkboxChecked) { + this.storageService.store(`${extensionId}-${providerId}`, allow ? 'true' : 'false', StorageScope.GLOBAL); } + + return allow; } async $loginPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise { const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL); if (alwaysAllow) { - return true; + return alwaysAllow === 'true'; } - const { choice } = await this.dialogService.show( + const { choice, checkboxChecked } = await this.dialogService.show( Severity.Info, nls.localize('confirmLogin', "The extension '{0}' wants to sign in using {1}.", extensionName, providerName), - [nls.localize('cancel', "Cancel"), nls.localize('continue', "Continue"), nls.localize('neverAgain', "Don't Show Again")], - { cancelId: 0 } + [nls.localize('cancel', "Cancel"), nls.localize('continue', "Continue")], + { + cancelId: 0, + checkbox: { + label: nls.localize('neverAgain', "Don't Show Again") + } + } ); - switch (choice) { - case 1/** Allow */: - return true; - case 2 /** Always Allow */: - this.storageService.store(`${extensionId}-${providerId}`, 'true', StorageScope.GLOBAL); - return true; - default: - return false; + const allow = choice === 1; + if (checkboxChecked) { + this.storageService.store(`${extensionId}-${providerId}`, allow ? 'true' : 'false', StorageScope.GLOBAL); } + + return allow; } } diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index a2d20294ea..cade69a1ac 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -122,7 +122,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._register(_webviewWorkbenchService.registerResolver({ canResolve: (webview: WebviewInput) => { if (webview instanceof CustomEditorInput) { - extensionService.activateByEvent(`onWebviewEditor:${webview.viewType}`); + extensionService.activateByEvent(`onCustomEditor:${webview.viewType}`); return false; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5645877b12..4ff6a6eba7 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -624,7 +624,7 @@ export interface ExtHostWebviewsShape { $onSave(resource: UriComponents, viewType: string): Promise; $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise; - $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; + $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; } export interface MainThreadUrlsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index db2898d4bb..88c658d26e 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -34,7 +34,7 @@ export class AuthenticationProviderWrapper implements vscode.AuthenticationProvi id: session.id, accountName: session.accountName, scopes: session.scopes, - accessToken: async () => { + getAccessToken: async () => { const isAllowed = await this._proxy.$getSessionsPrompt( this._provider.id, this.displayName, @@ -45,7 +45,7 @@ export class AuthenticationProviderWrapper implements vscode.AuthenticationProvi throw new Error('User did not consent to token access.'); } - return session.accessToken(); + return session.getAccessToken(); } }; }); @@ -60,7 +60,7 @@ export class AuthenticationProviderWrapper implements vscode.AuthenticationProvi return this._provider.login(scopes); } - logout(sessionId: string): Promise { + logout(sessionId: string): Thenable { return this._provider.logout(sessionId); } } @@ -137,7 +137,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { const sessions = await authProvider.getSessions(); const session = sessions.find(session => session.id === sessionId); if (session) { - return session.accessToken(); + return session.getAccessToken(); } throw new Error(`Unable to find session with id: ${sessionId}`); diff --git a/src/vs/workbench/api/common/extHostTimeline.ts b/src/vs/workbench/api/common/extHostTimeline.ts index b7b8b44f22..6480c766cd 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -71,11 +71,17 @@ export class ExtHostTimeline implements IExtHostTimeline { scheme: scheme, onDidChange: undefined, async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) { - timelineDisposables.clear(); - // For now, only allow the caching of a single Uri - if (internalOptions?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) { - itemsBySourceByUriMap.clear(); + if (internalOptions?.cacheResults) { + if (options.cursor === undefined) { + timelineDisposables.clear(); + } + + if (!itemsBySourceByUriMap.has(getUriKey(uri))) { + itemsBySourceByUriMap.clear(); + } + } else { + timelineDisposables.clear(); } const result = await provider.provideTimeline(uri, options, token); diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 1779060450..fded359e27 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -247,143 +247,160 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa type EditType = unknown; -class WebviewEditorCustomDocument extends Disposable implements vscode.CustomDocument { - private _currentEditIndex: number = -1; - private _savePoint: number = -1; - private readonly _edits: Array = []; +class CustomDocument extends Disposable implements vscode.CustomDocument { - public userData: unknown; - - public _capabilities?: vscode.CustomEditorCapabilities = undefined; - - constructor( - private readonly _proxy: MainThreadWebviewsShape, - public readonly viewType: string, - public readonly uri: vscode.Uri, - ) { - super(); + public static create(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) { + return Object.seal(new CustomDocument(proxy, viewType, uri)); } - _setCapabilities(capabilities: vscode.CustomEditorCapabilities) { - if (this._capabilities) { - throw new Error('Capabilities already provided'); - } + // Explicitly initialize all properties as we seal the object after creation! - this._capabilities = capabilities; - capabilities.editing?.onDidEdit(edit => { - this.pushEdit(edit, this); - }); + #currentEditIndex: number = -1; + #savePoint: number = -1; + readonly #edits: Array = []; + + readonly #proxy: MainThreadWebviewsShape; + readonly #viewType: string; + readonly #uri: vscode.Uri; + + #capabilities: vscode.CustomEditorCapabilities | undefined = undefined; + + private constructor(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) { + super(); + this.#proxy = proxy; + this.#viewType = viewType; + this.#uri = uri; + } + + dispose() { + this.#onDidDispose.fire(); + super.dispose(); } //#region Public API - #_onDidDispose = this._register(new Emitter()); - public readonly onDidDispose = this.#_onDidDispose.event; + public get viewType(): string { return this.#viewType; } + + public get uri(): vscode.Uri { return this.#uri; } + + #onDidDispose = this._register(new Emitter()); + public readonly onDidDispose = this.#onDidDispose.event; + + public userData: unknown = undefined; //#endregion - dispose() { - this.#_onDidDispose.fire(); - super.dispose(); + //#region Internal + + /** @internal*/ _setCapabilities(capabilities: vscode.CustomEditorCapabilities) { + if (this.#capabilities) { + throw new Error('Capabilities already provided'); + } + + this.#capabilities = capabilities; + capabilities.editing?.onDidEdit(edit => { + this.pushEdit(edit); + }); } - private pushEdit(edit: EditType, trigger: any) { - this.spliceEdits(edit); - - this._currentEditIndex = this._edits.length - 1; - this.updateState(); - // this._onApplyEdit.fire({ edits: [edit], trigger }); - } - - private updateState() { - const dirty = this._edits.length > 0 && this._savePoint !== this._currentEditIndex; - this._proxy.$onDidChangeCustomDocumentState(this.uri, this.viewType, { dirty }); - } - - private spliceEdits(editToInsert?: EditType) { - const start = this._currentEditIndex + 1; - const toRemove = this._edits.length - this._currentEditIndex; - - editToInsert - ? this._edits.splice(start, toRemove, editToInsert) - : this._edits.splice(start, toRemove); - } - - revert() { + /** @internal*/ _revert() { const editing = this.getEditingCapability(); - if (this._currentEditIndex === this._savePoint) { + if (this.#currentEditIndex === this.#savePoint) { return true; } - if (this._currentEditIndex >= this._savePoint) { - const editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex); + if (this.#currentEditIndex >= this.#savePoint) { + const editsToUndo = this.#edits.slice(this.#savePoint, this.#currentEditIndex); editing.undoEdits(editsToUndo.reverse()); - } else if (this._currentEditIndex < this._savePoint) { - const editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); + } else if (this.#currentEditIndex < this.#savePoint) { + const editsToRedo = this.#edits.slice(this.#currentEditIndex, this.#savePoint); editing.applyEdits(editsToRedo); } - this._currentEditIndex = this._savePoint; + this.#currentEditIndex = this.#savePoint; this.spliceEdits(); this.updateState(); return true; } - undo() { + /** @internal*/ _undo() { const editing = this.getEditingCapability(); - if (this._currentEditIndex < 0) { + if (this.#currentEditIndex < 0) { // nothing to undo return; } - const undoneEdit = this._edits[this._currentEditIndex]; - --this._currentEditIndex; + const undoneEdit = this.#edits[this.#currentEditIndex]; + --this.#currentEditIndex; editing.undoEdits([undoneEdit]); this.updateState(); } - redo() { + /** @internal*/ _redo() { const editing = this.getEditingCapability(); - if (this._currentEditIndex >= this._edits.length - 1) { + if (this.#currentEditIndex >= this.#edits.length - 1) { // nothing to redo return; } - ++this._currentEditIndex; - const redoneEdit = this._edits[this._currentEditIndex]; + ++this.#currentEditIndex; + const redoneEdit = this.#edits[this.#currentEditIndex]; editing.applyEdits([redoneEdit]); this.updateState(); } - save() { + /** @internal*/ _save() { return this.getEditingCapability().save(); } - saveAs(target: vscode.Uri) { + /** @internal*/ _saveAs(target: vscode.Uri) { return this.getEditingCapability().saveAs(target); } - backup(cancellation: CancellationToken) { + /** @internal*/ _backup(cancellation: CancellationToken) { return this.getEditingCapability().backup(cancellation); } + //#endregion + + private pushEdit(edit: EditType) { + this.spliceEdits(edit); + + this.#currentEditIndex = this.#edits.length - 1; + this.updateState(); + } + + private updateState() { + const dirty = this.#edits.length > 0 && this.#savePoint !== this.#currentEditIndex; + this.#proxy.$onDidChangeCustomDocumentState(this.uri, this.viewType, { dirty }); + } + + private spliceEdits(editToInsert?: EditType) { + const start = this.#currentEditIndex + 1; + const toRemove = this.#edits.length - this.#currentEditIndex; + + editToInsert + ? this.#edits.splice(start, toRemove, editToInsert) + : this.#edits.splice(start, toRemove); + } + private getEditingCapability(): vscode.CustomEditorEditingCapability { - if (!this._capabilities?.editing) { + if (!this.#capabilities?.editing) { throw new Error('Document is not editable'); } - return this._capabilities.editing; + return this.#capabilities.editing; } } class WebviewDocumentStore { - private readonly _documents = new Map(); + private readonly _documents = new Map(); - public get(viewType: string, resource: vscode.Uri): WebviewEditorCustomDocument | undefined { + public get(viewType: string, resource: vscode.Uri): CustomDocument | undefined { return this._documents.get(this.key(viewType, resource)); } - public add(document: WebviewEditorCustomDocument) { + public add(document: CustomDocument) { const key = this.key(document.viewType, document.uri); if (this._documents.has(key)) { throw new Error(`Document already exists for viewType:${document.viewType} resource:${document.uri}`); @@ -391,7 +408,7 @@ class WebviewDocumentStore { this._documents.set(key, document); } - public delete(document: WebviewEditorCustomDocument) { + public delete(document: CustomDocument) { const key = this.key(document.viewType, document.uri); this._documents.delete(key); } @@ -622,7 +639,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const revivedResource = URI.revive(resource); - const document = Object.seal(new WebviewEditorCustomDocument(this._proxy, viewType, revivedResource)); + const document = CustomDocument.create(this._proxy, viewType, revivedResource); const capabilities = await entry.provider.resolveCustomDocument(document); document._setCapabilities(capabilities); this._documents.add(document); @@ -687,39 +704,39 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { async $undo(resourceComponents: UriComponents, viewType: string): Promise { const document = this.getDocument(viewType, resourceComponents); - document.undo(); + document._undo(); } async $redo(resourceComponents: UriComponents, viewType: string): Promise { const document = this.getDocument(viewType, resourceComponents); - document.redo(); + document._redo(); } async $revert(resourceComponents: UriComponents, viewType: string): Promise { const document = this.getDocument(viewType, resourceComponents); - document.revert(); + document._revert(); } async $onSave(resourceComponents: UriComponents, viewType: string): Promise { const document = this.getDocument(viewType, resourceComponents); - document.save(); + document._save(); } async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents): Promise { const document = this.getDocument(viewType, resourceComponents); - return document.saveAs(URI.revive(targetResource)); + return document._saveAs(URI.revive(targetResource)); } - async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { + async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { const document = this.getDocument(viewType, resourceComponents); - return document.backup(cancellation); + return document._backup(cancellation); } private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { return this._webviewPanels.get(handle); } - private getDocument(viewType: string, resource: UriComponents): WebviewEditorCustomDocument { + private getDocument(viewType: string, resource: UriComponents): CustomDocument { const document = this._documents.get(viewType, URI.revive(resource)); if (!document) { throw new Error('No webview editor custom document found'); diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index eb3f7b3474..8c26fb41a3 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -25,9 +25,10 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, FocusedViewContext, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; const registry = Registry.as(WorkbenchExtensions.WorkbenchActions); const viewCategory = nls.localize('view', "View"); @@ -534,6 +535,7 @@ export class MoveFocusedViewAction extends Action { @IQuickInputService private quickInputService: IQuickInputService, @IContextKeyService private contextKeyService: IContextKeyService, @INotificationService private notificationService: INotificationService, + @IActivityBarService private activityBarService: IActivityBarService, @IViewletService private viewletService: IViewletService ) { super(id, label); @@ -542,58 +544,64 @@ export class MoveFocusedViewAction extends Action { run(): Promise { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); - const focusedView = FocusedViewContext.getValue(this.contextKeyService); + const focusedViewId = FocusedViewContext.getValue(this.contextKeyService); - if (focusedView === undefined || focusedView.trim() === '') { + if (focusedViewId === undefined || focusedViewId.trim() === '') { this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused.")); return Promise.resolve(); } - const viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedView); + const viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedViewId); if (!viewDescriptor || !viewDescriptor.canMoveView) { - this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable {0}.", focusedView)); + this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable.")); return Promise.resolve(); } const quickPick = this.quickInputService.createQuickPick(); - quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a destination area for the view..."); - quickPick.autoFocusOnList = true; + quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a Destination for the View"); - quickPick.items = [ - { - id: 'sidebar', - label: nls.localize('sidebar', "Sidebar") - }, - { - id: 'panel', + const pinnedViewlets = this.activityBarService.getPinnedViewletIds(); + const items: Array = this.viewletService.getViewlets() + .filter(viewlet => { + if (viewlet.id === this.viewDescriptorService.getViewContainer(focusedViewId)!.id) { + return false; + } + + return !viewContainerRegistry.get(viewlet.id)!.rejectAddedViews && pinnedViewlets.indexOf(viewlet.id) !== -1; + }) + .map(viewlet => { + return { + id: viewlet.id, + label: viewlet.name, + }; + }); + + if (this.viewDescriptorService.getViewLocation(focusedViewId) !== ViewContainerLocation.Panel) { + items.unshift({ + type: 'separator', + label: nls.localize('sidebar', "Side Bar") + }); + items.push({ + type: 'separator', label: nls.localize('panel', "Panel") - } - ]; + }); + items.push({ + id: '_.panel.newcontainer', + label: nls.localize('moveFocusedView.newContainerInPanel', "New Container in Panel"), + }); + } + + quickPick.items = items; quickPick.onDidAccept(() => { const destination = quickPick.selectedItems[0]; - if (destination.id === 'panel') { - quickPick.hide(); + if (destination.id === '_.panel.newcontainer') { this.viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Panel); - this.viewsService.openView(focusedView, true); - - return; - } else if (destination.id === 'sidebar') { - quickPick.placeholder = nls.localize('moveFocusedView.selectDestinationContainer', "Select a destination view group..."); - quickPick.items = this.viewletService.getViewlets().map(viewlet => { - return { - id: viewlet.id, - label: viewlet.name - }; - }); - - return; + this.viewsService.openView(focusedViewId, true); } else if (destination.id) { - quickPick.hide(); this.viewDescriptorService.moveViewsToContainer([viewDescriptor], viewContainerRegistry.get(destination.id)!); - this.viewsService.openView(focusedView, true); - return; + this.viewsService.openView(focusedViewId, true); } quickPick.hide(); @@ -605,7 +613,7 @@ export class MoveFocusedViewAction extends Action { } } -registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL), 'View: Move Focused View', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL), 'View: Move Focused View', viewCategory, FocusedViewContext.notEqualsTo('')); // --- Resize View diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 4f63dfb9d7..e02a939722 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -27,6 +27,10 @@ margin-bottom: auto; } +.monaco-workbench .activitybar > .content > .composite-bar-excess { + height: 100%; +} + .monaco-workbench .activitybar .menubar { width: 100%; height: 35px; diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 86aefd03c8..93b997a453 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -17,13 +17,14 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Widget } from 'vs/base/browser/ui/widget'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; -import { ITheme } from 'vs/platform/theme/common/themeService'; +import { LocalSelectionTransfer, DragAndDropObserver } from 'vs/workbench/browser/dnd'; +import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { Emitter } from 'vs/base/common/event'; import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Registry } from 'vs/platform/registry/common/platform'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd'; +import { IPaneComposite } from 'vs/workbench/common/panecomposite'; export interface ICompositeBarItem { id: string; @@ -38,7 +39,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { constructor( private viewDescriptorService: IViewDescriptorService, private targetContainerLocation: ViewContainerLocation, - private openComposite: (id: string, focus?: boolean) => void, + private openComposite: (id: string, focus?: boolean) => Promise, private moveComposite: (from: string, to: string) => void, private getVisibleCompositeIds: () => string[] ) { } @@ -52,9 +53,14 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { if (targetCompositeId) { if (currentLocation !== this.targetContainerLocation && this.targetContainerLocation !== ViewContainerLocation.Panel) { const destinationContainer = viewContainerRegistry.get(targetCompositeId); - if (destinationContainer) { - this.viewDescriptorService.moveViewsToContainer(this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView), destinationContainer); - this.openComposite(targetCompositeId, true); + if (destinationContainer && !destinationContainer.rejectAddedViews) { + const viewsToMove = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView); + this.viewDescriptorService.moveViewsToContainer(viewsToMove, destinationContainer); + this.openComposite(targetCompositeId, true).then(composite => { + if (composite && viewsToMove.length === 1) { + composite.openView(viewsToMove[0].id, true); + } + }); } } else { this.moveComposite(dragData.id, targetCompositeId); @@ -73,10 +79,14 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { if (viewDescriptor && viewDescriptor.canMoveView) { if (targetCompositeId) { const destinationContainer = viewContainerRegistry.get(targetCompositeId); - if (destinationContainer) { + if (destinationContainer && !destinationContainer.rejectAddedViews) { if (this.targetContainerLocation === ViewContainerLocation.Sidebar) { this.viewDescriptorService.moveViewsToContainer([viewDescriptor], destinationContainer); - this.openComposite(targetCompositeId, true); + this.openComposite(targetCompositeId, true).then(composite => { + if (composite) { + composite.openView(viewDescriptor.id, true); + } + }); } else { this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation); this.moveComposite(this.viewDescriptorService.getViewContainer(viewDescriptor.id)!.id, targetCompositeId); @@ -91,13 +101,25 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { this.moveComposite(newCompositeId, targetId); } - this.openComposite(newCompositeId, true); + this.openComposite(newCompositeId, true).then(composite => { + if (composite) { + composite.openView(viewDescriptor.id, true); + } + }); } } } } + onDragEnter(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): boolean { + return this.canDrop(data, targetCompositeId); + } + onDragOver(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): boolean { + return this.canDrop(data, targetCompositeId); + } + + private canDrop(data: CompositeDragAndDropData, targetCompositeId: string | undefined): boolean { const dragData = data.getData(); const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); @@ -134,6 +156,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { if (this.targetContainerLocation === ViewContainerLocation.Sidebar) { const destinationContainer = viewContainerRegistry.get(targetCompositeId); return !!destinationContainer && + !destinationContainer.rejectAddedViews && this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.some(vd => vd.canMoveView); } // ... from sidebar to the panel @@ -155,10 +178,9 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } // ... into a destination - return true; + const destinationContainer = viewContainerRegistry.get(targetCompositeId); + return !!destinationContainer && !destinationContainer.rejectAddedViews; } - - return false; } } @@ -200,6 +222,7 @@ export class CompositeBar extends Widget implements ICompositeBar { constructor( items: ICompositeBarItem[], private options: ICompositeBarOptions, + @IThemeService private readonly themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { @@ -228,6 +251,7 @@ export class CompositeBar extends Widget implements ICompositeBar { create(parent: HTMLElement): HTMLElement { const actionBarDiv = parent.appendChild($('.composite-bar')); + const excessDiv = parent.appendChild($('.composite-bar-excess')); this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, { actionViewItemProvider: (action: IAction) => { @@ -254,58 +278,99 @@ export class CompositeBar extends Widget implements ICompositeBar { this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e))); // Allow to drop at the end to move composites to the end - this._register(addDisposableListener(parent, EventType.DROP, (e: DragEvent) => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - EventHelper.stop(e, true); + this._register(new DragAndDropObserver(excessDiv, { + onDragOver: (e: DragEvent) => { + if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { + EventHelper.stop(e, true); - const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); - if (Array.isArray(data)) { - const draggedCompositeId = data[0].id; - this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); + const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); + if (Array.isArray(data)) { + const draggedCompositeId = data[0].id; - this.options.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e); - } - } - - if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { - const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); - if (Array.isArray(data)) { - const draggedViewId = data[0].id; - this.compositeTransfer.clearData(DraggedViewIdentifier.prototype); - - this.options.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), undefined, e); - } - } - })); - - this._register(addDisposableListener(parent, EventType.DRAG_OVER, (e: DragEvent) => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - EventHelper.stop(e, true); - - const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); - if (Array.isArray(data)) { - const draggedCompositeId = data[0].id; - - // Check if drop is allowed - if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e)) { - e.dataTransfer.dropEffect = 'none'; + // Check if drop is allowed + if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e)) { + e.dataTransfer.dropEffect = 'none'; + } } } - } - if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { - EventHelper.stop(e, true); + if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { + EventHelper.stop(e, true); - const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); - if (Array.isArray(data)) { - const draggedViewId = data[0].id; + const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); + if (Array.isArray(data)) { + const draggedViewId = data[0].id; - // Check if drop is allowed - if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), undefined, e)) { - e.dataTransfer.dropEffect = 'none'; + // Check if drop is allowed + if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), undefined, e)) { + e.dataTransfer.dropEffect = 'none'; + } } } - } + }, + + onDragEnter: (e: DragEvent) => { + if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { + EventHelper.stop(e, true); + + const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); + if (Array.isArray(data)) { + const draggedCompositeId = data[0].id; + + // Check if drop is allowed + const validDropTarget = this.options.dndHandler.onDragEnter(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e); + this.updateFromDragging(excessDiv, validDropTarget); + } + } + + if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { + EventHelper.stop(e, true); + + const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); + if (Array.isArray(data)) { + const draggedViewId = data[0].id; + + // Check if drop is allowed + const validDropTarget = this.options.dndHandler.onDragEnter(new CompositeDragAndDropData('view', draggedViewId), undefined, e); + this.updateFromDragging(excessDiv, validDropTarget); + } + } + }, + + onDragLeave: (e: DragEvent) => { + if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) || + this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { + this.updateFromDragging(excessDiv, false); + } + }, + onDragEnd: (e: DragEvent) => { + // no-op, will not be called + }, + onDrop: (e: DragEvent) => { + if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { + EventHelper.stop(e, true); + + const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); + if (Array.isArray(data)) { + const draggedCompositeId = data[0].id; + this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); + + this.options.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e); + this.updateFromDragging(excessDiv, false); + } + } + + if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { + const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); + if (Array.isArray(data)) { + const draggedViewId = data[0].id; + this.compositeTransfer.clearData(DraggedViewIdentifier.prototype); + + this.options.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), undefined, e); + this.updateFromDragging(excessDiv, false); + } + } + }, })); return actionBarDiv; @@ -410,6 +475,13 @@ export class CompositeBar extends Widget implements ICompositeBar { } } + private updateFromDragging(element: HTMLElement, isDragging: boolean): void { + const theme = this.themeService.getTheme(); + const dragBackground = this.options.colors(theme).dragAndDropBackground; + + element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : ''; + } + private resetActiveComposite(compositeId: string) { const defaultCompositeId = this.options.getDefaultCompositeId(); diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 4ae868f5bb..29882993da 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -544,14 +544,16 @@ export class CompositeActionViewItem extends ActivityActionViewItem { if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); if (Array.isArray(data) && data[0].id !== this.activity.id) { - this.updateFromDragging(container, true); + const validDropTarget = this.dndHandler.onDragEnter(new CompositeDragAndDropData('composite', data[0].id), this.activity.id, e); + this.updateFromDragging(container, validDropTarget); } } if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); if (Array.isArray(data) && data[0].id !== this.activity.id) { - this.updateFromDragging(container, true); + const validDropTarget = this.dndHandler.onDragEnter(new CompositeDragAndDropData('view', data[0].id), this.activity.id, e); + this.updateFromDragging(container, validDropTarget); } } }, @@ -616,6 +618,8 @@ export class CompositeActionViewItem extends ActivityActionViewItem { const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); if (Array.isArray(data)) { const draggedViewId = data[0].id; + this.updateFromDragging(container, false); + this.compositeTransfer.clearData(DraggedViewIdentifier.prototype); this.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e); } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 0e70ae4c85..50f238cc9d 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -147,6 +147,7 @@ class StateChange { encoding: boolean = false; EOL: boolean = false; tabFocusMode: boolean = false; + columnSelectionMode: boolean = false; screenReaderMode: boolean = false; metadata: boolean = false; @@ -157,6 +158,7 @@ class StateChange { this.encoding = this.encoding || other.encoding; this.EOL = this.EOL || other.EOL; this.tabFocusMode = this.tabFocusMode || other.tabFocusMode; + this.columnSelectionMode = this.columnSelectionMode || other.columnSelectionMode; this.screenReaderMode = this.screenReaderMode || other.screenReaderMode; this.metadata = this.metadata || other.metadata; } @@ -168,21 +170,23 @@ class StateChange { || this.encoding || this.EOL || this.tabFocusMode + || this.columnSelectionMode || this.screenReaderMode || this.metadata; } } -interface StateDelta { - selectionStatus?: string; - mode?: string; - encoding?: string; - EOL?: string; - indentation?: string; - tabFocusMode?: boolean; - screenReaderMode?: boolean; - metadata?: string | undefined; -} +type StateDelta = ( + { type: 'selectionStatus'; selectionStatus: string | undefined; } + | { type: 'mode'; mode: string | undefined; } + | { type: 'encoding'; encoding: string | undefined; } + | { type: 'EOL'; EOL: string | undefined; } + | { type: 'indentation'; indentation: string | undefined; } + | { type: 'tabFocusMode'; tabFocusMode: boolean; } + | { type: 'columnSelectionMode'; columnSelectionMode: boolean; } + | { type: 'screenReaderMode'; screenReaderMode: boolean; } + | { type: 'metadata'; metadata: string | undefined; } +); class State { @@ -204,6 +208,9 @@ class State { private _tabFocusMode: boolean | undefined; get tabFocusMode(): boolean | undefined { return this._tabFocusMode; } + private _columnSelectionMode: boolean | undefined; + get columnSelectionMode(): boolean | undefined { return this._columnSelectionMode; } + private _screenReaderMode: boolean | undefined; get screenReaderMode(): boolean | undefined { return this._screenReaderMode; } @@ -213,56 +220,63 @@ class State { update(update: StateDelta): StateChange { const change = new StateChange(); - if ('selectionStatus' in update) { + if (update.type === 'selectionStatus') { if (this._selectionStatus !== update.selectionStatus) { this._selectionStatus = update.selectionStatus; change.selectionStatus = true; } } - if ('indentation' in update) { + if (update.type === 'indentation') { if (this._indentation !== update.indentation) { this._indentation = update.indentation; change.indentation = true; } } - if ('mode' in update) { + if (update.type === 'mode') { if (this._mode !== update.mode) { this._mode = update.mode; change.mode = true; } } - if ('encoding' in update) { + if (update.type === 'encoding') { if (this._encoding !== update.encoding) { this._encoding = update.encoding; change.encoding = true; } } - if ('EOL' in update) { + if (update.type === 'EOL') { if (this._EOL !== update.EOL) { this._EOL = update.EOL; change.EOL = true; } } - if ('tabFocusMode' in update) { + if (update.type === 'tabFocusMode') { if (this._tabFocusMode !== update.tabFocusMode) { this._tabFocusMode = update.tabFocusMode; change.tabFocusMode = true; } } - if ('screenReaderMode' in update) { + if (update.type === 'columnSelectionMode') { + if (this._columnSelectionMode !== update.columnSelectionMode) { + this._columnSelectionMode = update.columnSelectionMode; + change.columnSelectionMode = true; + } + } + + if (update.type === 'screenReaderMode') { if (this._screenReaderMode !== update.screenReaderMode) { this._screenReaderMode = update.screenReaderMode; change.screenReaderMode = true; } } - if ('metadata' in update) { + if (update.type === 'metadata') { if (this._metadata !== update.metadata) { this._metadata = update.metadata; change.metadata = true; @@ -282,6 +296,7 @@ const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF"); export class EditorStatus extends Disposable implements IWorkbenchContribution { private readonly tabFocusModeElement = this._register(new MutableDisposable()); + private readonly columnSelectionModeElement = this._register(new MutableDisposable()); private readonly screenRedearModeElement = this._register(new MutableDisposable()); private readonly indentationElement = this._register(new MutableDisposable()); private readonly selectionElement = this._register(new MutableDisposable()); @@ -402,6 +417,22 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } } + private updateColumnSelectionModeElement(visible: boolean): void { + if (visible) { + if (!this.columnSelectionModeElement.value) { + this.columnSelectionModeElement.value = this.statusbarService.addEntry({ + text: nls.localize('columnSelectionModeEnabled', "Column Selection"), + tooltip: nls.localize('disableColumnSelectionMode', "Disable Column Selection Mode"), + command: 'editor.action.toggleColumnSelection', + backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), + color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND) + }, 'status.editor.columnSelectionMode', nls.localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8); + } + } else { + this.columnSelectionModeElement.clear(); + } + } + private updateScreenReaderModeElement(visible: boolean): void { if (visible) { if (!this.screenRedearModeElement.value) { @@ -544,6 +575,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private doRenderNow(changed: StateChange): void { this.updateTabFocusModeElement(!!this.state.tabFocusMode); + this.updateColumnSelectionModeElement(!!this.state.columnSelectionMode); this.updateScreenReaderModeElement(!!this.state.screenReaderMode); this.updateIndentationElement(this.state.indentation); this.updateSelectionElement(this.state.selectionStatus && !this.state.screenReaderMode ? this.state.selectionStatus : undefined); @@ -583,6 +615,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const activeCodeEditor = activeControl ? withNullAsUndefined(getCodeEditor(activeControl.getControl())) : undefined; // Update all states + this.onColumnSelectionModeChange(activeCodeEditor); this.onScreenReaderModeChange(activeCodeEditor); this.onSelectionChange(activeCodeEditor); this.onModeChange(activeCodeEditor, activeInput); @@ -600,6 +633,9 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { // Hook Listener for Configuration changes this.activeEditorListeners.add(activeCodeEditor.onDidChangeConfiguration((event: ConfigurationChangedEvent) => { + if (event.hasChanged(EditorOption.columnSelection)) { + this.onColumnSelectionModeChange(activeCodeEditor); + } if (event.hasChanged(EditorOption.accessibilitySupport)) { this.onScreenReaderModeChange(activeCodeEditor); } @@ -668,14 +704,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private onModeChange(editorWidget: ICodeEditor | undefined, editorInput: IEditorInput | undefined): void { - let info: StateDelta = { mode: undefined }; + let info: StateDelta = { type: 'mode', mode: undefined }; // We only support text based editors if (editorWidget && editorInput && toEditorWithModeSupport(editorInput)) { const textModel = editorWidget.getModel(); if (textModel) { const modeId = textModel.getLanguageIdentifier().language; - info = { mode: withNullAsUndefined(this.modeService.getLanguageName(modeId)) }; + info.mode = withNullAsUndefined(this.modeService.getLanguageName(modeId)); } } @@ -683,7 +719,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private onIndentationChange(editorWidget: ICodeEditor | undefined): void { - const update: StateDelta = { indentation: undefined }; + const update: StateDelta = { type: 'indentation', indentation: undefined }; if (editorWidget) { const model = editorWidget.getModel(); @@ -701,7 +737,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private onMetadataChange(editor: IBaseEditor | undefined): void { - const update: StateDelta = { metadata: undefined }; + const update: StateDelta = { type: 'metadata', metadata: undefined }; if (editor instanceof BaseBinaryResourceEditor || editor instanceof BinaryResourceDiffEditor) { update.metadata = editor.getMetadata(); @@ -710,6 +746,16 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.updateState(update); } + private onColumnSelectionModeChange(editorWidget: ICodeEditor | undefined): void { + const info: StateDelta = { type: 'columnSelectionMode', columnSelectionMode: false }; + + if (editorWidget && editorWidget.getOption(EditorOption.columnSelection)) { + info.columnSelectionMode = true; + } + + this.updateState(info); + } + private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void { let screenReaderMode = false; @@ -733,7 +779,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.screenReaderNotification.close(); } - this.updateState({ screenReaderMode: screenReaderMode }); + this.updateState({ type: 'screenReaderMode', screenReaderMode: screenReaderMode }); } private onSelectionChange(editorWidget: ICodeEditor | undefined): void { @@ -773,11 +819,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } } - this.updateState({ selectionStatus: this.getSelectionLabel(info) }); + this.updateState({ type: 'selectionStatus', selectionStatus: this.getSelectionLabel(info) }); } private onEOLChange(editorWidget: ICodeEditor | undefined): void { - const info: StateDelta = { EOL: undefined }; + const info: StateDelta = { type: 'EOL', EOL: undefined }; if (editorWidget && !editorWidget.getOption(EditorOption.readOnly)) { const codeEditorModel = editorWidget.getModel(); @@ -794,7 +840,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { return; } - const info: StateDelta = { encoding: undefined }; + const info: StateDelta = { type: 'encoding', encoding: undefined }; // We only support text based editors that have a model associated // This ensures we do not show the encoding picker while an editor @@ -828,7 +874,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private onTabFocusModeChange(): void { - const info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() }; + const info: StateDelta = { type: 'tabFocusMode', tabFocusMode: TabFocus.getTabFocusMode() }; this.updateState(info); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index ebb37ad5f2..81ceb61825 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -26,10 +26,8 @@ export class ClearNotificationAction extends Action { super(id, label, 'codicon-close'); } - run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(CLEAR_NOTIFICATION, notification); - - return Promise.resolve(); } } @@ -46,10 +44,8 @@ export class ClearAllNotificationsAction extends Action { super(id, label, 'codicon-clear-all'); } - run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(CLEAR_ALL_NOTIFICATIONS); - - return Promise.resolve(); } } @@ -63,13 +59,11 @@ export class HideNotificationsCenterAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'codicon-close'); + super(id, label, 'codicon-chevron-down'); } - run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(HIDE_NOTIFICATIONS_CENTER); - - return Promise.resolve(); } } @@ -86,10 +80,8 @@ export class ExpandNotificationAction extends Action { super(id, label, 'codicon-chevron-up'); } - run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(EXPAND_NOTIFICATION, notification); - - return Promise.resolve(); } } @@ -106,10 +98,8 @@ export class CollapseNotificationAction extends Action { super(id, label, 'codicon-chevron-down'); } - run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(COLLAPSE_NOTIFICATION, notification); - - return Promise.resolve(); } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index d5216e5f98..401abae921 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -100,6 +100,9 @@ export class NotificationsCenter extends Themable implements INotificationsCente // Theming this.updateStyles(); + // Mark as visible + this.model.notifications.forEach(notification => notification.updateVisibility(true)); + // Context Key this.notificationsCenterVisibleContextKey.set(true); @@ -115,7 +118,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente clearAllAction.enabled = false; } else { notificationsCenterTitle.textContent = localize('notifications', "Notifications"); - clearAllAction.enabled = true; + clearAllAction.enabled = this.model.notifications.some(notification => !notification.hasProgress); } } @@ -172,20 +175,22 @@ export class NotificationsCenter extends Themable implements INotificationsCente return; // only if visible } - let focusGroup = false; + let focusEditor = false; // Update notifications list based on event const [notificationsList, notificationsCenterContainer] = assertAllDefined(this.notificationsList, this.notificationsCenterContainer); switch (e.kind) { case NotificationChangeType.ADD: notificationsList.updateNotificationsList(e.index, 0, [e.item]); + e.item.updateVisibility(true); break; case NotificationChangeType.CHANGE: notificationsList.updateNotificationsList(e.index, 1, [e.item]); break; case NotificationChangeType.REMOVE: - focusGroup = isAncestor(document.activeElement, notificationsCenterContainer); + focusEditor = isAncestor(document.activeElement, notificationsCenterContainer); notificationsList.updateNotificationsList(e.index, 1); + e.item.updateVisibility(false); break; } @@ -197,7 +202,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente this.hide(); // Restore focus to editor group if we had focus - if (focusGroup) { + if (focusEditor) { this.editorGroupService.activeGroup.focus(); } } @@ -208,13 +213,16 @@ export class NotificationsCenter extends Themable implements INotificationsCente return; // already hidden } - const focusGroup = isAncestor(document.activeElement, this.notificationsCenterContainer); + const focusEditor = isAncestor(document.activeElement, this.notificationsCenterContainer); // Hide this._isVisible = false; removeClass(this.notificationsCenterContainer, 'visible'); this.notificationsList.hide(); + // Mark as hidden + this.model.notifications.forEach(notification => notification.updateVisibility(false)); + // Context Key this.notificationsCenterVisibleContextKey.set(false); @@ -222,7 +230,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente this._onDidChangeVisibility.fire(); // Restore focus to editor group if we had focus - if (focusGroup) { + if (focusEditor) { this.editorGroupService.activeGroup.focus(); } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index b80dceec9d..0fe24649fb 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -75,6 +75,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl // Show Notifications Cneter CommandsRegistry.registerCommand(SHOW_NOTIFICATIONS_CENTER, () => { + toasts.hide(); center.show(); }); @@ -92,6 +93,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl if (center.isVisible) { center.hide(); } else { + toasts.hide(); center.show(); } }); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index e0894579a1..b0a346c3a2 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -46,7 +46,7 @@ export class NotificationsStatus extends Disposable { if (!this.isNotificationsCenterVisible) { if (e.kind === NotificationChangeType.ADD) { this.newNotificationsCount++; - } else if (e.kind === NotificationChangeType.REMOVE) { + } else if (e.kind === NotificationChangeType.REMOVE && this.newNotificationsCount > 0) { this.newNotificationsCount--; } } @@ -69,8 +69,9 @@ export class NotificationsStatus extends Disposable { } } + // Show the bell with a dot if there are unread or in-progress notifications const statusProperties: IStatusbarEntry = { - text: `${this.newNotificationsCount === 0 ? '$(bell)' : '$(bell-dot)'}${notificationsInProgress > 0 ? ' $(sync~spin)' : ''}`, + text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-dot)' : '$(bell)'}`, command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER, tooltip: this.getTooltip(notificationsInProgress), showBeak: this.isNotificationsCenterVisible diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index c824d17a22..44e9ec9466 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -179,11 +179,8 @@ export class NotificationsToasts extends Themable implements INotificationsToast const toast: INotificationToast = { item, list: notificationList, container: notificationToastContainer, toast: notificationToast, toDispose: itemDisposables }; this.mapNotificationToToast.set(item, toast); - itemDisposables.add(toDisposable(() => { - if (this.isToastVisible(toast) && notificationsToastsContainer) { - notificationsToastsContainer.removeChild(toast.container); - } - })); + // When disposed, remove as visible + itemDisposables.add(toDisposable(() => this.updateToastVisibility(toast, false))); // Make visible notificationList.show(); @@ -236,6 +233,9 @@ export class NotificationsToasts extends Themable implements INotificationsToast addClass(notificationToast, 'notification-fade-in-done'); })); + // Mark as visible + item.updateVisibility(true); + // Events if (!this._isVisible) { this._isVisible = true; @@ -292,12 +292,13 @@ export class NotificationsToasts extends Themable implements INotificationsToast } private removeToast(item: INotificationViewItem): void { + let focusEditor = false; + const notificationToast = this.mapNotificationToToast.get(item); - let focusGroup = false; if (notificationToast) { const toastHasDOMFocus = isAncestor(document.activeElement, notificationToast.container); if (toastHasDOMFocus) { - focusGroup = !(this.focusNext() || this.focusPrevious()); // focus next if any, otherwise focus editor + focusEditor = !(this.focusNext() || this.focusPrevious()); // focus next if any, otherwise focus editor } // Listeners @@ -317,7 +318,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast this.doHide(); // Move focus back to editor group as needed - if (focusGroup) { + if (focusEditor) { this.editorGroupService.activeGroup.focus(); } } @@ -346,11 +347,11 @@ export class NotificationsToasts extends Themable implements INotificationsToast } hide(): void { - const focusGroup = this.notificationsToastsContainer ? isAncestor(document.activeElement, this.notificationsToastsContainer) : false; + const focusEditor = this.notificationsToastsContainer ? isAncestor(document.activeElement, this.notificationsToastsContainer) : false; this.removeToasts(); - if (focusGroup) { + if (focusEditor) { this.editorGroupService.activeGroup.focus(); } } @@ -459,12 +460,12 @@ export class NotificationsToasts extends Themable implements INotificationsToast notificationToasts.push(toast); break; case ToastVisibility.HIDDEN: - if (!this.isToastVisible(toast)) { + if (!this.isToastInDOM(toast)) { notificationToasts.push(toast); } break; case ToastVisibility.VISIBLE: - if (this.isToastVisible(toast)) { + if (this.isToastInDOM(toast)) { notificationToasts.push(toast); } break; @@ -530,7 +531,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast // In order to measure the client height, the element cannot have display: none toast.container.style.opacity = '0'; - this.setVisibility(toast, true); + this.updateToastVisibility(toast, true); heightToGive -= toast.container.offsetHeight; @@ -542,7 +543,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast } // Hide or show toast based on context - this.setVisibility(toast, makeVisible); + this.updateToastVisibility(toast, makeVisible); toast.container.style.opacity = ''; if (makeVisible) { @@ -551,20 +552,24 @@ export class NotificationsToasts extends Themable implements INotificationsToast }); } - private setVisibility(toast: INotificationToast, visible: boolean): void { - if (this.isToastVisible(toast) === visible) { + private updateToastVisibility(toast: INotificationToast, visible: boolean): void { + if (this.isToastInDOM(toast) === visible) { return; } + // Update visibility in DOM const notificationsToastsContainer = assertIsDefined(this.notificationsToastsContainer); if (visible) { notificationsToastsContainer.appendChild(toast.container); } else { notificationsToastsContainer.removeChild(toast.container); } + + // Update visibility in model + toast.item.updateVisibility(visible); } - private isToastVisible(toast: INotificationToast): boolean { + private isToastInDOM(toast: INotificationToast): boolean { return !!toast.container.parentElement; } } diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 4eacec1347..13d51e8ed8 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -62,6 +62,10 @@ } +.monaco-workbench .part.panel > .composite.title > .composite-bar-excess { + width: 100%; +} + .monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar { line-height: 27px; /* matches panel titles in settings */ height: 35px; diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 357b56d957..895010fd42 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -36,6 +36,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection, ViewContainerLocation } from 'vs/workbench/common/views'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; +import { IPaneComposite } from 'vs/workbench/common/panecomposite'; interface ICachedPanel { id: string; @@ -143,7 +144,7 @@ export class PanelPart extends CompositePart implements IPanelService { getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(), hidePart: () => this.layoutService.setPanelHidden(true), dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel, - (id: string, focus?: boolean) => this.openPanel(id, focus), + (id: string, focus?: boolean) => (this.openPanel(id, focus)) as Promise, // {{SQL CARBON EDIT}} strict-null-check (from: string, to: string) => this.compositeBar.move(from, to), () => this.getPinnedPanels().map(p => p.id) ), diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index a533f8af2d..1bf4c43b93 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -255,7 +255,10 @@ export abstract class ViewPane extends Pane implements IView { this._onDidFocus.fire(); })); this._register(focusTracker.onDidBlur(() => { - this.focusedViewContextKey.reset(); + if (this.focusedViewContextKey.get() === this.id) { + this.focusedViewContextKey.reset(); + } + this._onDidBlur.fire(); })); } @@ -1006,7 +1009,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } if (!this.areExtensionsReady) { if (this.visibleViewsCountFromCache === undefined) { - return true; + // TODO @sbatten fix hack for #91367 + return this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel; } // Check in cache so that view do not jump. See #29609 return this.visibleViewsCountFromCache === 1; diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 596c57e207..c22007a54e 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter, INotificationProgressProperties } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -92,6 +92,9 @@ export class NotificationHandle extends Disposable implements INotificationHandl private readonly _onDidClose = this._register(new Emitter()); readonly onDidClose = this._onDidClose.event; + private readonly _onDidChangeVisibility = this._register(new Emitter()); + readonly onDidChangeVisibility = this._onDidChangeVisibility.event; + constructor(private readonly item: INotificationViewItem, private readonly onClose: (item: INotificationViewItem) => void) { super(); @@ -99,6 +102,11 @@ export class NotificationHandle extends Disposable implements INotificationHandl } private registerListeners(): void { + + // Visibility + this._register(this.item.onDidChangeVisibility(visible => this._onDidChangeVisibility.fire(visible))); + + // Closing Event.once(this.item.onDidClose)(() => { this._onDidClose.fire(); @@ -265,6 +273,7 @@ export interface INotificationViewItem { readonly onDidChangeExpansion: Event; readonly onDidClose: Event; + readonly onDidChangeVisibility: Event; readonly onDidChangeLabel: Event; expand(): void; @@ -275,6 +284,8 @@ export interface INotificationViewItem { updateMessage(message: NotificationMessage): void; updateActions(actions?: INotificationActions): void; + updateVisibility(visible: boolean): void; + close(): void; equals(item: INotificationViewItem): boolean; @@ -398,6 +409,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie private static readonly MAX_MESSAGE_LENGTH = 1000; private _expanded: boolean | undefined; + private _visible: boolean = false; private _actions: INotificationActions | undefined; private _progress: NotificationViewItemProgress | undefined; @@ -411,6 +423,9 @@ export class NotificationViewItem extends Disposable implements INotificationVie private readonly _onDidChangeLabel = this._register(new Emitter()); readonly onDidChangeLabel = this._onDidChangeLabel.event; + private readonly _onDidChangeVisibility = this._register(new Emitter()); + readonly onDidChangeVisibility = this._onDidChangeVisibility.event; + static create(notification: INotification, filter: NotificationsFilter = NotificationsFilter.OFF): INotificationViewItem | undefined { if (!notification || !notification.message || isPromiseCanceledError(notification.message)) { return undefined; // we need a message to show @@ -435,7 +450,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie actions = { primary: notification.message.actions }; } - return new NotificationViewItem(severity, notification.sticky, notification.silent || filter === NotificationsFilter.SILENT || (filter === NotificationsFilter.ERROR && notification.severity !== Severity.Error), message, notification.source, actions); + return new NotificationViewItem(severity, notification.sticky, notification.silent || filter === NotificationsFilter.SILENT || (filter === NotificationsFilter.ERROR && notification.severity !== Severity.Error), message, notification.source, notification.progress, actions); } private static parseNotificationMessage(input: NotificationMessage): INotificationMessage | undefined { @@ -472,13 +487,30 @@ export class NotificationViewItem extends Disposable implements INotificationVie private _silent: boolean | undefined, private _message: INotificationMessage, private _source: string | undefined, + progress: INotificationProgressProperties | undefined, actions?: INotificationActions ) { super(); + if (progress) { + this.setProgress(progress); + } + this.setActions(actions); } + private setProgress(progress: INotificationProgressProperties): void { + if (progress.infinite) { + this.progress.infinite(); + } else if (progress.total) { + this.progress.total(progress.total); + + if (progress.worked) { + this.progress.worked(progress.worked); + } + } + } + private setActions(actions: INotificationActions = { primary: [], secondary: [] }): void { if (!Array.isArray(actions.primary)) { actions.primary = []; @@ -583,6 +615,14 @@ export class NotificationViewItem extends Disposable implements INotificationVie this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.ACTIONS }); } + updateVisibility(visible: boolean): void { + if (this._visible !== visible) { + this._visible = visible; + + this._onDidChangeVisibility.fire(visible); + } + } + expand(): void { if (this._expanded || !this.canCollapse) { return; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 912f55f313..4a20b0dd59 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -17,7 +17,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IAction, IActionViewItem } from 'vs/base/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { flatten } from 'vs/base/common/arrays'; +import { flatten, mergeSort } from 'vs/base/common/arrays'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SetMap } from 'vs/base/common/collections'; @@ -53,6 +53,7 @@ export interface IViewContainerDescriptor { readonly extensionId?: ExtensionIdentifier; + readonly rejectAddedViews?: boolean; } export interface IViewContainersRegistry { @@ -211,9 +212,16 @@ export interface IViewDescriptorCollection extends IDisposable { readonly allViewDescriptors: IViewDescriptor[]; } +export enum ViewContentPriority { + Normal = 0, + Low = 1, + Lowest = 2 +} + export interface IViewContentDescriptor { readonly content: string; readonly when?: ContextKeyExpr | 'default'; + readonly priority?: ViewContentPriority; /** * ordered preconditions for each button in the content @@ -247,6 +255,13 @@ export interface IViewsRegistry { } function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewContentDescriptor): number { + const aPriority = a.priority ?? ViewContentPriority.Normal; + const bPriority = b.priority ?? ViewContentPriority.Normal; + + if (aPriority !== bPriority) { + return aPriority - bPriority; + } + return a.content < b.content ? -1 : 1; } @@ -328,8 +343,8 @@ class ViewsRegistry extends Disposable implements IViewsRegistry { getViewWelcomeContent(id: string): IViewContentDescriptor[] { const result: IViewContentDescriptor[] = []; - result.sort(compareViewContentDescriptors); this._viewWelcomeContents.forEach(id, descriptor => result.push(descriptor)); + mergeSort(result, compareViewContentDescriptors); return result; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 070f7ef05c..24d5203cc0 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -111,6 +111,7 @@ class BulkEditPreviewContribution { private async _previewEdit(edit: WorkspaceEdit) { this._ctxEnabled.set(true); + const uxState = this._activeSession?.uxState ?? new UXState(this._panelService, this._editorGroupsService); const view = await getBulkEditPane(this._viewsService); if (!view) { this._ctxEnabled.set(false); @@ -136,9 +137,9 @@ class BulkEditPreviewContribution { let session: PreviewSession; if (this._activeSession) { this._activeSession.cts.dispose(true); - session = new PreviewSession(this._activeSession.uxState); + session = new PreviewSession(uxState); } else { - session = new PreviewSession(new UXState(this._panelService, this._editorGroupsService)); + session = new PreviewSession(uxState); } this._activeSession = session; diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts index 0af7824e8c..0155838e65 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts @@ -10,6 +10,13 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands'; +import { Position } from 'vs/editor/common/core/position'; +import { Selection } from 'vs/editor/common/core/selection'; +import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; export class ToggleColumnSelectionAction extends Action { public static readonly ID = 'editor.action.toggleColumnSelection'; @@ -18,26 +25,68 @@ export class ToggleColumnSelectionAction extends Action { constructor( id: string, label: string, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService ) { super(id, label); } - public run(): Promise { - const newValue = !this._configurationService.getValue('editor.columnSelection'); - return this._configurationService.updateValue('editor.columnSelection', newValue, ConfigurationTarget.USER); + private _getCodeEditor(): ICodeEditor | null { + const codeEditor = this._codeEditorService.getFocusedCodeEditor(); + if (codeEditor) { + return codeEditor; + } + return this._codeEditorService.getActiveCodeEditor(); + } + + public async run(): Promise { + const oldValue = this._configurationService.getValue('editor.columnSelection'); + const codeEditor = this._getCodeEditor(); + await this._configurationService.updateValue('editor.columnSelection', !oldValue, ConfigurationTarget.USER); + const newValue = this._configurationService.getValue('editor.columnSelection'); + if (!codeEditor || codeEditor !== this._getCodeEditor() || oldValue === newValue || !codeEditor.hasModel()) { + return; + } + const cursors = codeEditor._getCursors(); + if (codeEditor.getOption(EditorOption.columnSelection)) { + const selection = codeEditor.getSelection(); + const modelSelectionStart = new Position(selection.selectionStartLineNumber, selection.selectionStartColumn); + const viewSelectionStart = cursors.context.convertModelPositionToViewPosition(modelSelectionStart); + const modelPosition = new Position(selection.positionLineNumber, selection.positionColumn); + const viewPosition = cursors.context.convertModelPositionToViewPosition(modelPosition); + + CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursors, { + position: modelSelectionStart, + viewPosition: viewSelectionStart + }); + const visibleColumn = CursorColumns.visibleColumnFromColumn2(cursors.context.config, cursors.context.viewModel, viewPosition); + CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(cursors, { + position: modelPosition, + viewPosition: viewPosition, + doColumnSelect: true, + mouseColumn: visibleColumn + 1 + }); + } else { + const columnSelectData = cursors.getColumnSelectData(); + const fromViewColumn = CursorColumns.columnFromVisibleColumn2(cursors.context.config, cursors.context.viewModel, columnSelectData.fromViewLineNumber, columnSelectData.fromViewVisualColumn); + const fromPosition = cursors.context.convertViewPositionToModelPosition(columnSelectData.fromViewLineNumber, fromViewColumn); + const toViewColumn = CursorColumns.columnFromVisibleColumn2(cursors.context.config, cursors.context.viewModel, columnSelectData.toViewLineNumber, columnSelectData.toViewVisualColumn); + const toPosition = cursors.context.convertViewPositionToModelPosition(columnSelectData.toViewLineNumber, toViewColumn); + + codeEditor.setSelection(new Selection(fromPosition.lineNumber, fromPosition.column, toPosition.lineNumber, toPosition.column)); + } } } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleColumnSelectionAction, ToggleColumnSelectionAction.ID, ToggleColumnSelectionAction.LABEL), 'View: Toggle Column Selection Mode', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleColumnSelectionAction, ToggleColumnSelectionAction.ID, ToggleColumnSelectionAction.LABEL), 'Toggle Column Selection Mode'); MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { - group: '3_multi', + group: '4_config', command: { id: ToggleColumnSelectionAction.ID, title: nls.localize({ key: 'miColumnSelection', comment: ['&& denotes a mnemonic'] }, "Column &&Selection Mode"), toggled: ContextKeyExpr.equals('config.editor.columnSelection', true) }, - order: 1.5 + order: 2 }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts index d481811fc0..29ece6914a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts @@ -70,7 +70,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMultiCursorModifierAction, ToggleMultiCursorModifierAction.ID, ToggleMultiCursorModifierAction.LABEL), 'Toggle Multi-Cursor Modifier'); MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { - group: '3_multi', + group: '4_config', command: { id: ToggleMultiCursorModifierAction.ID, title: nls.localize('miMultiCursorAlt', "Switch to Alt+Click for Multi-Cursor") @@ -79,7 +79,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { order: 1 }); MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { - group: '3_multi', + group: '4_config', command: { id: ToggleMultiCursorModifierAction.ID, title: ( diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts index 63194dfe67..e53322b5c2 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts @@ -20,10 +20,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; export class SelectionClipboard extends Disposable implements IEditorContribution { private static readonly SELECTION_LENGTH_LIMIT = 65536; @@ -119,15 +116,7 @@ class PasteSelectionClipboardAction extends EditorAction { id: 'editor.action.selectionClipboardPaste', label: nls.localize('actions.pasteSelectionClipboard', "Paste Selection Clipboard"), alias: 'Paste Selection Clipboard', - precondition: EditorContextKeys.writable, - kbOpts: { - kbExpr: ContextKeyExpr.and( - EditorContextKeys.editorTextFocus, - ContextKeyExpr.has('config.editor.selectionClipboard') - ), - primary: KeyMod.Shift | KeyCode.Insert, - weight: KeybindingWeight.EditorContrib - } + precondition: EditorContextKeys.writable }); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 405a4b2780..65fe1275c3 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -333,11 +333,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } const editorInfo = this._editorInfoStore.get(editor.viewType); - if (!editorInfo) { - continue; - } - - if (!editorInfo.matches(newResource)) { + if (!editorInfo?.matches(newResource)) { continue; } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 5acba2e3ff..0773b4fd12 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -74,7 +74,7 @@ export interface ICustomEditorModel extends IWorkingCopy { readonly onWillSave: Event; readonly onWillSaveAs: Event; - onBackup(f: () => CancelablePromise): void; + onBackup(f: () => CancelablePromise): void; setDirty(dirty: boolean): void; undo(): void; diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts index 520f7c0b00..2d3a2490ca 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts @@ -29,7 +29,7 @@ namespace HotExitState { readonly type = Type.Pending; constructor( - public readonly operation: CancelablePromise, + public readonly operation: CancelablePromise, ) { } } @@ -90,9 +90,9 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel private readonly _onWillSaveAs = this._register(new Emitter()); public readonly onWillSaveAs = this._onWillSaveAs.event; - private _onBackup: undefined | (() => CancelablePromise); + private _onBackup: undefined | (() => CancelablePromise); - public onBackup(f: () => CancelablePromise) { + public onBackup(f: () => CancelablePromise) { if (this._onBackup) { throw new Error('Backup already implemented'); } @@ -182,7 +182,11 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel this._hotExitState = pendingState; try { - this._hotExitState = await pendingState.operation ? HotExitState.Allowed : HotExitState.NotAllowed; + await pendingState.operation; + // Make sure state has not changed in the meantime + if (this._hotExitState === pendingState) { + this._hotExitState = HotExitState.Allowed; + } } catch (e) { // Make sure state has not changed in the meantime if (this._hotExitState === pendingState) { diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 88603f12ac..055f9f4d38 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -267,7 +267,7 @@ configurationRegistry.registerConfiguration({ default: true }, 'debug.onTaskErrors': { - enum: ['debugAnyway', 'showErrors', 'prompt', 'cancel'], + enum: ['debugAnyway', 'showErrors', 'prompt', 'abort'], enumDescriptions: [nls.localize('debugAnyway', "Ignore task errors and start debugging."), nls.localize('showErrors', "Show the Problems view and do not start debugging."), nls.localize('prompt', "Prompt user."), nls.localize('cancel', "Cancel debugging.")], description: nls.localize('debug.onTaskErrors', "Controls what to do when errors are encountered after running a preLaunchTask."), default: 'prompt' diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 9fa3385cde..99e40a963e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -519,18 +519,17 @@ abstract class AbstractLaunch { if (!config || (!Array.isArray(config.configurations) && !Array.isArray(config.compounds))) { return []; } else { - const names: string[] = []; + const configurations: (IConfig | ICompound)[] = []; if (config.configurations) { - names.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name)); + configurations.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string')); } if (includeCompounds && config.compounds) { if (config.compounds) { - names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length) - .map(compound => compound.name)); + configurations.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length)); } } - return names; + return getVisibleAndSorted(configurations).map(c => c.name); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 2f2f0b74cc..935833a87c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -209,7 +209,7 @@ export class DebugHoverWidget implements IContentWidget { if (!matchingExpression) { const lineContent = model.getLineContent(pos.lineNumber); - matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn); + matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn - 1); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index bce0fb1c11..8f05992da2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -19,7 +19,7 @@ import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSess import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, Queue } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; @@ -807,62 +807,58 @@ export class DebugSession implements IDebugSession { this._onDidChangeState.fire(); })); - let outpuPromises: Promise[] = []; + const outputQueue = new Queue(); this.rawListeners.push(this.raw.onDidOutput(async event => { - if (!event.body || !this.raw) { - return; - } - - const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info; - if (event.body.category === 'telemetry') { - // only log telemetry events from debug adapter if the debug extension provided the telemetry key - // and the user opted in telemetry - if (this.raw.customTelemetryService && this.telemetryService.isOptedIn) { - // __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly. - this.raw.customTelemetryService.publicLog(event.body.output, event.body.data); - } - - return; - } - - // Make sure to append output in the correct order by properly waiting on preivous promises #33822 - const waitFor = outpuPromises.slice(); - const source = event.body.source && event.body.line ? { - lineNumber: event.body.line, - column: event.body.column ? event.body.column : 1, - source: this.getSource(event.body.source) - } : undefined; - - if (event.body.group === 'start' || event.body.group === 'startCollapsed') { - const expanded = event.body.group === 'start'; - this.repl.startGroup(event.body.output || '', expanded, source); - return; - } - if (event.body.group === 'end') { - this.repl.endGroup(); - if (!event.body.output) { - // Only return if the end event does not have additional output in it + outputQueue.queue(async () => { + if (!event.body || !this.raw) { return; } - } - if (event.body.variablesReference) { - const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid()); - outpuPromises.push(container.getChildren().then(async children => { - await Promise.all(waitFor); - children.forEach(child => { - // Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names) - (child).name = null; - this.appendToRepl(child, outputSeverity, source); + const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info; + if (event.body.category === 'telemetry') { + // only log telemetry events from debug adapter if the debug extension provided the telemetry key + // and the user opted in telemetry + if (this.raw.customTelemetryService && this.telemetryService.isOptedIn) { + // __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly. + this.raw.customTelemetryService.publicLog(event.body.output, event.body.data); + } + + return; + } + + // Make sure to append output in the correct order by properly waiting on preivous promises #33822 + const source = event.body.source && event.body.line ? { + lineNumber: event.body.line, + column: event.body.column ? event.body.column : 1, + source: this.getSource(event.body.source) + } : undefined; + + if (event.body.group === 'start' || event.body.group === 'startCollapsed') { + const expanded = event.body.group === 'start'; + this.repl.startGroup(event.body.output || '', expanded, source); + return; + } + if (event.body.group === 'end') { + this.repl.endGroup(); + if (!event.body.output) { + // Only return if the end event does not have additional output in it + return; + } + } + + if (event.body.variablesReference) { + const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid()); + await container.getChildren().then(children => { + children.forEach(child => { + // Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names) + (child).name = null; + this.appendToRepl(child, outputSeverity, source); + }); }); - })); - } else if (typeof event.body.output === 'string') { - await Promise.all(waitFor); - this.appendToRepl(event.body.output, outputSeverity, source); - } - - await Promise.all(outpuPromises); - outpuPromises = []; + } else if (typeof event.body.output === 'string') { + this.appendToRepl(event.body.output, outputSeverity, source); + } + }); })); this.rawListeners.push(this.raw.onDidBreakpoint(event => { diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index b3f91b9c5b..bdc14da43d 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -75,35 +75,35 @@ export class StartView extends ViewPane { }; this._register(editorService.onDidActiveEditorChange(setContextKey)); this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(setContextKey)); - this.registerViews(); + setContextKey(); + + const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID); + debugKeybindingLabel = debugKeybinding ? ` (${debugKeybinding.getLabel()})` : ''; } shouldShowWelcome(): boolean { return true; } - - private registerViews(): void { - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), - when: CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.toNegated() - }); - - const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID); - const debugKeybindingLabel = debugKeybinding ? ` (${debugKeybinding.getLabel()})` : ''; - viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('runAndDebugAction', "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID), - preconditions: [CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR] - }); - - viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), - when: WorkbenchStateContext.notEqualsTo('empty') - }); - - viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), - when: WorkbenchStateContext.isEqualTo('empty') - }); - } } + +const viewsRegistry = Registry.as(Extensions.ViewsRegistry); +viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), + when: CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.toNegated() +}); + +let debugKeybindingLabel = ''; +viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('runAndDebugAction', "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID), + preconditions: [CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR] +}); + +viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), + when: WorkbenchStateContext.notEqualsTo('empty') +}); + +viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), + when: WorkbenchStateContext.isEqualTo('empty') +}); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 4175d9f5b0..353693abc6 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -87,7 +87,8 @@ Registry.as(ViewContainerExtensions.ViewContainersRegis name: localize('extensions', "Extensions"), ctorDescriptor: new SyncDescriptor(ExtensionsViewPaneContainer), icon: 'codicon-extensions', - order: 14 // {{SQL CARBON EDIT}} + order: 14, // {{SQL CARBON EDIT}} + rejectAddedViews: true, }, ViewContainerLocation.Sidebar); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index bca05db064..3a1a9a4858 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -2690,7 +2690,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { if (server) { this.tooltip = localize('Install in other server to enable', "Install the extension on '{0}' to enable.", server.label); } else { - this.tooltip = localize('disabled because of extension kind', "This extension cannot be enabled in the remote server."); + this.tooltip = localize('disabled because of extension kind', "This extension has defined that it cannot run on the remote server"); } return; } diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 5832af7777..24c61a0704 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -65,21 +65,21 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor private registerViews(): void { const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID), when: WorkbenchStateContext.isEqualTo('workspace') - }); + })); const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID; - viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:{0})", commandId), when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated()) - }); + })); - viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId), when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)) - }); + })); const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER); diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index 7f6736ae43..06a3588294 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -55,11 +55,11 @@ align-items: center; } -.explorer-viewlet .pane-header .monaco-count-badge.hidden { +.pane-header .dirty-count.monaco-count-badge.hidden { display: none; } -.explorer-viewlet .monaco-count-badge { +.dirty-count.monaco-count-badge { padding: 1px 6px 2px; margin-left: 6px; min-height: auto; diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 1966127c53..50139ef3af 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -185,7 +185,7 @@ export class OpenEditorsView extends ViewPane { super.renderHeaderTitle(container, this.title); const count = dom.append(container, $('.count')); - this.dirtyCountElement = dom.append(count, $('.monaco-count-badge')); + this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge')); this._register((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => { const background = colors.badgeBackground ? colors.badgeBackground.toString() : ''; diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 6a77a2ca7e..0237816fd1 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -856,6 +856,7 @@ export class ResourceDragAndDrop implements ITreeDragAndDrop { registerThemingParticipant((theme, collector) => { const linkFg = theme.getColor(textLinkForeground); if (linkFg) { - collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container a.code-link span:hover { color: ${linkFg}; }`); + collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container a.code-link .marker-code > span:hover { color: ${linkFg}; }`); + collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-list:focus .monaco-tl-contents .details-container a.code-link .marker-code > span:hover { color: ${linkFg.lighten(.4)}; }`); } }); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index b6a8109a97..21c02fabac 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -349,7 +349,6 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private createArialLabelElement(parent: HTMLElement): void { this.ariaLabelElement = dom.append(parent, dom.$('')); this.ariaLabelElement.setAttribute('id', 'markers-panel-arialabel'); - this.ariaLabelElement.setAttribute('aria-live', 'polite'); } private createTree(parent: HTMLElement): void { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index c0d8c1942b..9ec53893ca 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -1238,7 +1238,10 @@ export class SettingTreeRenderers { private getActionsForSetting(setting: ISetting): IAction[] { const enableSync = this._userDataSyncEnablementService.isEnabled(); return enableSync && !setting.disallowSyncIgnore ? - [this._instantiationService.createInstance(SyncSettingAction, setting)] : + [ + new Separator(), + this._instantiationService.createInstance(SyncSettingAction, setting) + ] : []; } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 2008b3a8d2..f0ab82a381 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -44,7 +44,7 @@ export interface ISpliceEvent { export class EmptyPane extends ViewPane { static readonly ID = 'workbench.scm'; - static readonly TITLE = localize('scm providers', "Source Control Providers"); + static readonly TITLE = localize('scm', "Source Control"); constructor( options: IViewPaneOptions, diff --git a/src/vs/workbench/contrib/searchEditor/browser/constants.ts b/src/vs/workbench/contrib/searchEditor/browser/constants.ts index b3e0899d67..da545a328a 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/constants.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/constants.ts @@ -19,5 +19,8 @@ export const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatch export const InSearchEditor = new RawContextKey('inSearchEditor', false); export const SearchEditorScheme = 'search-editor'; +export const SearchEditorBodyScheme = 'search-editor-body'; export const SearchEditorFindMatchClass = 'seaarchEditorFindMatch'; + +export const SearchEditorID = 'workbench.editor.searchEditor'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 6d5b6cac5a..8686ad86b6 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -9,7 +9,7 @@ import { endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; import { localize } from 'vs/nls'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -20,16 +20,16 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry, ActiveEditorContext } from 'vs/workbench/common/editor'; import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand, RerunSearchEditorSearchAction } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; //#region Editor Descriptior Registry.as(EditorExtensions.Editors).registerEditor( @@ -54,10 +54,15 @@ class SearchEditorContribution implements IWorkbenchContribution { ) { this.editorService.overrideOpenEditor((editor, options, group) => { - const resource = editor.resource; - if (!resource || - !(endsWith(resource.path, '.code-search') || resource.scheme === SearchEditorConstants.SearchEditorScheme) || - !(editor instanceof FileEditorInput || (resource.scheme === SearchEditorConstants.SearchEditorScheme))) { + let resource = editor.resource; + if (!resource) { return undefined; } + + if (resource.scheme === SearchEditorConstants.SearchEditorBodyScheme) { + resource = resource.with({ scheme: SearchEditorConstants.SearchEditorScheme }); + } + + if (resource.scheme !== SearchEditorConstants.SearchEditorScheme + && !(endsWith(resource.path, '.code-search') && editor instanceof FileEditorInput)) { return undefined; } @@ -154,15 +159,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: selectAllSearchEditorMatchesCommand }); -CommandsRegistry.registerCommand( - SearchEditorConstants.RerunSearchEditorSearchCommandId, - (accessor: ServicesAccessor) => { - const activeControl = accessor.get(IEditorService).activeControl; - if (activeControl instanceof SearchEditor) { - activeControl.triggerSearch({ resetCursor: false }); - } - }); - CommandsRegistry.registerCommand( SearchEditorConstants.CleanSearchEditorStateCommandId, (accessor: ServicesAccessor) => { @@ -186,4 +182,19 @@ registry.registerWorkbenchAction( registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL), 'Search Editor: Open New Search Editor', category); + +registry.registerWorkbenchAction(SyncActionDescriptor.create(RerunSearchEditorSearchAction, RerunSearchEditorSearchAction.ID, RerunSearchEditorSearchAction.LABEL, + { mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R } }, ContextKeyExpr.and(SearchEditorConstants.InSearchEditor)), + 'Search Editor: Rerun', category); //#endregion + + +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: RerunSearchEditorSearchAction.ID, + title: RerunSearchEditorSearchAction.LABEL, + icon: { id: 'codicon/refresh' }, + }, + group: 'navigation', + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID)) +}); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 1d46ef63c6..ad9e23e4b1 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -35,7 +35,7 @@ import { InputBoxFocusedKey } from 'vs/workbench/contrib/search/common/constants import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; -import { InSearchEditor, SearchEditorFindMatchClass } from 'vs/workbench/contrib/searchEditor/browser/constants'; +import { InSearchEditor, SearchEditorFindMatchClass, SearchEditorID } from 'vs/workbench/contrib/searchEditor/browser/constants'; import type { SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { extractSearchQuery, serializeSearchConfiguration, serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; @@ -56,7 +56,7 @@ const FILE_LINE_REGEX = /^(\S.*):$/; type SearchEditorViewState = ICodeEditorViewState & { focused: 'input' | 'editor' }; export class SearchEditor extends BaseTextEditor { - static readonly ID: string = 'workbench.editor.searchEditor'; + static readonly ID: string = SearchEditorID; static readonly SEARCH_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'searchEditorViewState'; @@ -194,6 +194,7 @@ export class SearchEditor extends BaseTextEditor { const runAgainLink = DOM.append(this.messageBox, DOM.$('a.pointer.prominent.message', {}, localize('runSearch', "Run Search"))); this.messageDisposables.push(DOM.addDisposableListener(runAgainLink, DOM.EventType.CLICK, async () => { await this.triggerSearch(); + this.searchResultEditor.focus(); this.toggleRunAgainMessage(false); })); } diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 421eb7397a..2cb18075cb 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -116,6 +116,24 @@ export class OpenResultsInEditorAction extends Action { } } +export class RerunSearchEditorSearchAction extends Action { + static readonly ID: string = Constants.RerunSearchEditorSearchCommandId; + static readonly LABEL = localize('search.rerunSearchInEditor', "Search Again"); + + constructor(id: string, label: string, + @IEditorService private readonly editorService: IEditorService, + ) { + super(id, label, 'codicon-refresh'); + } + + async run() { + const input = this.editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (this.editorService.activeControl as SearchEditor).triggerSearch({ resetCursor: false }); + } + } +} + const openNewSearchEditor = async (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 0b7aee547c..8e25116706 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -18,7 +18,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { EditorInput, GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, IMoveResult } from 'vs/workbench/common/editor'; -import { SearchEditorFindMatchClass, SearchEditorScheme } from 'vs/workbench/contrib/searchEditor/browser/constants'; +import { SearchEditorFindMatchClass, SearchEditorScheme, SearchEditorBodyScheme } from 'vs/workbench/contrib/searchEditor/browser/constants'; import { extractSearchQuery, serializeSearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -339,7 +339,7 @@ export const getOrMakeSearchEditorInput = ( } } - const contentsModelURI = uri.with({ scheme: 'search-editor-body' }); + const contentsModelURI = uri.with({ scheme: SearchEditorBodyScheme }); const headerModelURI = uri.with({ scheme: 'search-editor-header' }); const contentsModel = modelService.getModel(contentsModelURI) ?? modelService.createModel('', modeService.create('search-result'), contentsModelURI); const headerModel = modelService.getModel(headerModelURI) ?? modelService.createModel('', modeService.create('search-result'), headerModelURI); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts index 288243d378..ea7bd775bf 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts @@ -216,7 +216,7 @@ export const serializeSearchResultForEditor = const info = [ searchResult.count() - ? `${filecount} - ${resultcount}` + ? `${resultcount} - ${filecount}` : localize('noResults', "No Results"), '']; diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 509b931cb3..b9277ac719 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -63,7 +63,7 @@ import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/com import * as TaskConfig from '../common/taskConfiguration'; import { TerminalTaskSystem } from './terminalTaskSystem'; -import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, QuickPickInput, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -80,6 +80,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { find } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IViewsService } from 'vs/workbench/common/views'; +import { ProviderProgressMananger } from 'vs/workbench/contrib/tasks/browser/providerProgressManager'; const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; @@ -218,6 +219,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _providers: Map; private _providerTypes: Map; protected _taskSystemInfos: Map; + private _providerProgressManager: ProviderProgressMananger | undefined; protected _workspaceTasksPromise?: Promise>; protected _areJsonTasksSupportedPromise: Promise = Promise.resolve(false); @@ -560,6 +562,37 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract versionAndEngineCompatible(filter?: TaskFilter): boolean; + private tasksAndGroupedTasks(filter?: TaskFilter): { tasks: Promise, grouped: Promise } { + if (!this.versionAndEngineCompatible(filter)) { + return { tasks: Promise.resolve([]), grouped: Promise.resolve(new TaskMap()) }; + } + const grouped = this.getGroupedTasks(filter ? filter.type : undefined); + const tasks = grouped.then((map) => { + if (!filter || !filter.type) { + return map.all(); + } + let result: Task[] = []; + map.forEach((tasks) => { + for (let task of tasks) { + if (ContributedTask.is(task) && task.defines.type === filter.type) { + result.push(task); + } else if (CustomTask.is(task)) { + if (task.type === filter.type) { + result.push(task); + } else { + let customizes = task.customizes(); + if (customizes && customizes.type === filter.type) { + result.push(task); + } + } + } + } + }); + return result; + }); + return { tasks, grouped }; + } + public tasks(filter?: TaskFilter): Promise { if (!this.versionAndEngineCompatible(filter)) { return Promise.resolve([]); @@ -703,11 +736,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System): Promise { + public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System, grouped?: Promise): Promise { if (!task) { throw new TaskError(Severity.Info, nls.localize('TaskServer.noTask', 'Task to execute is undefined'), TaskErrors.TaskNotFound); } - return this.getGroupedTasks().then((grouped) => { + return (grouped ?? this.getGroupedTasks()).then((grouped) => { let resolver = this.createResolver(grouped); if (options && options.attachProblemMatcher && this.shouldAttachProblemMatcher(task) && !InMemoryTask.is(task)) { return this.attachProblemMatcher(task).then((toExecute) => { @@ -1345,11 +1378,24 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract getTaskSystem(): ITaskSystem; - private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary): Promise { + private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary): Promise { return new Promise(async (resolve, reject) => { - provider.provideTasks(validTypes).then((value) => { + let isDone = false; + let disposable: IDisposable | undefined; + const providePromise = provider.provideTasks(validTypes); + this._providerProgressManager?.addProvider(type, providePromise); + disposable = this._providerProgressManager?.canceled.token.onCancellationRequested(() => { + if (!isDone) { + resolve(); + } + }); + providePromise.then((value) => { + isDone = true; + disposable?.dispose(); resolve(value); }, (e) => { + isDone = true; + disposable?.dispose(); reject(e); }); }); @@ -1361,10 +1407,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); validTypes['shell'] = true; validTypes['process'] = true; + this._providerProgressManager = new ProviderProgressMananger(); return new Promise(resolve => { let result: TaskSet[] = []; let counter: number = 0; - let done = (value: TaskSet) => { + let done = (value: TaskSet | undefined) => { if (value) { result.push(value); } @@ -2060,30 +2107,69 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } return entries; }); - return this.quickInputService.pick(pickEntries, { - placeHolder, - matchOnDescription: true, - onDidTriggerItemButton: context => { - let task = context.item.task; - this.quickInputService.cancel(); - if (ContributedTask.is(task)) { - this.customize(task, undefined, true); - } else if (CustomTask.is(task)) { - this.openConfig(task); + + const picker: IQuickPick = this.quickInputService.createQuickPick(); + picker.placeholder = placeHolder; + picker.matchOnDescription = true; + picker.ignoreFocusOut = true; + + picker.onDidTriggerItemButton(context => { + let task = context.item.task; + this.quickInputService.cancel(); + if (ContributedTask.is(task)) { + this.customize(task, undefined, true); + } else if (CustomTask.is(task)) { + this.openConfig(task); + } + }); + picker.busy = true; + const progressManager = this._providerProgressManager; + const progressTimeout = setTimeout(() => { + if (progressManager) { + progressManager.showProgress = (stillProviding, total) => { + let message = undefined; + if (stillProviding.length > 0) { + message = nls.localize('pickProgressManager.description', 'Detecting tasks ({0} of {1}): {2} in progress', total - stillProviding.length, total, stillProviding.join(', ')); + } + picker.description = message; + }; + progressManager.addOnDoneListener(() => { + picker.focusOnInput(); + picker.customButton = false; + }); + if (!progressManager.isDone) { + picker.customLabel = nls.localize('taskQuickPick.cancel', "Stop detecting"); + picker.onDidCustom(() => { + this._providerProgressManager?.cancel(); + }); + picker.customButton = true; } } - }, cancellationToken).then(async (selection) => { - if (cancellationToken.isCancellationRequested) { - // canceled when there's only one task - const task = (await pickEntries)[0]; - if ((task).task) { - selection = task; + }, 1000); + pickEntries.then(entries => { + clearTimeout(progressTimeout); + progressManager?.dispose(); + picker.busy = false; + picker.items = entries; + }); + picker.show(); + + return new Promise(resolve => { + this._register(picker.onDidAccept(async () => { + let selection = picker.selectedItems ? picker.selectedItems[0] : undefined; + if (cancellationToken.isCancellationRequested) { + // canceled when there's only one task + const task = (await pickEntries)[0]; + if ((task).task) { + selection = task; + } } - } - if (!selection) { - return undefined; // {{SQL CARBON EDIT}} strict-null-checks - } - return selection; + picker.dispose(); + if (!selection) { + resolve(); + } + resolve(selection); + })); }); } @@ -2138,7 +2224,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private doRunTaskCommand(tasks?: Task[]): void { this.showIgnoredFoldersMessage().then(() => { - this.showQuickPick(tasks ? tasks : this.tasks(), + let taskResult: { tasks: Promise, grouped: Promise } | undefined = undefined; + if (!tasks) { + taskResult = this.tasksAndGroupedTasks(); + } + this.showQuickPick(tasks ? tasks : taskResult!.tasks, nls.localize('TaskService.pickRunTask', 'Select the task to run'), { label: nls.localize('TaskService.noEntryToRun', 'No task to run found. Configure Tasks...'), @@ -2153,7 +2243,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (task === null) { this.runConfigureTasks(); } else { - this.run(task, { attachProblemMatcher: true }, TaskRunSource.User).then(undefined, reason => { + this.run(task, { attachProblemMatcher: true }, TaskRunSource.User, taskResult?.grouped).then(undefined, reason => { // eat the error, it has already been surfaced to the user and we don't care about it here }); } diff --git a/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts b/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts new file mode 100644 index 0000000000..2eb181665a --- /dev/null +++ b/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TaskSet } from 'vs/workbench/contrib/tasks/common/tasks'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; + +export class ProviderProgressMananger extends Disposable { + private _onProviderComplete: Emitter = new Emitter(); + private _stillProviding: Set = new Set(); + private _totalProviders: number = 0; + private _onDone: Emitter = new Emitter(); + private _isDone: boolean = false; + private _showProgress: ((remaining: string[], total: number) => void) | undefined; + public canceled: CancellationTokenSource = new CancellationTokenSource(); + + constructor() { + super(); + this._register(this._onProviderComplete.event(taskType => { + this._stillProviding.delete(taskType); + if (this._stillProviding.size === 0) { + this._isDone = true; + this._onDone.fire(); + } + if (this._showProgress) { + this._showProgress(Array.from(this._stillProviding), this._totalProviders); + } + })); + } + + public addProvider(taskType: string, provider: Promise) { + this._totalProviders++; + this._stillProviding.add(taskType); + provider.then(() => this._onProviderComplete.fire(taskType)); + } + + public addOnDoneListener(onDoneListener: () => void) { + this._register(this._onDone.event(onDoneListener)); + } + + set showProgress(progressDisplayFunction: (remaining: string[], total: number) => void) { + this._showProgress = progressDisplayFunction; + this._showProgress(Array.from(this._stillProviding), this._totalProviders); + } + + get isDone(): boolean { + return this._isDone; + } + + public cancel() { + this._isDone = true; + if (this._showProgress) { + this._showProgress([], 0); + } + this._onDone.fire(); + this.canceled.cancel(); + } +} diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index b159f3250b..69d29941ed 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -681,6 +681,7 @@ export namespace RunOnOptions { } export namespace RunOptions { + const properties: MetaData[] = [{ property: 'reevaluateOnRerun' }, { property: 'runOn' }, { property: 'instanceLimit' }]; export function fromConfiguration(value: RunOptionsConfig | undefined): Tasks.RunOptions { return { reevaluateOnRerun: value ? value.reevaluateOnRerun : true, @@ -688,6 +689,14 @@ export namespace RunOptions { instanceLimit: value ? value.instanceLimit : 1 }; } + + export function assignProperties(target: Tasks.RunOptions, source: Tasks.RunOptions | undefined): Tasks.RunOptions { + return _assignProperties(target, source, properties)!; + } + + export function fillProperties(target: Tasks.RunOptions, source: Tasks.RunOptions | undefined): Tasks.RunOptions { + return _fillProperties(target, source, properties)!; + } } interface ParseContext { @@ -1609,6 +1618,7 @@ namespace CustomTask { result.command.presentation = CommandConfiguration.PresentationOptions.assignProperties( result.command.presentation!, configuredProps.configurationProperties.presentation)!; result.command.options = CommandOptions.assignProperties(result.command.options, configuredProps.configurationProperties.options); + result.runOptions = RunOptions.assignProperties(result.runOptions, configuredProps.runOptions); let contributedConfigProps: Tasks.ConfigurationProperties = contributedTask.configurationProperties; fillProperty(resultConfigProps, contributedConfigProps, 'group'); @@ -1621,6 +1631,7 @@ namespace CustomTask { result.command.presentation = CommandConfiguration.PresentationOptions.fillProperties( result.command.presentation!, contributedConfigProps.presentation)!; result.command.options = CommandOptions.fillProperties(result.command.options, contributedConfigProps.options); + result.runOptions = RunOptions.fillProperties(result.runOptions, contributedTask.runOptions); if (contributedTask.hasDefinedMatchers === true) { result.hasDefinedMatchers = true; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index fd0b32657e..a1cb0125ea 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -114,7 +114,7 @@ export class TerminalViewPane extends ViewPane { })); // Force another layout (first is setContainers) since config has changed - this.layoutBody(this._terminalContainer.offsetWidth, this._terminalContainer.offsetHeight); + this.layoutBody(this._terminalContainer.offsetHeight, this._terminalContainer.offsetWidth); } protected layoutBody(height: number, width: number): void { @@ -321,7 +321,7 @@ export class TerminalViewPane extends ViewPane { } // TODO: Can we support ligatures? // dom.toggleClass(this._parentDomElement, 'enable-ligatures', this._terminalService.configHelper.config.fontLigatures); - this.layoutBody(this._parentDomElement.offsetWidth, this._parentDomElement.offsetHeight); + this.layoutBody(this._parentDomElement.offsetHeight, this._parentDomElement.offsetWidth); } } diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 15f5262e76..e3b5bd93bc 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -251,7 +251,7 @@ export class TimelinePane extends ViewPane { } this._tree.setChildren(null, undefined); - this.message = localize('timeline.loading', 'Loading timeline for ${0}...', basename(uri.fsPath)); + this.message = localize('timeline.loading', 'Loading timeline for {0}...', basename(uri.fsPath)); }, 500, this._uri); } } @@ -291,7 +291,7 @@ export class TimelinePane extends ViewPane { if (!reset) { // TODO: Handle pending request - if (cursors?.more === false) { + if (cursors?.more !== true) { continue; } @@ -306,6 +306,10 @@ export class TimelinePane extends ViewPane { request?.tokenSource ?? new CancellationTokenSource(), { cacheResults: true } )!; + if (request === undefined) { + continue; + } + this._pendingRequests.set(source, request); if (!reusingToken) { request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); @@ -322,6 +326,10 @@ export class TimelinePane extends ViewPane { new CancellationTokenSource(), { cacheResults: true } )!; + if (request === undefined) { + continue; + } + this._pendingRequests.set(source, request); request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts index 16ad20c72a..15db362ea2 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts @@ -10,6 +10,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { @@ -20,15 +21,15 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { @IAuthenticationTokenService authTokenService: IAuthenticationTokenService, @IInstantiationService instantiationService: IInstantiationService, @IHostService hostService: IHostService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService); + super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService, telemetryService); - // Sync immediately if there is a local change. - this._register(Event.debounce(Event.any( - userDataSyncService.onDidChangeLocal, + this._register(Event.debounce(Event.any( + Event.map(hostService.onDidChangeFocus, () => 'windowFocus'), instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync, - hostService.onDidChangeFocus - ), () => undefined, 500)(() => this.triggerAutoSync())); + userDataSyncService.onDidChangeLocal, + ), (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerAutoSync(sources))); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 38a4de0e2e..dbc04d3563 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -88,18 +88,18 @@ const getActivityTitle = (label: string, userDataSyncService: IUserDataSyncServi } return label; }; -const getIdentityTitle = (label: string, account?: AuthenticationSession): string => { - return account ? `${label} (${account.accountName})` : label; +const getIdentityTitle = (label: string, authenticationProviderId: string, account: AuthenticationSession | undefined, authenticationService: IAuthenticationService): string => { + return account ? `${label} (${authenticationService.getDisplayName(authenticationProviderId)}:${account.accountName})` : label; }; const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Sync: Turn on Sync") }; const signInCommand = { id: 'workbench.userData.actions.signin', title: localize('sign in', "Sync: Sign in to sync") }; -const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title(account?: AuthenticationSession) { return getIdentityTitle(localize('stop sync', "Sync: Turn off Sync"), account); } }; +const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title(authenticationProviderId: string, account: AuthenticationSession | undefined, authenticationService: IAuthenticationService) { return getIdentityTitle(localize('stop sync', "Sync: Turn off Sync"), authenticationProviderId, account, authenticationService); } }; const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Sync: Show Settings Conflicts") }; const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Sync: Show Keybindings Conflicts") }; const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Sync: Configure") }; const showSyncActivityCommand = { id: 'workbench.userData.actions.showSyncActivity', title(userDataSyncService: IUserDataSyncService): string { - return getActivityTitle(localize('show sync log', "Sync: Show Activity"), userDataSyncService); + return getActivityTitle(localize('show sync log', "Sync: Show Log"), userDataSyncService); } }; const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Sync: Settings"), }; @@ -163,7 +163,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.registerActions(); this.initializeActiveAccount().then(_ => { if (!isWeb) { - this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => userDataAutoSyncService.triggerAutoSync())); + this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(source => userDataAutoSyncService.triggerAutoSync([source]))); } }); @@ -226,7 +226,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (account) { try { - const token = await account.accessToken(); + const token = await account.getAccessToken(); this.authTokenService.setToken(token); this.authenticationState.set(AuthStatus.SignedIn); } catch (e) { @@ -290,7 +290,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const conflictsEditorInput = this.getConflictsEditorInput(conflictsSource); if (!conflictsEditorInput && !this.conflictsDisposables.has(conflictsSource)) { const conflictsArea = getSyncAreaLabel(conflictsSource); - const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea), + const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea.toLowerCase()), [ { label: localize('accept remote', "Accept Remote"), @@ -409,9 +409,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const sourceArea = getSyncAreaLabel(error.source); this.notificationService.notify({ severity: Severity.Error, - message: localize('too large', "Disabled syncing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea, sourceArea, '100kb'), + message: localize('too large', "Disabled syncing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea.toLowerCase(), sourceArea.toLowerCase(), '100kb'), actions: { - primary: [new Action('open sync file', localize('open file', "Open {0} file", sourceArea), undefined, true, + primary: [new Action('open sync file', localize('open file', "Open {0} File", sourceArea), undefined, true, () => error.source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); @@ -450,22 +450,31 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private handleInvalidContentError(source: SyncSource): void { - if (!this.invalidContentErrorDisposables.has(source)) { - const errorArea = getSyncAreaLabel(source); - const handle = this.notificationService.notify({ - severity: Severity.Error, - message: localize('errorInvalidConfiguration', "Unable to sync {0} because there are some errors/warnings in the file. Please open the file to correct errors/warnings in it.", errorArea), - actions: { - primary: [new Action('open sync file', localize('open file', "Open {0} file", errorArea), undefined, true, - () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] - } - }); - this.invalidContentErrorDisposables.set(source, toDisposable(() => { - // close the error warning notification - handle.close(); - this.invalidContentErrorDisposables.delete(source); - })); + if (this.invalidContentErrorDisposables.has(source)) { + return; } + if (source !== SyncSource.Settings && source !== SyncSource.Keybindings) { + return; + } + const resource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsResource : this.workbenchEnvironmentService.keybindingsResource; + if (isEqual(resource, this.editorService.activeEditor?.resource)) { + // Do not show notification if the file in error is active + return; + } + const errorArea = getSyncAreaLabel(source); + const handle = this.notificationService.notify({ + severity: Severity.Error, + message: localize('errorInvalidConfiguration', "Unable to sync {0} because there are some errors/warnings in the file. Please open the file to correct errors/warnings in it.", errorArea.toLowerCase()), + actions: { + primary: [new Action('open sync file', localize('open file', "Open {0} File", errorArea), undefined, true, + () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + } + }); + this.invalidContentErrorDisposables.set(source, toDisposable(() => { + // close the error warning notification + handle.close(); + this.invalidContentErrorDisposables.delete(source); + })); } private async updateBadge(): Promise { @@ -493,7 +502,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."), [ localize('open doc', "Open Documentation"), - localize('confirm', "Continue"), + localize('turn on sync', "Turn on Sync"), localize('cancel', "Cancel"), ], { @@ -509,11 +518,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); disposables.add(quickPick); - quickPick.title = localize('turn on sync', "Turn on Sync"); + quickPick.title = localize('turn on title', "Sync: Turn On"); quickPick.ok = false; quickPick.customButton = true; if (this.authenticationState.get() === AuthStatus.SignedIn) { - quickPick.customLabel = localize('turn on', "Turn on"); + quickPick.customLabel = localize('turn on', "Turn On"); } else { const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId); quickPick.description = localize('sign in and turn on sync detail', "Sign in with your {0} account to synchronize your data across devices.", displayName); @@ -538,6 +547,39 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async doTurnOn(): Promise { + if (this.authenticationState.get() === AuthStatus.SignedIn) { + await new Promise((c, e) => { + const disposables: DisposableStore = new DisposableStore(); + const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId); + const quickPick = this.quickInputService.createQuickPick<{ id: string, label: string, description?: string, detail?: string }>(); + disposables.add(quickPick); + const chooseAnotherItemId = 'chooseAnother'; + quickPick.title = localize('pick account', "{0}: Pick an account", displayName); + quickPick.ok = false; + quickPick.placeholder = localize('choose account placeholder', "Pick an account for syncing"); + quickPick.ignoreFocusOut = true; + quickPick.items = [{ + id: 'existing', + label: localize('existing', "{0}", this.activeAccount!.accountName), + detail: localize('signed in', "Signed in"), + }, { + id: chooseAnotherItemId, + label: localize('choose another', "Use another account") + }]; + disposables.add(quickPick.onDidAccept(async () => { + if (quickPick.selectedItems.length) { + if (quickPick.selectedItems[0].id === chooseAnotherItemId) { + await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount!.id); + await this.setActiveAccount(undefined); + } + quickPick.hide(); + c(); + } + })); + disposables.add(quickPick.onDidHide(() => disposables.dispose())); + quickPick.show(); + }); + } if (this.authenticationState.get() === AuthStatus.SignedOut) { await this.signIn(); } @@ -608,7 +650,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } const result = await this.dialogService.show( Severity.Info, - localize('firs time sync', "First time Sync"), + localize('firs time sync', "Sync"), [ localize('merge', "Merge"), localize('cancel', "Cancel"), @@ -616,7 +658,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo ], { cancelId: 1, - detail: localize('first time sync detail', "Synchronizing from this device for the first time.\nWould you like to merge or replace with the data from the cloud?"), + detail: localize('first time sync detail', "It looks like this is the first time sync is set up.\nWould you like to merge or replace with the data from the cloud?"), } ); switch (result.choice) { @@ -638,7 +680,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo type: 'info', message: localize('turn off sync confirmation', "Turn off Sync"), detail: localize('turn off sync detail', "Your settings, keybindings, extensions and UI State will no longer be synced."), - primaryButton: localize('turn off', "Turn off"), + primaryButton: localize('turn off', "Turn Off"), checkbox: { label: localize('turn off sync everywhere', "Turn off sync on all your devices and clear the data from the cloud.") } @@ -874,7 +916,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo return new Promise((c, e) => { const quickInputService = accessor.get(IQuickInputService); const commandService = accessor.get(ICommandService); + const disposables = new DisposableStore(); const quickPick = quickInputService.createQuickPick(); + disposables.add(quickPick); const items: Array = []; if (that.userDataSyncService.conflictsSources.length) { for (const source of that.userDataSyncService.conflictsSources) { @@ -893,9 +937,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo items.push({ id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title }); items.push({ id: showSyncActivityCommand.id, label: showSyncActivityCommand.title(that.userDataSyncService) }); items.push({ type: 'separator' }); - items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title(that.activeAccount), }); + items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title(that.userDataSyncStore!.authenticationProviderId, that.activeAccount, that.authenticationService) }); quickPick.items = items; - const disposables = new DisposableStore(); disposables.add(quickPick.onDidAccept(() => { if (quickPick.selectedItems[0] && quickPick.selectedItems[0].id) { commandService.executeCommand(quickPick.selectedItems[0].id); @@ -918,7 +961,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: stopSyncCommand.id, - title: stopSyncCommand.title(that.activeAccount), + title: stopSyncCommand.title(that.userDataSyncStore!.authenticationProviderId, that.activeAccount, that.authenticationService), menu: { id: MenuId.CommandPalette, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), @@ -1091,8 +1134,8 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio ? localize('Sync accept remote', "Sync: {0}", acceptRemoteLabel) : localize('Sync accept local', "Sync: {0}", acceptLocalLabel), message: isRemote - ? localize('confirm replace and overwrite local', "Would you like to accept Remote {0} and replace Local {1}?", syncAreaLabel, syncAreaLabel) - : localize('confirm replace and overwrite remote', "Would you like to accept Local {0} and replace Remote {1}?", syncAreaLabel, syncAreaLabel), + ? localize('confirm replace and overwrite local', "Would you like to accept Remote {0} and replace Local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()) + : localize('confirm replace and overwrite remote', "Would you like to accept Local {0} and replace Remote {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()), primaryButton: isRemote ? acceptRemoteLabel : acceptLocalLabel }); if (result.confirmed) { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts index 6b21810acf..bcf6ef7ef1 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts @@ -16,8 +16,8 @@ import { IViewlet } from 'vs/workbench/common/viewlet'; export class UserDataSyncTrigger extends Disposable { - private readonly _onDidTriggerSync: Emitter = this._register(new Emitter()); - readonly onDidTriggerSync: Event = this._onDidTriggerSync.event; + private readonly _onDidTriggerSync: Emitter = this._register(new Emitter()); + readonly onDidTriggerSync: Event = this._onDidTriggerSync.event; constructor( @IEditorService editorService: IEditorService, @@ -25,37 +25,44 @@ export class UserDataSyncTrigger extends Disposable { @IViewletService viewletService: IViewletService, ) { super(); - this._register(Event.debounce(Event.any( - Event.filter(editorService.onDidActiveEditorChange, () => this.isUserDataEditorInput(editorService.activeEditor)), - Event.filter(viewletService.onDidViewletOpen, viewlet => this.isUserDataViewlet(viewlet)) - ), () => undefined, 500)(() => this._onDidTriggerSync.fire())); + this._register(Event.any( + Event.map(editorService.onDidActiveEditorChange, () => this.getUserDataEditorInputSource(editorService.activeEditor)), + Event.map(viewletService.onDidViewletOpen, viewlet => this.getUserDataViewletSource(viewlet)) + )(source => { + if (source) { + this._onDidTriggerSync.fire(source); + } + })); } - private isUserDataViewlet(viewlet: IViewlet): boolean { - return viewlet.getId() === VIEWLET_ID; + private getUserDataViewletSource(viewlet: IViewlet): string | undefined { + if (viewlet.getId() === VIEWLET_ID) { + return 'extensionsViewlet'; + } + return undefined; } - private isUserDataEditorInput(editorInput: IEditorInput | undefined): boolean { + private getUserDataEditorInputSource(editorInput: IEditorInput | undefined): string | undefined { if (!editorInput) { - return false; + return undefined; } if (editorInput instanceof SettingsEditor2Input) { - return true; + return 'settingsEditor'; } if (editorInput instanceof PreferencesEditorInput) { - return true; + return 'settingsEditor'; } if (editorInput instanceof KeybindingsEditorInput) { - return true; + return 'keybindingsEditor'; } const resource = editorInput.resource; if (isEqual(resource, this.workbenchEnvironmentService.settingsResource)) { - return true; + return 'settingsEditor'; } if (isEqual(resource, this.workbenchEnvironmentService.keybindingsResource)) { - return true; + return 'keybindingsEditor'; } - return false; + return undefined; } } diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts index dbfb91e1cb..c1d9ff1419 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts @@ -7,9 +7,9 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewsWelcomeExtensionPoint, ViewWelcome, viewsWelcomeExtensionPointDescriptor } from './viewsWelcomeExtensionPoint'; +import { ViewsWelcomeExtensionPoint, ViewWelcome, viewsWelcomeExtensionPointDescriptor, ViewIdentifierMap } from './viewsWelcomeExtensionPoint'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IViewsRegistry, ViewContentPriority } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); @@ -45,9 +45,11 @@ export class ViewsWelcomeContribution extends Disposable implements IWorkbenchCo } for (const welcome of contribution.value) { - const disposable = viewsRegistry.registerViewWelcomeContent(welcome.view, { + const id = ViewIdentifierMap[welcome.view] ?? welcome.view; + const disposable = viewsRegistry.registerViewWelcomeContent(id, { content: welcome.contents, - when: ContextKeyExpr.deserialize(welcome.when) + when: ContextKeyExpr.deserialize(welcome.when), + priority: contribution.description.isBuiltin ? ViewContentPriority.Low : ViewContentPriority.Lowest }); this.viewWelcomeContents.set(welcome, disposable); diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts index 49283c570f..2cb7762086 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts @@ -20,9 +20,15 @@ export interface ViewWelcome { export type ViewsWelcomeExtensionPoint = ViewWelcome[]; +export const ViewIdentifierMap: { [key: string]: string } = { + 'explorer': 'workbench.explorer.emptyView', + 'debug': 'workbench.debug.startView', + 'scm': 'workbench.scm', +}; + const viewsWelcomeExtensionPointSchema = Object.freeze({ type: 'array', - description: nls.localize('contributes.viewsWelcome', "Contributed views welcome content."), + description: nls.localize('contributes.viewsWelcome', "Contributed views welcome content. Welcome content will be rendered in views whenever they have no meaningful content to display, ie. the File Explorer when no folder is open. Such content is useful as in-product documentation to drive users to use certain features before they are available. A good example would be a `Clone Repository` button in the File Explorer welcome view."), items: { type: 'object', description: nls.localize('contributes.viewsWelcome.view', "Contributed welcome content for a specific view."), @@ -33,15 +39,16 @@ const viewsWelcomeExtensionPointSchema = Object.freeze, edits: WorkspaceTextEdit[], @@ -232,7 +233,7 @@ class BulkEditModel implements IDisposable { } const multiModelEditStackElement = new MultiModelEditStackElement( - localize('workspaceEdit', "Workspace Edit"), + this._label || localize('workspaceEdit', "Workspace Edit"), tasks.map(t => new EditStackElement(t.model, t.getBeforeCursorState())) ); this._undoRedoService.pushElement(multiModelEditStackElement); @@ -250,11 +251,13 @@ type Edit = WorkspaceFileEdit | WorkspaceTextEdit; class BulkEdit { + private readonly _label: string | undefined; private readonly _edits: Edit[] = []; private readonly _editor: ICodeEditor | undefined; private readonly _progress: IProgress; constructor( + label: string | undefined, editor: ICodeEditor | undefined, progress: IProgress | undefined, edits: Edit[], @@ -265,6 +268,7 @@ class BulkEdit { @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { + this._label = label; this._editor = editor; this._progress = progress || Progress.None; this._edits = edits; @@ -361,7 +365,7 @@ class BulkEdit { private async _performTextEdits(edits: WorkspaceTextEdit[], progress: IProgress): Promise { this._logService.debug('_performTextEdits', JSON.stringify(edits)); - const model = this._instaService.createInstance(BulkEditModel, this._editor, progress, edits); + const model = this._instaService.createInstance(BulkEditModel, this._label, this._editor, progress, edits); await model.prepare(); @@ -439,7 +443,7 @@ export class BulkEditService implements IBulkEditService { // If the code editor is readonly still allow bulk edits to be applied #68549 codeEditor = undefined; } - const bulkEdit = this._instaService.createInstance(BulkEdit, codeEditor, options?.progress, edits); + const bulkEdit = this._instaService.createInstance(BulkEdit, options?.quotableLabel || options?.label, codeEditor, options?.progress, edits); return bulkEdit.perform().then(() => { return { ariaSummary: bulkEdit.ariaMessage() }; }).catch(err => { diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts index 7200254785..4ecfa4d460 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts @@ -46,7 +46,7 @@ export const inputsSchema: IJSONSchema = { }, password: { type: 'boolean', - description: nls.localize('JsonSchema.input.password', "Set to true to show a password prompt that will not show the typed value."), + description: nls.localize('JsonSchema.input.password', "Controls if a password input is shown. Password input hides the typed text."), }, } }, diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index 05f49054f1..63013052e8 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -92,6 +92,10 @@ export abstract class AbstractFileDialogService implements IFileDialogService { return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) } + return this.doShowSaveConfirm(fileNamesOrResources); + } + + protected async doShowSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { if (fileNamesOrResources.length === 0) { return ConfirmResult.DONT_SAVE; } diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index b180a77f49..d546740f9e 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -156,7 +156,7 @@ export class SimpleFileDialog { public async showOpenDialog(options: IOpenDialogOptions = {}): Promise { this.scheme = this.getScheme(options.availableFileSystems, options.defaultUri); - this.userHome = await this.remotePathService.userHome; + this.userHome = await this.getUserHome(); const newOptions = this.getOptions(options); if (!newOptions) { return Promise.resolve(undefined); @@ -167,7 +167,7 @@ export class SimpleFileDialog { public async showSaveDialog(options: ISaveDialogOptions): Promise { this.scheme = this.getScheme(options.availableFileSystems, options.defaultUri); - this.userHome = await this.remotePathService.userHome; + this.userHome = await this.getUserHome(); this.requiresTrailing = true; const newOptions = this.getOptions(options, true); if (!newOptions) { @@ -231,6 +231,13 @@ export class SimpleFileDialog { return this.remoteAgentEnvironment; } + private async getUserHome(): Promise { + if (this.scheme !== Schemas.file) { + return this.remotePathService.userHome; + } + return URI.from({ scheme: this.scheme, path: this.environmentService.userHome }); + } + private async pickResource(isSave: boolean = false): Promise { this.allowFolderSelection = !!this.options.canSelectFolders; this.allowFileSelection = !!this.options.canSelectFiles; diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index 399272d689..720ffc9993 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -198,7 +198,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil } } - return super.showSaveConfirm(fileNamesOrResources); + return super.doShowSaveConfirm(fileNamesOrResources); } } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 24e44f4ddc..b9b7743edb 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -137,7 +137,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); } @memoize - get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, '.sync'); } + get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'sync'); } @memoize get settingsSyncPreviewResource(): URI { return joinPath(this.userDataSyncHome, 'settings.json'); } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index e877fb59c1..85725eccf2 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -209,7 +209,7 @@ export class ExtensionManagementService extends Disposable implements IExtension return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name)); } if (!isLanguagePackExtension(manifest) && !canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) { - const error = new Error(localize('cannot be installed', "Cannot install '{0}' extension since it cannot be enabled in the remote server.", gallery.displayName || gallery.name)); + const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension has defined that it cannot run on the remote server.", gallery.displayName || gallery.name)); error.name = INSTALL_ERROR_NOT_SUPPORTED; return Promise.reject(error); } diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index f7e7bc111e..7e1b563f6a 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/progressService'; import { localize } from 'vs/nls'; -import { IDisposable, dispose, DisposableStore, MutableDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore, MutableDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator, IProgressWindowOptions } from 'vs/platform/progress/common/progress'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { StatusbarAlignment, IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; @@ -24,6 +24,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventHelper } from 'vs/base/browser/dom'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { parseLinkedText } from 'vs/base/common/linkedText'; export class ProgressService extends Disposable implements IProgressService { @@ -191,6 +192,46 @@ export class ProgressService extends Disposable implements IProgressService { } }; + const createWindowProgress = () => { + + // Create a promise that we can resolve as needed + // when the outside calls dispose on us + let promiseResolve: () => void; + const promise = new Promise(resolve => promiseResolve = resolve); + + this.withWindowProgress({ + location: ProgressLocation.Window, + title: options.title ? parseLinkedText(options.title).toString() : undefined, // convert markdown links => string + command: 'notifications.showList' + }, progress => { + + function reportProgress(step: IProgressStep) { + if (step.message) { + progress.report({ + message: parseLinkedText(step.message).toString() // convert markdown links => string + }); + } + } + + // Apply any progress that was made already + if (progressStateModel.step) { + reportProgress(progressStateModel.step); + } + + // Continue to report progress as it happens + const onDidReportListener = progressStateModel.onDidReport(step => reportProgress(step)); + promise.finally(() => onDidReportListener.dispose()); + + // When the progress model gets disposed, we are done as well + Event.once(progressStateModel.onDispose)(() => promiseResolve()); + + return promise; + }); + + // Dispose means completing our promise + return toDisposable(() => promiseResolve()); + }; + const createNotification = (message: string, increment?: number): INotificationHandle => { const notificationDisposables = new DisposableStore(); @@ -229,19 +270,34 @@ export class ProgressService extends Disposable implements IProgressService { primaryActions.push(cancelAction); } - const handle = this.notificationService.notify({ + const notification = this.notificationService.notify({ severity: Severity.Info, message, source: options.source, - actions: { primary: primaryActions, secondary: secondaryActions } + actions: { primary: primaryActions, secondary: secondaryActions }, + progress: typeof increment === 'number' && increment >= 0 ? { total: 100, worked: increment } : { infinite: true } }); - updateProgress(handle, increment); + // Switch to window based progress once the notification + // changes visibility to hidden and is still ongoing. + // Remove that window based progress once the notification + // shows again. + let windowProgressDisposable: IDisposable | undefined = undefined; + notificationDisposables.add(notification.onDidChangeVisibility(visible => { + + // Clear any previous running window progress + dispose(windowProgressDisposable); + + // Create new window progress if notification got hidden + if (!visible && !progressStateModel.done) { + windowProgressDisposable = createWindowProgress(); + } + })); // Clear upon dispose - Event.once(handle.onDidClose)(() => notificationDisposables.dispose()); + Event.once(notification.onDidClose)(() => notificationDisposables.dispose()); - return handle; + return notification; }; const updateProgress = (notification: INotificationHandle, increment?: number): void => { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 9ebcfc885a..cdb6f4e798 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -251,6 +251,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil async load(options?: ITextFileLoadOptions): Promise { this.logService.trace('[text file model] load() - enter', this.resource.toString(true)); + // Return early if we are disposed + if (this.isDisposed()) { + this.logService.trace('[text file model] load() - exit - without loading because model is disposed', this.resource.toString(true)); + + return this; + } + // It is very important to not reload the model when the model is dirty. // We also only want to reload the model from the disk if no save is pending // to avoid data loss. @@ -359,7 +366,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private loadFromContent(content: ITextFileStreamContent, options?: ITextFileLoadOptions, fromBackup?: boolean): TextFileEditorModel { - this.logService.trace('[text file model] load() - resolved content', this.resource.toString(true)); + this.logService.trace('[text file model] loadFromContent() - enter', this.resource.toString(true)); + + // Return early if we are disposed + if (this.isDisposed()) { + this.logService.trace('[text file model] loadFromContent() - exit - because model is disposed', this.resource.toString(true)); + + return this; + } // Update our resolved disk stat model this.updateLastResolvedFileStat({ @@ -405,7 +419,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private doCreateTextModel(resource: URI, value: ITextBufferFactory, fromBackup: boolean): void { - this.logService.trace('[text file model] load() - created text editor model', this.resource.toString(true)); + this.logService.trace('[text file model] doCreateTextModel()', this.resource.toString(true)); // Create model const textModel = this.createTextEditorModel(value, resource, this.preferredMode); @@ -420,7 +434,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private doUpdateTextModel(value: ITextBufferFactory): void { - this.logService.trace('[text file model] load() - updated text editor model', this.resource.toString(true)); + this.logService.trace('[text file model] doUpdateTextModel()', this.resource.toString(true)); // Update model value in a block that ignores content change events for dirty tracking this.ignoreDirtyOnModelContentChange = true; @@ -703,7 +717,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveOptions): void { - this.logService.trace(`[text file model] doSave(${versionId}) - after write()`, this.resource.toString(true)); // Updated resolved stat with updated stat this.updateLastResolvedFileStat(stat); @@ -906,6 +919,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } dispose(): void { + this.logService.trace('[text file model] dispose()', this.resource.toString(true)); + this.disposed = true; this.inConflictMode = false; this.inOrphanMode = false; diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts index f36200b640..2ced95292e 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -24,8 +24,8 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto this.channel = sharedProcessService.getChannel('userDataAutoSync'); } - triggerAutoSync(): Promise { - return this.channel.call('triggerAutoSync'); + triggerAutoSync(sources: string[]): Promise { + return this.channel.call('triggerAutoSync', [sources]); } } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 70fc9339b8..dbf22bf30f 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -22,7 +22,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } + get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } private _conflictsSources: SyncSource[] = []; get conflictsSources(): SyncSource[] { return this._conflictsSources; } diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index 3f18fb3e9d..cdfcd28fda 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -23,6 +23,7 @@ suite('Notifications', () => { let item3 = NotificationViewItem.create({ severity: Severity.Info, message: 'Info Message' })!; let item4 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', source: 'Source' })!; let item5 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', actions: { primary: [new Action('id', 'label')] } })!; + let item6 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', actions: { primary: [new Action('id', 'label')] }, progress: { infinite: true } })!; assert.equal(item1.equals(item1), true); assert.equal(item2.equals(item2), true); @@ -35,6 +36,10 @@ suite('Notifications', () => { assert.equal(item1.equals(item4), false); assert.equal(item1.equals(item5), false); + // Progress + assert.equal(item1.hasProgress, false); + assert.equal(item6.hasProgress, true); + // Message Box assert.equal(item5.canCollapse, false); assert.equal(item5.expanded, true); @@ -93,6 +98,17 @@ suite('Notifications', () => { assert.equal(called, 1); + called = 0; + item1.onDidChangeVisibility(e => { + called++; + }); + + item1.updateVisibility(true); + item1.updateVisibility(false); + item1.updateVisibility(false); + + assert.equal(called, 2); + called = 0; item1.onDidClose(() => { called++; @@ -102,8 +118,8 @@ suite('Notifications', () => { assert.equal(called, 1); // Error with Action - let item6 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!; - assert.equal(item6.actions!.primary!.length, 1); + let item7 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!; + assert.equal(item7.actions!.primary!.length, 1); // Filter let item8 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.SILENT)!; diff --git a/test/automation/package.json b/test/automation/package.json index a3f3baf2f4..8fced3ba82 100644 --- a/test/automation/package.json +++ b/test/automation/package.json @@ -28,7 +28,8 @@ "concurrently": "^3.5.1", "cpx": "^1.5.0", "typescript": "3.7.5", - "watch": "^1.0.2" + "watch": "^1.0.2", + "tree-kill": "1.2.2" }, "dependencies": { "mkdirp": "^0.5.1", diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index a903e430b7..69bfdfcec3 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -115,9 +115,6 @@ async function createDriverHandle(): Promise { } export async function spawn(options: SpawnOptions): Promise { - const codePath = options.codePath; - const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath(); - const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); const handle = await createDriverHandle(); let child: cp.ChildProcess | undefined; @@ -126,63 +123,67 @@ export async function spawn(options: SpawnOptions): Promise { if (options.web) { await launch(options.userDataDir, options.workspacePath, options.codePath); connectDriver = connectPlaywrightDriver.bind(connectPlaywrightDriver, options.browser); - } else { - const env = process.env; - - const args = [ - options.workspacePath, - '--skip-getting-started', - '--skip-release-notes', - '--sticky-quickopen', - '--disable-telemetry', - '--disable-updates', - '--disable-crash-reporter', - `--extensions-dir=${options.extensionsPath}`, - `--user-data-dir=${options.userDataDir}`, - '--driver', handle - ]; - - if (options.remote) { - // Replace workspace path with URI - args[0] = `--${options.workspacePath.endsWith('.code-workspace') ? 'file' : 'folder'}-uri=vscode-remote://test+test/${URI.file(options.workspacePath).path}`; - - if (codePath) { - // running against a build: copy the test resolver extension - const testResolverExtPath = path.join(options.extensionsPath, 'vscode-test-resolver'); - if (!fs.existsSync(testResolverExtPath)) { - const orig = path.join(repoPath, 'extensions', 'vscode-test-resolver'); - await new Promise((c, e) => ncp(orig, testResolverExtPath, err => err ? e(err) : c())); - } - } - args.push('--enable-proposed-api=vscode.vscode-test-resolver'); - const remoteDataDir = `${options.userDataDir}-server`; - mkdirp.sync(remoteDataDir); - env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir; - } - - if (!codePath) { - args.unshift(repoPath); - } - - if (options.verbose) { - args.push('--driver-verbose'); - } - - if (options.log) { - args.push('--log', options.log); - } - - if (options.extraArgs) { - args.push(...options.extraArgs); - } - - const spawnOptions: cp.SpawnOptions = { env }; - child = cp.spawn(electronPath, args, spawnOptions); - instances.add(child); - child.once('exit', () => instances.delete(child!)); - connectDriver = connectElectronDriver; + return connect(connectDriver, child, '', handle, options.logger); } + const env = process.env; + const codePath = options.codePath; + const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); + + const args = [ + options.workspacePath, + '--skip-getting-started', + '--skip-release-notes', + '--sticky-quickopen', + '--disable-telemetry', + '--disable-updates', + '--disable-crash-reporter', + `--extensions-dir=${options.extensionsPath}`, + `--user-data-dir=${options.userDataDir}`, + `--disable-restore-windows`, + '--driver', handle + ]; + + if (options.remote) { + // Replace workspace path with URI + args[0] = `--${options.workspacePath.endsWith('.code-workspace') ? 'file' : 'folder'}-uri=vscode-remote://test+test/${URI.file(options.workspacePath).path}`; + + if (codePath) { + // running against a build: copy the test resolver extension + const testResolverExtPath = path.join(options.extensionsPath, 'vscode-test-resolver'); + if (!fs.existsSync(testResolverExtPath)) { + const orig = path.join(repoPath, 'extensions', 'vscode-test-resolver'); + await new Promise((c, e) => ncp(orig, testResolverExtPath, err => err ? e(err) : c())); + } + } + args.push('--enable-proposed-api=vscode.vscode-test-resolver'); + const remoteDataDir = `${options.userDataDir}-server`; + mkdirp.sync(remoteDataDir); + env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir; + } + + if (!codePath) { + args.unshift(repoPath); + } + + if (options.verbose) { + args.push('--driver-verbose'); + } + + if (options.log) { + args.push('--log', options.log); + } + + if (options.extraArgs) { + args.push(...options.extraArgs); + } + + const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath(); + const spawnOptions: cp.SpawnOptions = { env }; + child = cp.spawn(electronPath, args, spawnOptions); + instances.add(child); + child.once('exit', () => instances.delete(child!)); + connectDriver = connectElectronDriver; return connect(connectDriver, child, outPath, handle, options.logger); } diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index a41bcb12a0..2560cf75ee 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -10,6 +10,7 @@ import { mkdir } from 'fs'; import { promisify } from 'util'; import { IDriver, IDisposable } from './driver'; import { URI } from 'vscode-uri'; +import * as kill from 'tree-kill'; const width = 1200; const height = 800; @@ -93,6 +94,7 @@ let workspacePath: string | undefined; export async function launch(userDataDir: string, _workspacePath: string, codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH): Promise { workspacePath = _workspacePath; + const agentFolder = userDataDir; await promisify(mkdir)(agentFolder); const env = { @@ -121,7 +123,7 @@ export async function launch(userDataDir: string, _workspacePath: string, codeSe function teardown(): void { if (server) { - server.kill(); + kill(server.pid); server = undefined; } } @@ -137,13 +139,9 @@ function waitForEndpoint(): Promise { }); } -export function connect(engine: 'chromium' | 'webkit' | 'firefox' = 'chromium'): Promise<{ client: IDisposable, driver: IDriver }> { +export function connect(browserType: 'chromium' | 'webkit' | 'firefox' = 'chromium'): Promise<{ client: IDisposable, driver: IDriver }> { return new Promise(async (c) => { - const browser = await playwright[engine].launch({ - // Run in Edge dev on macOS - // executablePath: '/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev', - headless: false - }); + const browser = await playwright[browserType].launch({ headless: false, dumpio: true }); const context = await browser.newContext(); const page = await context.newPage(); await page.setViewportSize({ width, height }); diff --git a/test/automation/yarn.lock b/test/automation/yarn.lock index 98c63c91e0..11785373d4 100644 --- a/test/automation/yarn.lock +++ b/test/automation/yarn.lock @@ -1634,6 +1634,11 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +tree-kill@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + tree-kill@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.1.tgz#5398f374e2f292b9dcc7b2e71e30a5c3bb6c743a" diff --git a/test/smoke/README.md b/test/smoke/README.md index 8b41d05ed0..7bec799f89 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -13,13 +13,13 @@ yarn --cwd test/automation yarn smoketest # Dev (Web) -yarn smoketest --web --browser [chromium|firefox|webkit] +yarn smoketest --web --browser [chromium|webkit] # Build (Electron) yarn smoketest --build --stable-build # Build (Web - read instructions below) -yarn smoketest --build --web --browser [chromium|firefox|webkit] +yarn smoketest --build --web --browser [chromium|webkit] # Remote (Electron) yarn smoketest --build --remote diff --git a/test/smoke/src/areas/css/css.test.ts b/test/smoke/src/areas/css/css.test.ts deleted file mode 100644 index 01dceb5091..0000000000 --- a/test/smoke/src/areas/css/css.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Application, ProblemSeverity, Problems } from '../../../../automation'; - -export function setup() { - describe('CSS', () => { - it('verifies quick outline', async function () { - const app = this.app as Application; - await app.workbench.quickopen.openFile('style.css'); - - await app.workbench.quickopen.openQuickOutline(); - await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 2); - }); - - it('verifies warnings for the empty rule', async function () { - const app = this.app as Application; - await app.workbench.quickopen.openFile('style.css'); - await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}'); - - await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING)); - - await app.workbench.problems.showProblemsView(); - await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.WARNING)); - await app.workbench.problems.hideProblemsView(); - }); - - it('verifies that warning becomes an error once setting changed', async function () { - const app = this.app as Application; - await app.workbench.settingsEditor.addUserSetting('css.lint.emptyRules', '"error"'); - await app.workbench.quickopen.openFile('style.css'); - - await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR)); - - const problems = new Problems(app.code); - await problems.showProblemsView(); - await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.ERROR)); - await problems.hideProblemsView(); - }); - }); -} diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index fbc82ab498..876d4fdfb4 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -37,10 +37,10 @@ export function setup() { await app.workbench.scm.waitForTitle(title => /quellcodeverwaltung/i.test(title)); await app.workbench.debug.openDebugViewlet(); - await app.workbench.debug.waitForTitle(title => /debug/i.test(title)); + await app.workbench.debug.waitForTitle(title => /starten/i.test(title)); - // await app.workbench.extensions.openExtensionsViewlet(); - // await app.workbench.extensions.waitForTitle(title => /erweiterungen/i.test(title)); + await app.workbench.extensions.openExtensionsViewlet(); + await app.workbench.extensions.waitForTitle(title => /extensions/i.test(title)); }); }); } diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 1024cbf125..4ead782a9a 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -31,7 +31,6 @@ import { setup as setupDataMigrationTests } from './areas/workbench/data-migrati import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; import { setup as setupDataPreferencesTests } from './areas/preferences/preferences.test'; import { setup as setupDataSearchTests } from './areas/search/search.test'; -import { setup as setupDataCSSTests } from './areas/css/css.test'; import { setup as setupDataEditorTests } from './areas/editor/editor.test'; import { setup as setupDataStatusbarTests } from './areas/statusbar/statusbar.test'; import { setup as setupDataExtensionTests } from './areas/extensions/extensions.test'; @@ -63,8 +62,7 @@ const opts = minimist(args, { boolean: [ 'verbose', 'remote', - 'web', - 'ci' + 'web' ], default: { verbose: false @@ -319,27 +317,17 @@ describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { }); } - // CI only tests (must be reliable) - if (opts.ci) { - // TODO@Ben figure out tests that can run continously and reliably - } - - // Non-CI execution (all tests) - else { - /*if (!opts.web) { setupDataMigrationTests(opts['stable-build'], testDataPath); } {{SQL CARBON EDIT}} comment out tests - if (!opts.web) { setupDataLossTests(); } - if (!opts.web) { setupDataPreferencesTests(); } - setupDataSearchTests(); - setupDataCSSTests(); - setupDataEditorTests(); - setupDataStatusbarTests(!!opts.web); - if (!opts.web) { setupDataExtensionTests(); } - setupTerminalTests(); - if (!opts.web) { setupDataMultirootTests(); } - if (!opts.web) { setupDataLocalizationTests(); } - if (!opts.web) { setupLaunchTests(); }*/ - - runProfilerTests(); // {{SQL CARBON EDIT}} add our tests - runQueryEditorTests(); // {{SQL CARBON EDIT}} add our tests - } + /*if (!opts.web) { setupDataMigrationTests(opts['stable-build'], testDataPath); } + if (!opts.web) { setupDataLossTests(); } + if (!opts.web) { setupDataPreferencesTests(); } + setupDataSearchTests(); + setupDataEditorTests(); + setupDataStatusbarTests(!!opts.web); + if (!opts.web) { setupDataExtensionTests(); } + setupTerminalTests(); + if (!opts.web) { setupDataMultirootTests(); } + if (!opts.web) { setupDataLocalizationTests(); } + if (!opts.web) { setupLaunchTests(); }*/ + runProfilerTests(); // {{SQL CARBON EDIT}} add our tests + runQueryEditorTests(); // {{SQL CARBON EDIT}} add our tests }); diff --git a/test/unit/README.md b/test/unit/README.md index 4172285834..f471ebb7d8 100644 --- a/test/unit/README.md +++ b/test/unit/README.md @@ -9,6 +9,8 @@ All unit tests are run inside a electron-browser environment which access to DOM - use the `--debug` to see an electron window with dev tools which allows for debugging - to run only a subset of tests use the `--run` or `--glob` options +For instance, `./scripts/test.sh --debug --glob **/extHost*.test.js` runs all tests from `extHost`-files and enables you to debug them. + ## Run (inside browser) yarn test-browser --browser webkit --browser chromium @@ -24,11 +26,6 @@ Unit tests from layers `common` and `browser` are run inside `chromium`, `webkit yarn run mocha --run src/vs/editor/test/browser/controller/cursor.test.ts -## Debug - -To debug tests use `--debug` when running the test script. Also, the set of tests can be reduced with the `--run` and `--runGlob` flags. Both require a file path/pattern. Like so: - - ./scripts/test.sh --debug --runGrep **/extHost*.test.js ## Coverage diff --git a/yarn.lock b/yarn.lock index 88bf0b87ec..8b062cabd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9899,10 +9899,10 @@ vsce@1.48.0: yauzl "^2.3.1" yazl "^2.2.2" -vscode-debugprotocol@1.39.0-pre.0: - version "1.39.0-pre.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.39.0-pre.0.tgz#67843631a3c53f2d5282f75ab5996e1408c3958c" - integrity sha512-VpoD8m0gOo2Ag5dEpNT9sAI6BBxIyCxEk2dhGIBegxnlOuiB1SVxMgo1tmsvNYcRCpf9eng27kZ6d6FGoPpIAg== +vscode-debugprotocol@1.39.0: + version "1.39.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.39.0.tgz#0c639178d0d5ea7de7903b6478b53d2bc0d77461" + integrity sha512-Wkvgtuz90vjtQBcvw9Z+BYa4dA6W+sHwHMpqvJVNmwWSuT3JZdl0XDhZNLqtMXkVF4okxtAe0MmbupPSt+gnAQ== vscode-minimist@^1.2.2: version "1.2.2"