From 047c2218347e386ac8e047d8607e1d5adaf918fa Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Thu, 9 Jul 2020 13:40:02 -0700 Subject: [PATCH] Merge from vscode d5e9aa0227e057a60c82568bf31c04730dc15dcd (#11276) * Merge from vscode d5e9aa0227e057a60c82568bf31c04730dc15dcd * fix tests --- .vscode/launch.json | 1 + .vscode/notebooks/inbox.github-issues | 10 +- .vscode/notebooks/my-work.github-issues | 2 +- .vscode/notebooks/verification.github-issues | 55 ++++ .vscode/tasks.json | 4 +- build/gulpfile.vscode.js | 1 + build/npm/postinstall.js | 2 + build/package.json | 2 +- build/yarn.lock | 8 +- cgmanifest.json | 2 +- extensions/git/package.json | 2 +- extensions/git/src/api/extension.ts | 13 +- extensions/git/src/main.ts | 27 +- extensions/git/src/repository.ts | 2 +- extensions/git/src/util.ts | 12 - extensions/git/yarn.lock | 8 +- extensions/github-browser/package.json | 9 + extensions/github-browser/src/changeStore.ts | 40 +-- extensions/github-browser/src/contextStore.ts | 35 +- extensions/github-browser/src/extension.ts | 63 ++-- extensions/github-browser/src/fs.ts | 24 +- extensions/github-browser/src/github/api.ts | 91 +++--- extensions/github-browser/src/github/fs.ts | 4 +- extensions/github-browser/src/scm.ts | 23 +- extensions/github-browser/src/statusbar.ts | 99 ++++++ extensions/github/src/commands.ts | 7 +- extensions/github/src/extension.ts | 42 ++- extensions/github/src/publish.ts | 84 +++-- extensions/github/src/util.ts | 24 ++ extensions/package.json | 2 +- .../theme-defaults/themes/hc_black.json | 8 +- .../theme-defaults/themes/light_defaults.json | 2 +- extensions/yarn.lock | 8 +- package.json | 6 +- remote/package.json | 2 +- remote/web/package.json | 2 +- remote/web/yarn.lock | 8 +- remote/yarn.lock | 8 +- resources/serverless/code-web.js | 25 +- .../browser/connectionTreeActions.test.ts | 5 +- .../browser/ui/codicons/codicon/codicon.ttf | Bin 60140 -> 60600 bytes src/vs/base/browser/ui/list/listWidget.ts | 1 + .../browser/ui/scrollbar/scrollableElement.ts | 17 +- src/vs/base/browser/ui/tree/abstractTree.ts | 11 +- src/vs/base/common/codicons.ts | 2 + .../parts/contextmenu/common/contextmenu.ts | 3 +- .../electron-sandbox/contextmenu.ts | 6 +- .../parts/quickinput/browser/quickInput.ts | 9 +- src/vs/code/browser/workbench/workbench.ts | 3 + .../electron-browser/workbench/workbench.js | 2 + src/vs/editor/contrib/find/findController.ts | 41 ++- .../contrib/find/test/findController.test.ts | 164 +++++----- src/vs/editor/test/browser/testCodeEditor.ts | 18 ++ src/vs/loader.js | 6 + src/vs/platform/browser/checkbox.ts | 1 - .../test/common/configurationService.test.ts | 246 ++++++++++++++ .../test/node/configurationService.test.ts | 304 ------------------ .../browser/indexedDBFileSystemProvider.ts | 2 - src/vs/platform/files/common/fileService.ts | 18 +- .../diskFileSystemProvider.ts | 2 +- .../files/node/diskFileSystemProvider.ts | 15 +- src/vs/platform/list/browser/listService.ts | 65 ++-- .../platform/storage/node/storageService.ts | 2 +- .../common/userDataAutoSyncService.ts | 10 +- .../electron-main/webviewProtocolProvider.ts | 3 +- src/vs/vscode.d.ts | 8 +- src/vs/vscode.proposed.d.ts | 13 +- .../api/browser/mainThreadDebugService.ts | 2 +- .../api/browser/mainThreadStatusBar.ts | 4 +- .../workbench/api/browser/mainThreadTask.ts | 49 ++- .../workbench/api/common/extHost.protocol.ts | 4 +- .../api/common/extHostDebugService.ts | 2 +- .../workbench/api/common/extHostStatusBar.ts | 5 + .../workbench/api/common/extHostTreeViews.ts | 2 +- .../api/common/menusExtensionPoint.ts | 2 +- src/vs/workbench/api/node/extHostTask.ts | 5 +- src/vs/workbench/browser/contextkeys.ts | 2 +- .../workbench/browser/parts/compositeBar.ts | 2 +- .../parts/editor/breadcrumbsControl.ts | 14 +- .../browser/parts/editor/breadcrumbsModel.ts | 3 +- .../parts/editor/editor.contribution.ts | 1 - .../browser/parts/statusbar/statusbarPart.ts | 3 + .../browser/parts/views/viewsService.ts | 5 + src/vs/workbench/common/views.ts | 1 + .../customEditor/browser/customEditors.ts | 8 +- .../contrib/debug/browser/callStackView.ts | 27 +- .../contrib/debug/browser/debugService.ts | 5 +- .../contrib/debug/browser/debugSession.ts | 4 +- .../debug/browser/media/debugViewlet.css | 1 + .../workbench/contrib/debug/browser/repl.ts | 9 +- .../workbench/contrib/debug/common/debug.ts | 4 +- .../contrib/debug/test/common/mockDebug.ts | 2 +- .../extensions/browser/extensionEditor.ts | 13 +- .../browser/extensions.contribution.ts | 3 +- .../extensions/browser/extensionsActions.ts | 39 ++- .../browser/media/extensionEditor.css | 7 +- .../runtimeExtensionsEditor.ts | 7 +- .../files/browser/views/explorerView.ts | 2 +- .../contrib/notebook/browser/constants.ts | 15 +- .../notebook/browser/media/notebook.css | 62 +++- .../notebook/browser/notebookEditorInput.ts | 2 +- .../notebook/browser/notebookEditorWidget.ts | 67 ++-- .../view/renderers/backLayerWebView.ts | 8 +- .../browser/viewModel/codeCellViewModel.ts | 2 +- .../contrib/outline/browser/outlinePane.ts | 6 +- .../preferences/browser/settingsTree.ts | 43 ++- .../preferences/browser/settingsWidgets.ts | 11 +- .../contrib/scm/browser/media/scm.css | 47 ++- .../contrib/scm/browser/scm.contribution.ts | 11 + .../contrib/scm/browser/scmViewPane.ts | 207 +++++++++--- .../searchEditor/browser/searchEditorInput.ts | 4 +- .../searchEditor/browser/searchEditorModel.ts | 2 +- .../terminalExternalLinkProviderAdapter.ts | 9 +- .../browser/links/terminalLinkManager.ts | 2 +- .../contrib/terminal/node/terminalProcess.ts | 4 +- .../contrib/timeline/browser/timelinePane.ts | 6 + .../contrib/update/browser/update.ts | 8 +- .../userDataSync/browser/userDataSync.ts | 16 +- .../contrib/views/browser/treeView.ts | 8 +- .../webview/browser/webviewCommands.ts | 12 +- .../electron-browser/webview.contribution.ts | 9 +- .../electron-browser/webviewElement.ts | 11 + .../electron-sandbox/contextmenuService.ts | 3 +- .../services/editor/browser/editorService.ts | 6 +- .../services/editor/common/editorOpenWith.ts | 8 +- .../services/editor/common/editorService.ts | 4 +- .../workbench/services/hover/browser/hover.ts | 2 +- .../keybinding/common/keybindingEditing.ts | 28 +- .../keybindingEditing.test.ts | 10 + .../services/statusbar/common/statusbar.ts | 6 + .../test/node/encoding/encoding.test.ts | 11 + .../themes/browser/workbenchThemeService.ts | 7 - .../parts/editor/breadcrumbModel.test.ts | 19 +- .../test/browser/workbenchTestServices.ts | 1 + yarn.lock | 68 +++- 135 files changed, 1772 insertions(+), 954 deletions(-) create mode 100644 .vscode/notebooks/verification.github-issues create mode 100644 extensions/github-browser/src/statusbar.ts create mode 100644 extensions/github/src/util.ts create mode 100644 src/vs/platform/configuration/test/common/configurationService.test.ts delete mode 100644 src/vs/platform/configuration/test/node/configurationService.test.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 1a19bbf549..04efa7aa70 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -92,6 +92,7 @@ "env": { "VSCODE_EXTHOST_WILL_SEND_SOCKET": null }, + "cleanUp": "wholeBrowser", "breakOnLoad": false, "urlFilter": "*workbench.html*", "runtimeArgs": [ diff --git a/.vscode/notebooks/inbox.github-issues b/.vscode/notebooks/inbox.github-issues index 9edd342251..8931e7b0b3 100644 --- a/.vscode/notebooks/inbox.github-issues +++ b/.vscode/notebooks/inbox.github-issues @@ -2,12 +2,14 @@ { "kind": 1, "language": "markdown", - "value": "##### `Config`: defines the inbox query" + "value": "##### `Config`: defines the inbox query", + "editable": true }, { "kind": 2, "language": "github-issues", - "value": "$inbox=repo:microsoft/vscode is:open no:assignee -label:feature-request -label:testplan-item -label:plan-item " + "value": "$inbox=repo:microsoft/vscode is:open no:assignee -label:feature-request -label:testplan-item -label:plan-item ", + "editable": true }, { "kind": 1, @@ -18,7 +20,7 @@ { "kind": 2, "language": "github-issues", - "value": "$inbox -label:\"needs more info\"", + "value": "$inbox -label:\"needs more info\" -label:emmet", "editable": true }, { @@ -31,6 +33,6 @@ "kind": 2, "language": "github-issues", "value": "$inbox", - "editable": true + "editable": false } ] \ No newline at end of file diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index c6d2309b39..dc6d33365d 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -20,7 +20,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos $milestone assignee:@me is:open\n", + "value": "$repos $milestone assignee:@me is:open", "editable": false }, { diff --git a/.vscode/notebooks/verification.github-issues b/.vscode/notebooks/verification.github-issues new file mode 100644 index 0000000000..6f32df8e07 --- /dev/null +++ b/.vscode/notebooks/verification.github-issues @@ -0,0 +1,55 @@ +[ + { + "kind": 1, + "language": "markdown", + "value": "### Bug Verification Queries\n\nBefore shipping we want to verify _all_ bugs. That means when a bug is fixed we check that the fix actually works. It's always best to start with bugs that you have filed and the proceed with bugs that have been filed from users outside the development team. ", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "#### Config: update list of `repos` and the `milestone`", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks \n$milestone=milestone:\"June 2020\"", + "editable": true + }, + { + "kind": 1, + "language": "markdown", + "value": "### Bugs You Filed", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$repos $milestone is:closed -assignee:@me label:bug -label:verified -label:*duplicate author:@me", + "editable": false + }, + { + "kind": 1, + "language": "markdown", + "value": "### Bugs From Outside", + "editable": true + }, + { + "kind": 2, + "language": "github-issues", + "value": "$repos $milestone is:closed -assignee:@me label:bug -label:verified -label:*duplicate -author:@me -assignee:@me label:bug -label:verified -author:@me -author:aeschli -author:alexdima -author:alexr00 -author:bpasero -author:chrisdias -author:chrmarti -author:connor4312 -author:dbaeumer -author:deepak1556 -author:eamodio -author:egamma -author:gregvanl -author:isidorn -author:JacksonKearl -author:joaomoreno -author:jrieken -author:lramos15 -author:lszomoru -author:misolori -author:mjbvz -author:rebornix -author:RMacfarlane -author:roblourens -author:sana-ajani -author:sandy081 -author:sbatten -author:Tyriar -author:weinand", + "editable": false + }, + { + "kind": 1, + "language": "markdown", + "value": "### All" + }, + { + "kind": 2, + "language": "github-issues", + "value": "$repos $milestone is:closed -assignee:@me label:bug -label:verified -label:*duplicate", + "editable": false + } +] \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 076129b820..8df37c9323 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -26,8 +26,8 @@ "message": 3 }, "background": { - "beginsPattern": "Starting compilation", - "endsPattern": "Finished compilation" + "beginsPattern": "\\[watch-client\\].*Starting compilation", + "endsPattern": "\\[watch-client\\].*Finished compilation" } } }, diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 4c7e36bdc8..a02cd07bd0 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -75,6 +75,7 @@ const vscodeResources = [ 'out-build/paths.js', 'out-build/vs/**/*.{svg,png,html}', '!out-build/vs/code/browser/**/*.html', + '!out-build/vs/editor/standalone/**/*.svg', 'out-build/vs/base/common/performance.js', 'out-build/vs/base/node/languagePacks.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index 9d2734da6d..cfa6aefa5f 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -73,3 +73,5 @@ yarnInstall('test/automation'); // node modules required for smoketest yarnInstall('test/smoke'); // node modules required for smoketest yarnInstall('test/integration/browser'); // node modules required for integration yarnInstallBuildDependencies(); // node modules for watching, specific to host node version, not electron + +cp.execSync('git config pull.rebase true'); diff --git a/build/package.json b/build/package.json index 09863d4011..da106e9fda 100644 --- a/build/package.json +++ b/build/package.json @@ -40,7 +40,7 @@ "gulp-bom": "^1.0.0", "gulp-sourcemaps": "^1.11.0", "gulp-uglify": "^3.0.0", - "iconv-lite-umd": "0.6.5", + "iconv-lite-umd": "0.6.7", "mime": "^1.3.4", "minimatch": "3.0.4", "minimist": "^1.2.3", diff --git a/build/yarn.lock b/build/yarn.lock index 61f64b4f43..a97c0ab780 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -1817,10 +1817,10 @@ https-proxy-agent@^4.0.0: agent-base "5" debug "4" -iconv-lite-umd@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722" - integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg== +iconv-lite-umd@0.6.7: + version "0.6.7" + resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.7.tgz#ee437e34b30f15dc00ec93ea65065e672770777c" + integrity sha512-DT90zb7wL1B3I6DmYUMcfJeVdY19XigzDj5AtXbXEw9Jfi0+AVAxfn7ytvY7Xhr+GFn7nd7hPonapC37oo7iAQ== iconv-lite@^0.4.4: version "0.4.24" diff --git a/cgmanifest.json b/cgmanifest.json index 25178f2f20..cb9954628d 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -533,7 +533,7 @@ "git": { "name": "ripgrep", "repositoryUrl": "https://github.com/BurntSushi/ripgrep", - "commitHash": "8a7db1a918e969b85cd933d8ed9fa5285b281ba4" + "commitHash": "973de50c9ef451da2cfcdfa86f2b2711d8d6ff48" } }, "isOnlyProductionDependency": true, diff --git a/extensions/git/package.json b/extensions/git/package.json index e5e78fd3d8..de68bee7fa 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1878,7 +1878,7 @@ "dependencies": { "byline": "^5.0.0", "file-type": "^7.2.0", - "iconv-lite-umd": "0.6.5", + "iconv-lite-umd": "0.6.7", "jschardet": "2.1.1", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0", diff --git a/extensions/git/src/api/extension.ts b/extensions/git/src/api/extension.ts index b74925145e..61089fc582 100644 --- a/extensions/git/src/api/extension.ts +++ b/extensions/git/src/api/extension.ts @@ -7,7 +7,6 @@ import { Model } from '../model'; import { GitExtension, Repository, API } from './git'; import { ApiRepository, ApiImpl } from './api1'; import { Event, EventEmitter } from 'vscode'; -import { latchEvent } from '../util'; export function deprecated(_target: any, key: string, descriptor: any): void { if (typeof descriptor.value !== 'function') { @@ -26,14 +25,20 @@ export class GitExtensionImpl implements GitExtension { enabled: boolean = false; private _onDidChangeEnablement = new EventEmitter(); - readonly onDidChangeEnablement: Event = latchEvent(this._onDidChangeEnablement.event); + readonly onDidChangeEnablement: Event = this._onDidChangeEnablement.event; private _model: Model | undefined = undefined; set model(model: Model | undefined) { this._model = model; - this.enabled = !!model; + const enabled = !!model; + + if (this.enabled === enabled) { + return; + } + + this.enabled = enabled; this._onDidChangeEnablement.fire(this.enabled); } @@ -73,4 +78,4 @@ export class GitExtensionImpl implements GitExtension { return new ApiImpl(this._model); } -} \ No newline at end of file +} diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 456b485cb7..6756a14248 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -176,8 +176,7 @@ export async function activate(context: ExtensionContext): Promise return result; } -// @ts-expect-error -async function checkGitVersion(info: IGit): Promise { +async function checkGitv1(info: IGit): Promise { const config = workspace.getConfiguration('git'); const shouldIgnore = config.get('ignoreLegacyWarning') === true; @@ -204,3 +203,27 @@ async function checkGitVersion(info: IGit): Promise { await config.update('ignoreLegacyWarning', true, true); } } + +async function checkGitWindows(info: IGit): Promise { + if (!/^2\.(25|26)\./.test(info.version)) { + return; + } + + const update = localize('updateGit', "Update Git"); + const choice = await window.showWarningMessage( + localize('git2526', "There are known issues with the installed Git {0}. Please update to Git >= 2.27 for the git features to work correctly.", info.version), + update + ); + + if (choice === update) { + commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/')); + } +} +// @ts-expect-error +async function checkGitVersion(info: IGit): Promise { + await checkGitv1(info); + + if (process.platform === 'win32') { + await checkGitWindows(info); + } +} diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index ea3feabd01..dfd0983706 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -865,7 +865,7 @@ export class Repository implements Disposable { } async getInputTemplate(): Promise { - const commitMessage = (await Promise.all([this.repository.getMergeMessage(), this.repository.getSquashMessage()])).find(msg => msg !== undefined); + const commitMessage = (await Promise.all([this.repository.getMergeMessage(), this.repository.getSquashMessage()])).find(msg => !!msg); if (commitMessage) { return commitMessage; diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 221f2a65a2..23e8f9f8b7 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -44,18 +44,6 @@ export function filterEvent(event: Event, filter: (e: T) => boolean): Even return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); } -export function latchEvent(event: Event): Event { - let firstCall = true; - let cache: T; - - return filterEvent(event, value => { - let shouldEmit = firstCall || value !== cache; - firstCall = false; - cache = value; - return shouldEmit; - }); -} - export function anyEvent(...events: Event[]): Event { return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => { const result = combinedDisposable(events.map(event => event(i => listener.call(thisArgs, i)))); diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 35277c4713..ee3e3f6df1 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -425,10 +425,10 @@ https-proxy-agent@^2.2.1: agent-base "^4.3.0" debug "^3.1.0" -iconv-lite-umd@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722" - integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg== +iconv-lite-umd@0.6.7: + version "0.6.7" + resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.7.tgz#ee437e34b30f15dc00ec93ea65065e672770777c" + integrity sha512-DT90zb7wL1B3I6DmYUMcfJeVdY19XigzDj5AtXbXEw9Jfi0+AVAxfn7ytvY7Xhr+GFn7nd7hPonapC37oo7iAQ== inflight@^1.0.4: version "1.0.6" diff --git a/extensions/github-browser/package.json b/extensions/github-browser/package.json index 6e3c071b70..9938e25ed5 100644 --- a/extensions/github-browser/package.json +++ b/extensions/github-browser/package.json @@ -21,6 +21,11 @@ "main": "./out/extension.js", "contributes": { "commands": [ + { + "command": "githubBrowser.openRepository", + "title": "Open GitHub Repository...", + "category": "GitHub Browser" + }, { "command": "githubBrowser.commit", "title": "Commit", @@ -48,6 +53,10 @@ ], "menus": { "commandPalette": [ + { + "command": "githubBrowser.openRepository", + "when": "config.githubBrowser.openRepository" + }, { "command": "githubBrowser.commit", "when": "false" diff --git a/extensions/github-browser/src/changeStore.ts b/extensions/github-browser/src/changeStore.ts index 26ba7f1071..516eee7c38 100644 --- a/extensions/github-browser/src/changeStore.ts +++ b/extensions/github-browser/src/changeStore.ts @@ -47,31 +47,17 @@ function fromSerialized(operations: StoredOperation): Operation { return { ...operations, uri: Uri.parse(operations.uri) }; } -interface CreatedFileChangeStoreEvent { - type: 'created'; +export interface ChangeStoreEvent { + type: 'created' | 'changed' | 'deleted'; rootUri: Uri; uri: Uri; } -interface ChangedFileChangeStoreEvent { - type: 'changed'; - rootUri: Uri; - uri: Uri; -} - -interface DeletedFileChangeStoreEvent { - type: 'deleted'; - rootUri: Uri; - uri: Uri; -} - -type ChangeStoreEvent = CreatedFileChangeStoreEvent | ChangedFileChangeStoreEvent | DeletedFileChangeStoreEvent; - function toChangeStoreEvent(operation: Operation | StoredOperation, rootUri: Uri, uri?: Uri): ChangeStoreEvent { return { type: operation.type, rootUri: rootUri, - uri: uri ?? (typeof operation.uri === 'string' ? Uri.parse(operation.uri) : operation.uri) + uri: uri ?? (typeof operation.uri === 'string' ? Uri.parse(operation.uri) : operation.uri), }; } @@ -82,6 +68,8 @@ export interface IChangeStore { discard(uri: Uri): Promise; discardAll(rootUri: Uri): Promise; + hasChanges(rootUri: Uri): boolean; + getChanges(rootUri: Uri): Operation[]; getContent(uri: Uri): string | undefined; @@ -116,9 +104,15 @@ export class ChangeStore implements IChangeStore, IWritableChangeStore { await this.saveWorkingOperations(rootUri, undefined); + const events: ChangeStoreEvent[] = []; + for (const operation of operations) { await this.discardWorkingContent(operation.uri); - this._onDidChange.fire(toChangeStoreEvent(operation, rootUri)); + events.push(toChangeStoreEvent(operation, rootUri)); + } + + for (const e of events) { + this._onDidChange.fire(e); } } @@ -143,7 +137,7 @@ export class ChangeStore implements IChangeStore, IWritableChangeStore { this._onDidChange.fire({ type: operation.type === 'created' ? 'deleted' : operation.type === 'deleted' ? 'created' : 'changed', rootUri: rootUri, - uri: uri + uri: uri, }); } @@ -152,9 +146,15 @@ export class ChangeStore implements IChangeStore, IWritableChangeStore { await this.saveWorkingOperations(rootUri, undefined); + const events: ChangeStoreEvent[] = []; + for (const operation of operations) { await this.discardWorkingContent(operation.uri); - this._onDidChange.fire(toChangeStoreEvent(operation, rootUri)); + events.push(toChangeStoreEvent(operation, rootUri)); + } + + for (const e of events) { + this._onDidChange.fire(e); } } diff --git a/extensions/github-browser/src/contextStore.ts b/extensions/github-browser/src/contextStore.ts index f0f418da36..f53d635dd2 100644 --- a/extensions/github-browser/src/contextStore.ts +++ b/extensions/github-browser/src/contextStore.ts @@ -4,9 +4,13 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { Event, EventEmitter, Memento, Uri } from 'vscode'; +import { Event, EventEmitter, Memento, Uri, workspace } from 'vscode'; -export const contextKeyPrefix = 'github.context|'; +export interface WorkspaceFolderContext { + context: T; + name: string; + folderUri: Uri; +} export class ContextStore { private _onDidChange = new EventEmitter(); @@ -14,23 +18,36 @@ export class ContextStore { return this._onDidChange.event; } - constructor(private readonly memento: Memento, private readonly scheme: string) { } + constructor( + private readonly scheme: string, + private readonly originalScheme: string, + private readonly memento: Memento, + ) { } delete(uri: Uri) { return this.set(uri, undefined); } get(uri: Uri): T | undefined { - return this.memento.get(`${contextKeyPrefix}${uri.toString()}`); + return this.memento.get(`${this.originalScheme}.context|${this.getOriginalResource(uri).toString()}`); } + getForWorkspace(): WorkspaceFolderContext[] { + const folders = workspace.workspaceFolders?.filter(f => f.uri.scheme === this.scheme || f.uri.scheme === this.originalScheme) ?? []; + return folders.map(f => ({ context: this.get(f.uri)!, name: f.name, folderUri: f.uri })).filter(c => c.context !== undefined); + } async set(uri: Uri, context: T | undefined) { - if (uri.scheme !== this.scheme) { - throw new Error(`Invalid context scheme: ${uri.scheme}`); - } - - await this.memento.update(`${contextKeyPrefix}${uri.toString()}`, context); + uri = this.getOriginalResource(uri); + await this.memento.update(`${this.originalScheme}.context|${uri.toString()}`, context); this._onDidChange.fire(uri); } + + getOriginalResource(uri: Uri): Uri { + return uri.with({ scheme: this.originalScheme }); + } + + getWorkspaceResource(uri: Uri): Uri { + return uri.with({ scheme: this.scheme }); + } } diff --git a/extensions/github-browser/src/extension.ts b/extensions/github-browser/src/extension.ts index d0df7d647a..d0f551b0c7 100644 --- a/extensions/github-browser/src/extension.ts +++ b/extensions/github-browser/src/extension.ts @@ -3,48 +3,50 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext, Uri, workspace } from 'vscode'; +import { commands, ExtensionContext, Uri, window, workspace } from 'vscode'; import { ChangeStore } from './changeStore'; import { ContextStore } from './contextStore'; import { VirtualFS } from './fs'; import { GitHubApiContext, GitHubApi } from './github/api'; import { GitHubFS } from './github/fs'; import { VirtualSCM } from './scm'; +import { StatusBar } from './statusbar'; -// const repositoryRegex = /^(?:(?:https:\/\/)?github.com\/)?([^\/]+)\/([^\/]+?)(?:\/|.git|$)/i; +const repositoryRegex = /^(?:(?:https:\/\/)?github.com\/)?([^\/]+)\/([^\/]+?)(?:\/|.git|$)/i; -export function activate(context: ExtensionContext) { - const contextStore = new ContextStore(context.workspaceState, GitHubFS.scheme); +export async function activate(context: ExtensionContext) { + const contextStore = new ContextStore('codespace', GitHubFS.scheme, context.workspaceState); const changeStore = new ChangeStore(context.workspaceState); const githubApi = new GitHubApi(contextStore); const gitHubFS = new GitHubFS(githubApi); - const virtualFS = new VirtualFS('codespace', GitHubFS.scheme, contextStore, changeStore, gitHubFS); + const virtualFS = new VirtualFS('codespace', contextStore, changeStore, gitHubFS); context.subscriptions.push( githubApi, gitHubFS, virtualFS, - new VirtualSCM(GitHubFS.scheme, githubApi, changeStore) + new VirtualSCM(GitHubFS.scheme, githubApi, changeStore), + new StatusBar(contextStore, changeStore), ); - // commands.registerCommand('githubBrowser.openRepository', async () => { - // const value = await window.showInputBox({ - // placeHolder: 'e.g. https://github.com/microsoft/vscode', - // prompt: 'Enter a GitHub repository url', - // validateInput: value => repositoryRegex.test(value) ? undefined : 'Invalid repository url' - // }); + commands.registerCommand('githubBrowser.openRepository', async () => { + const value = await window.showInputBox({ + placeHolder: 'e.g. https://github.com/microsoft/vscode', + prompt: 'Enter a GitHub repository url', + validateInput: value => repositoryRegex.test(value) ? undefined : 'Invalid repository url' + }); - // if (value) { - // const match = repositoryRegex.exec(value); - // if (match) { - // const [, owner, repo] = match; + if (value) { + const match = repositoryRegex.exec(value); + if (match) { + const [, owner, repo] = match; - // const uri = Uri.parse(`codespace://HEAD/${owner}/${repo}`); - // openWorkspace(uri, repo, 'currentWindow'); - // } - // } - // }); + const uri = Uri.parse(`codespace://HEAD/${owner}/${repo}`); + openWorkspace(uri, repo, 'currentWindow'); + } + } + }); } export function getRelativePath(rootUri: Uri, uri: Uri) { @@ -63,11 +65,16 @@ export function isDescendent(folderPath: string, filePath: string) { return folderPath.length === 0 || filePath.startsWith(folderPath.endsWith('/') ? folderPath : `${folderPath}/`); } -// function openWorkspace(uri: Uri, name: string, location: 'currentWindow' | 'newWindow' | 'addToCurrentWorkspace') { -// if (location === 'addToCurrentWorkspace') { -// const count = (workspace.workspaceFolders && workspace.workspaceFolders.length) || 0; -// return workspace.updateWorkspaceFolders(count, 0, { uri: uri, name: name }); -// } +const shaRegex = /^[0-9a-f]{40}$/; +export function isSha(ref: string) { + return shaRegex.test(ref); +} -// return commands.executeCommand('vscode.openFolder', uri, location === 'newWindow'); -// } +function openWorkspace(uri: Uri, name: string, location: 'currentWindow' | 'newWindow' | 'addToCurrentWorkspace') { + if (location === 'addToCurrentWorkspace') { + const count = (workspace.workspaceFolders && workspace.workspaceFolders.length) || 0; + return workspace.updateWorkspaceFolders(count, 0, { uri: uri, name: name }); + } + + return commands.executeCommand('vscode.openFolder', uri, location === 'newWindow'); +} diff --git a/extensions/github-browser/src/fs.ts b/extensions/github-browser/src/fs.ts index cb163c85b2..0a0ca250ee 100644 --- a/extensions/github-browser/src/fs.ts +++ b/extensions/github-browser/src/fs.ts @@ -43,26 +43,22 @@ export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSe constructor( readonly scheme: string, - private readonly originalScheme: string, - contextStore: ContextStore, + private readonly contextStore: ContextStore, private readonly changeStore: IWritableChangeStore, private readonly fs: FileSystemProvider & FileSearchProvider & TextSearchProvider ) { // TODO@eamodio listen for workspace folder changes - for (const folder of workspace.workspaceFolders ?? []) { - const uri = this.getOriginalResource(folder.uri); - + for (const context of contextStore.getForWorkspace()) { // If we have a saved context, but no longer have any changes, reset the context // We only do this on startup/reload to keep things consistent - if (contextStore.get(uri) !== undefined && !changeStore.hasChanges(folder.uri)) { - contextStore.delete(uri); + if (!changeStore.hasChanges(context.folderUri)) { + console.log('Clear context', context.folderUri.toString()); + contextStore.delete(context.folderUri); } } this.disposable = Disposable.from( - workspace.registerFileSystemProvider(scheme, this, { - isCaseSensitive: true, - }), + workspace.registerFileSystemProvider(scheme, this, { isCaseSensitive: true }), workspace.registerFileSearchProvider(scheme, this), workspace.registerTextSearchProvider(scheme, this), changeStore.onDidChange(e => { @@ -86,11 +82,11 @@ export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSe } private getOriginalResource(uri: Uri): Uri { - return uri.with({ scheme: this.originalScheme }); + return this.contextStore.getOriginalResource(uri); } - private getVirtualResource(uri: Uri): Uri { - return uri.with({ scheme: this.scheme }); + private getWorkspaceResource(uri: Uri): Uri { + return this.contextStore.getWorkspaceResource(uri); } //#region FileSystemProvider @@ -211,7 +207,7 @@ export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSe return this.fs.provideTextSearchResults( query, { ...options, folder: this.getOriginalResource(options.folder) }, - { report: (result: TextSearchResult) => progress.report({ ...result, uri: this.getVirtualResource(result.uri) }) }, + { report: (result: TextSearchResult) => progress.report({ ...result, uri: this.getWorkspaceResource(result.uri) }) }, token ); } diff --git a/extensions/github-browser/src/github/api.ts b/extensions/github-browser/src/github/api.ts index 7fffb4a6b7..fd5f032041 100644 --- a/extensions/github-browser/src/github/api.ts +++ b/extensions/github-browser/src/github/api.ts @@ -6,14 +6,16 @@ import { authentication, AuthenticationSession, Disposable, Event, EventEmitter, Range, Uri } from 'vscode'; import { graphql } from '@octokit/graphql'; import { Octokit } from '@octokit/rest'; -import { fromGitHubUri } from './fs'; import { ContextStore } from '../contextStore'; +import { fromGitHubUri } from './fs'; +import { isSha } from '../extension'; import { Iterables } from '../iterables'; -export const shaRegex = /^[0-9a-f]{40}$/; - export interface GitHubApiContext { - sha: string; + requestRef: string; + + branch: string; + sha: string | undefined; timestamp: number; } @@ -110,19 +112,12 @@ export class GitHubApi implements Disposable { } async commit(rootUri: Uri, message: string, operations: CommitOperation[]): Promise { - let { owner, repo, ref } = fromGitHubUri(rootUri); + const { owner, repo } = fromGitHubUri(rootUri); try { - if (ref === undefined || ref === 'HEAD') { - ref = await this.defaultBranchQuery(rootUri); - if (ref === undefined) { - throw new Error('Cannot commit — invalid ref'); - } - } - const context = await this.getContext(rootUri); if (context.sha === undefined) { - throw new Error('Cannot commit — invalid context'); + throw new Error(`Cannot commit to Uri(${rootUri.toString(true)}); Invalid context sha`); } const hasDeletes = operations.some(op => op.type === 'deleted'); @@ -204,14 +199,14 @@ export class GitHubApi implements Disposable { parents: [context.sha] }); - this.updateContext(rootUri, { sha: resp.data.sha, timestamp: Date.now() }); + this.updateContext(rootUri, { ...context, sha: resp.data.sha, timestamp: Date.now() }); // TODO@eamodio need to send a file change for any open files await github.git.updateRef({ owner: owner, repo: repo, - ref: `heads/${ref}`, + ref: `heads/${context.branch}`, sha: resp.data.sha }); @@ -256,7 +251,7 @@ export class GitHubApi implements Disposable { owner: owner, repo: repo, recursive: '1', - tree_sha: context?.sha ?? ref ?? 'HEAD', + tree_sha: context?.sha ?? ref, }); return Iterables.filterMap(resp.data.tree, p => p.type === 'blob' ? p.path : undefined); } catch (ex) { @@ -283,7 +278,7 @@ export class GitHubApi implements Disposable { }>(query, { owner: owner, repo: repo, - path: `${context.sha ?? ref ?? 'HEAD'}:${path}`, + path: `${context.sha ?? ref}:${path}`, }); return rsp?.repository?.object ?? undefined; } catch (ex) { @@ -295,7 +290,7 @@ export class GitHubApi implements Disposable { const { owner, repo, ref } = fromGitHubUri(uri); try { - if (ref === undefined || ref === 'HEAD') { + if (ref === 'HEAD') { const query = `query latest($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { defaultBranchRef { @@ -322,6 +317,7 @@ export class GitHubApi implements Disposable { oid } } + } }`; const rsp = await this.gqlQuery<{ @@ -345,7 +341,7 @@ export class GitHubApi implements Disposable { const { owner, repo, ref } = fromGitHubUri(uri); // If we have a specific ref, don't try to search, because GitHub search only works against the default branch - if (ref === undefined) { + if (ref !== 'HEAD') { return { matches: [], limitHit: true }; } @@ -436,29 +432,46 @@ export class GitHubApi implements Disposable { private readonly rootUriToContextMap = new Map(); private async getContextCore(rootUri: Uri): Promise { - let context = this.rootUriToContextMap.get(rootUri.toString()); - if (context === undefined) { - const { ref } = fromGitHubUri(rootUri); - if (ref !== undefined && shaRegex.test(ref)) { - context = { sha: ref, timestamp: Date.now() }; - } else { - context = this.context.get(rootUri); - if (context?.sha === undefined) { - const sha = await this.latestCommitQuery(rootUri); - if (sha !== undefined) { - context = { sha: sha, timestamp: Date.now() }; - } else { - context = undefined; - } - } - } + const key = rootUri.toString(); + let context = this.rootUriToContextMap.get(key); - if (context !== undefined) { - this.updateContext(rootUri, context); - } + // Check if we have a cached a context + if (context?.sha !== undefined) { + return context; } - return context ?? { sha: rootUri.authority, timestamp: Date.now() }; + // Check if we have a saved context + context = this.context.get(rootUri); + if (context?.sha !== undefined) { + this.rootUriToContextMap.set(key, context); + + return context; + } + + const { ref } = fromGitHubUri(rootUri); + + // If the requested ref looks like a sha, then use it + if (isSha(ref)) { + context = { requestRef: ref, branch: ref, sha: ref, timestamp: Date.now() }; + } else { + let branch; + if (ref === 'HEAD') { + branch = await this.defaultBranchQuery(rootUri); + if (branch === undefined) { + throw new Error(`Cannot get context for Uri(${rootUri.toString(true)}); unable to get default branch`); + } + } else { + branch = ref; + } + + // Query for the latest sha for the give ref + const sha = await this.latestCommitQuery(rootUri); + context = { requestRef: ref, branch: branch, sha: sha, timestamp: Date.now() }; + } + + this.updateContext(rootUri, context); + + return context; } private updateContext(rootUri: Uri, context: GitHubApiContext) { diff --git a/extensions/github-browser/src/github/fs.ts b/extensions/github-browser/src/github/fs.ts index 9149a69b3a..389f3cc00e 100644 --- a/extensions/github-browser/src/github/fs.ts +++ b/extensions/github-browser/src/github/fs.ts @@ -299,7 +299,7 @@ function typenameToFileType(typename: string | undefined | null) { } } -type RepoInfo = { owner: string; repo: string; path: string | undefined; ref?: string }; +type RepoInfo = { owner: string; repo: string; path: string | undefined; ref: string }; export function fromGitHubUri(uri: Uri): RepoInfo { const [, owner, repo, ...rest] = uri.path.split('/'); @@ -311,7 +311,7 @@ export function fromGitHubUri(uri: Uri): RepoInfo { ref = 'HEAD'; } } - return { owner: owner, repo: repo, path: rest.join('/'), ref: ref }; + return { owner: owner, repo: repo, path: rest.join('/'), ref: ref ?? 'HEAD' }; } function getHashCode(s: string): number { diff --git a/extensions/github-browser/src/scm.ts b/extensions/github-browser/src/scm.ts index f1e039dc2c..ce0519d935 100644 --- a/extensions/github-browser/src/scm.ts +++ b/extensions/github-browser/src/scm.ts @@ -32,17 +32,15 @@ export class VirtualSCM implements Disposable { // TODO@eamodio listen for workspace folder changes for (const folder of workspace.workspaceFolders ?? []) { this.createScmProvider(folder.uri, folder.name); + + for (const operation of changeStore.getChanges(folder.uri)) { + this.update(folder.uri, operation.uri); + } } this.disposable = Disposable.from( changeStore.onDidChange(e => this.update(e.rootUri, e.uri)), ); - - for (const { uri } of workspace.workspaceFolders ?? []) { - for (const operation of changeStore.getChanges(uri)) { - this.update(uri, operation.uri); - } - } } dispose() { @@ -50,7 +48,18 @@ export class VirtualSCM implements Disposable { } private registerCommands() { - commands.registerCommand('githubBrowser.commit', (...args: any[]) => this.commitChanges(args[0])); + commands.registerCommand('githubBrowser.commit', (sourceControl: SourceControl | undefined) => { + // TODO@eamodio remove this hack once I figure out why the args are missing + if (sourceControl === undefined && this.providers.length === 1) { + sourceControl = this.providers[0].sourceControl; + } + + if (sourceControl === undefined) { + return; + } + + this.commitChanges(sourceControl); + }); commands.registerCommand('githubBrowser.discardChanges', (resourceState: SourceControlResourceState) => this.discardChanges(resourceState.resourceUri) diff --git a/extensions/github-browser/src/statusbar.ts b/extensions/github-browser/src/statusbar.ts new file mode 100644 index 0000000000..d949a95f7c --- /dev/null +++ b/extensions/github-browser/src/statusbar.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import { Disposable, StatusBarAlignment, StatusBarItem, Uri, window, workspace } from 'vscode'; +import { ChangeStoreEvent, IChangeStore } from './changeStore'; +import { GitHubApiContext } from './github/api'; +import { isSha } from './extension'; +import { ContextStore, WorkspaceFolderContext } from './contextStore'; + +export class StatusBar implements Disposable { + private readonly disposable: Disposable; + + private readonly items = new Map(); + + constructor( + private readonly contextStore: ContextStore, + private readonly changeStore: IChangeStore + ) { + this.disposable = Disposable.from( + contextStore.onDidChange(this.onContextsChanged, this), + changeStore.onDidChange(this.onChanged, this) + ); + + for (const context of this.contextStore.getForWorkspace()) { + this.createOrUpdateStatusBarItem(context); + } + } + + dispose() { + this.disposable?.dispose(); + this.items.forEach(i => i.dispose()); + } + + private createOrUpdateStatusBarItem(wc: WorkspaceFolderContext) { + let item = this.items.get(wc.folderUri.toString()); + if (item === undefined) { + item = window.createStatusBarItem({ + id: `githubBrowser.branch:${wc.folderUri.toString()}`, + name: `GitHub Browser: ${wc.name}`, + alignment: StatusBarAlignment.Left, + priority: 1000 + }); + } + + if (isSha(wc.context.branch)) { + item.text = `$(git-commit) ${wc.context.branch.substr(0, 8)}`; + item.tooltip = `${wc.name} \u2022 ${wc.context.branch.substr(0, 8)}`; + } else { + item.text = `$(git-branch) ${wc.context.branch}`; + item.tooltip = `${wc.name} \u2022 ${wc.context.branch}${wc.context.sha ? ` @ ${wc.context.sha?.substr(0, 8)}` : ''}`; + } + + const hasChanges = this.changeStore.hasChanges(wc.folderUri); + if (hasChanges) { + item.text += '*'; + } + + item.show(); + + this.items.set(wc.folderUri.toString(), item); + } + + private onContextsChanged(uri: Uri) { + const folder = workspace.getWorkspaceFolder(this.contextStore.getWorkspaceResource(uri)); + if (folder === undefined) { + return; + } + + const context = this.contextStore.get(uri); + if (context === undefined) { + return; + } + + this.createOrUpdateStatusBarItem({ + context: context, + name: folder.name, + folderUri: folder.uri, + }); + } + + private onChanged(e: ChangeStoreEvent) { + const item = this.items.get(e.rootUri.toString()); + if (item !== undefined) { + const hasChanges = this.changeStore.hasChanges(e.rootUri); + if (hasChanges) { + if (!item.text.endsWith('*')) { + item.text += '*'; + } + } else { + if (item.text.endsWith('*')) { + item.text = item.text.substr(0, item.text.length - 1); + } + } + } + } +} diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts index 1a3fd851c0..1baf1a85d3 100644 --- a/extensions/github/src/commands.ts +++ b/extensions/github/src/commands.ts @@ -6,9 +6,10 @@ import * as vscode from 'vscode'; import { API as GitAPI } from './typings/git'; import { publishRepository } from './publish'; +import { combinedDisposable } from './util'; -export function registerCommands(gitAPI: GitAPI): vscode.Disposable[] { - const disposables = []; +export function registerCommands(gitAPI: GitAPI): vscode.Disposable { + const disposables: vscode.Disposable[] = []; disposables.push(vscode.commands.registerCommand('github.publish', async () => { try { @@ -18,5 +19,5 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable[] { } })); - return disposables; + return combinedDisposable(disposables); } diff --git a/extensions/github/src/extension.ts b/extensions/github/src/extension.ts index ae43f9177f..def3f48dc5 100644 --- a/extensions/github/src/extension.ts +++ b/extensions/github/src/extension.ts @@ -3,23 +3,41 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; +import { Disposable, ExtensionContext, extensions } from 'vscode'; import { GithubRemoteSourceProvider } from './remoteSourceProvider'; import { GitExtension } from './typings/git'; import { registerCommands } from './commands'; import { GithubCredentialProviderManager } from './credentialProvider'; +import { dispose, combinedDisposable } from './util'; -export async function activate(context: vscode.ExtensionContext) { - const gitExtension = vscode.extensions.getExtension('vscode.git')!.exports; +export function activate(context: ExtensionContext): void { + const disposables = new Set(); + context.subscriptions.push(combinedDisposable(disposables)); - try { - const gitAPI = gitExtension.getAPI(1); + const init = () => { + try { + const gitAPI = gitExtension.getAPI(1); - context.subscriptions.push(...registerCommands(gitAPI)); - context.subscriptions.push(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider(gitAPI))); - context.subscriptions.push(new GithubCredentialProviderManager(gitAPI)); - } catch (err) { - console.error('Could not initialize GitHub extension'); - console.warn(err); - } + disposables.add(registerCommands(gitAPI)); + disposables.add(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider(gitAPI))); + disposables.add(new GithubCredentialProviderManager(gitAPI)); + } catch (err) { + console.error('Could not initialize GitHub extension'); + console.warn(err); + } + }; + + const onDidChangeGitExtensionEnablement = (enabled: boolean) => { + if (!enabled) { + dispose(disposables); + disposables.clear(); + } else { + init(); + } + }; + + + const gitExtension = extensions.getExtension('vscode.git')!.exports; + context.subscriptions.push(gitExtension.onDidChangeEnablement(onDidChangeGitExtensionEnablement)); + onDidChangeGitExtensionEnablement(gitExtension.enabled); } diff --git a/extensions/github/src/publish.ts b/extensions/github/src/publish.ts index fdb3bd90e9..d9ee2cff3b 100644 --- a/extensions/github/src/publish.ts +++ b/extensions/github/src/publish.ts @@ -5,10 +5,10 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; -import * as path from 'path'; -import { promises as fs } from 'fs'; import { API as GitAPI, Repository } from './typings/git'; import { getOctokit } from './auth'; +import { TextEncoder } from 'util'; +import { basename } from 'path'; const localize = nls.loadMessageBundle(); @@ -28,10 +28,12 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) return; } - let folder: vscode.WorkspaceFolder; + let folder: vscode.Uri; - if (vscode.workspace.workspaceFolders.length === 1) { - folder = vscode.workspace.workspaceFolders[0]; + if (repository) { + folder = repository.rootUri; + } else if (vscode.workspace.workspaceFolders.length === 1) { + folder = vscode.workspace.workspaceFolders[0].uri; } else { const picks = vscode.workspace.workspaceFolders.map(folder => ({ label: folder.name, folder })); const placeHolder = localize('pick folder', "Pick a folder to publish to GitHub"); @@ -41,14 +43,14 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) return; } - folder = pick.folder; + folder = pick.folder.uri; } let quickpick = vscode.window.createQuickPick(); quickpick.ignoreFocusOut = true; quickpick.placeholder = 'Repository Name'; - quickpick.value = folder.name; + quickpick.value = basename(folder.fsPath); quickpick.show(); quickpick.busy = true; @@ -97,37 +99,49 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) return; } - quickpick = vscode.window.createQuickPick(); - quickpick.placeholder = localize('ignore', "Select which files should be included in the repository."); - quickpick.canSelectMany = true; - quickpick.show(); + if (!repository) { + const gitignore = vscode.Uri.joinPath(folder, '.gitignore'); + let shouldGenerateGitignore = false; - try { - quickpick.busy = true; - - const repositoryPath = folder.uri.fsPath; - const currentPath = path.join(repositoryPath); - const children = await fs.readdir(currentPath); - quickpick.items = children.map(name => ({ label: name })); - quickpick.selectedItems = quickpick.items; - quickpick.busy = false; - - const result = await Promise.race([ - new Promise(c => quickpick.onDidAccept(() => c(quickpick.selectedItems))), - new Promise(c => quickpick.onDidHide(() => c(undefined))) - ]); - - if (!result) { - return; + try { + await vscode.workspace.fs.stat(gitignore); + } catch (err) { + shouldGenerateGitignore = true; } - const ignored = new Set(children); - result.forEach(c => ignored.delete(c.label)); + if (shouldGenerateGitignore) { + quickpick = vscode.window.createQuickPick(); + quickpick.placeholder = localize('ignore', "Select which files should be included in the repository."); + quickpick.canSelectMany = true; + quickpick.show(); - const raw = [...ignored].map(i => `/${i}`).join('\n'); - await fs.writeFile(path.join(repositoryPath, '.gitignore'), raw, 'utf8'); - } finally { - quickpick.dispose(); + try { + quickpick.busy = true; + + const children = (await vscode.workspace.fs.readDirectory(folder)).map(([name]) => name); + quickpick.items = children.map(name => ({ label: name })); + quickpick.selectedItems = quickpick.items; + quickpick.busy = false; + + const result = await Promise.race([ + new Promise(c => quickpick.onDidAccept(() => c(quickpick.selectedItems))), + new Promise(c => quickpick.onDidHide(() => c(undefined))) + ]); + + if (!result) { + return; + } + + const ignored = new Set(children); + result.forEach(c => ignored.delete(c.label)); + + const raw = [...ignored].map(i => `/${i}`).join('\n'); + const encoder = new TextEncoder(); + await vscode.workspace.fs.writeFile(gitignore, encoder.encode(raw)); + } finally { + quickpick.dispose(); + } + } } const githubRepository = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: false, title: 'Publish to GitHub' }, async progress => { @@ -143,7 +157,7 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) progress.report({ message: 'Creating first commit', increment: 25 }); if (!repository) { - repository = await gitAPI.init(folder.uri) || undefined; + repository = await gitAPI.init(folder) || undefined; if (!repository) { return; diff --git a/extensions/github/src/util.ts b/extensions/github/src/util.ts new file mode 100644 index 0000000000..982ad7f07b --- /dev/null +++ b/extensions/github/src/util.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +export function dispose(arg: vscode.Disposable | Iterable): void { + if (arg instanceof vscode.Disposable) { + arg.dispose(); + } else { + for (const disposable of arg) { + disposable.dispose(); + } + } +} + +export function combinedDisposable(disposables: Iterable): vscode.Disposable { + return { + dispose() { + dispose(disposables); + } + }; +} diff --git a/extensions/package.json b/extensions/package.json index e54ff1794b..7c668c9744 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.9.5" + "typescript": "3.9.6" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/theme-defaults/themes/hc_black.json b/extensions/theme-defaults/themes/hc_black.json index 73309c34b4..436dfa5291 100644 --- a/extensions/theme-defaults/themes/hc_black.json +++ b/extensions/theme-defaults/themes/hc_black.json @@ -122,5 +122,11 @@ "foreground": "#CBEDCB", } } - ] + ], + "semanticTokenColors": { + "newOperator": "#FFFFFF", + "stringLiteral": "#ce9178", + "customLiteral": "#DCDCAA", + "numberLiteral": "#b5cea8", + } } diff --git a/extensions/theme-defaults/themes/light_defaults.json b/extensions/theme-defaults/themes/light_defaults.json index a22dd21b4a..f8b6f6f0c8 100644 --- a/extensions/theme-defaults/themes/light_defaults.json +++ b/extensions/theme-defaults/themes/light_defaults.json @@ -20,7 +20,7 @@ "statusBarItem.remoteBackground": "#16825D", "sideBarSectionHeader.background": "#0000", "sideBarSectionHeader.border": "#61616130", - "notebook.cellFocusBackground": "#c8ddf150", + "notebook.focusedCellBackground": "#c8ddf150", "notebook.cellBorderColor": "#dae3e9", "notebook.outputContainerBackgroundColor": "#c8ddf150", "notebook.focusedCellShadow": "#00315040" diff --git a/extensions/yarn.lock b/extensions/yarn.lock index e0f5c7c4dd..d41a4ab48b 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -76,10 +76,10 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -typescript@3.9.5: - version "3.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" - integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== +typescript@3.9.6: + version "3.9.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" + integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== wrappy@1: version "1.0.2" diff --git a/package.json b/package.json index 3cd7324890..60cf3dbd88 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "preinstall": "node build/npm/preinstall.js", "postinstall": "node build/npm/postinstall.js", "compile": "gulp compile --max_old_space_size=4095", - "watch": "gulp watch --max_old_space_size=4095", + "watch": "concurrently \"npm:watch-client\" \"npm:watch-extensions\"", "watchd": "deemon yarn watch", "watch-webd": "deemon yarn watch-web", "kill-watchd": "deemon --kill yarn watch", @@ -22,6 +22,7 @@ "restart-watchd": "deemon --restart yarn watch", "restart-watch-webd": "deemon --restart yarn watch-web", "watch-client": "gulp watch-client --max_old_space_size=4095", + "watch-extensions": "gulp watch-extensions --max_old_space_size=4095", "mocha": "mocha test/unit/node/all.js --delay", "precommit": "node build/gulpfile.hygiene.js", "gulp": "gulp --max_old_space_size=8192", @@ -61,7 +62,7 @@ "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", - "iconv-lite-umd": "0.6.5", + "iconv-lite-umd": "0.6.7", "jquery": "3.5.0", "jschardet": "2.1.1", "keytar": "^5.5.0", @@ -123,6 +124,7 @@ "ansi-colors": "^3.2.3", "asar": "^0.14.0", "chromium-pickle-js": "^0.2.0", + "concurrently": "^5.2.0", "copy-webpack-plugin": "^4.5.2", "cson-parser": "^1.3.3", "css-loader": "^3.2.0", diff --git a/remote/package.json b/remote/package.json index 0892761f3b..5738143a11 100644 --- a/remote/package.json +++ b/remote/package.json @@ -20,7 +20,7 @@ "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", - "iconv-lite-umd": "0.6.5", + "iconv-lite-umd": "0.6.7", "jquery": "3.5.0", "jschardet": "2.1.1", "minimist": "^1.2.5", diff --git a/remote/web/package.json b/remote/web/package.json index 8de1ac76b2..9953cc8c41 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -14,7 +14,7 @@ "ansi_up": "^3.0.0", "chart.js": "^2.6.0", "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6", - "iconv-lite-umd": "0.6.5", + "iconv-lite-umd": "0.6.7", "jschardet": "2.1.1", "jquery": "3.5.0", "ng2-charts": "^1.6.0", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 6bc88bb10f..316526afa6 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -182,10 +182,10 @@ htmlparser2@^3.10.0: inherits "^2.0.1" readable-stream "^3.1.1" -iconv-lite-umd@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722" - integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg== +iconv-lite-umd@0.6.7: + version "0.6.7" + resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.7.tgz#ee437e34b30f15dc00ec93ea65065e672770777c" + integrity sha512-DT90zb7wL1B3I6DmYUMcfJeVdY19XigzDj5AtXbXEw9Jfi0+AVAxfn7ytvY7Xhr+GFn7nd7hPonapC37oo7iAQ== inherits@^2.0.1, inherits@^2.0.3: version "2.0.4" diff --git a/remote/yarn.lock b/remote/yarn.lock index 98809ebf37..d27835e054 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -376,10 +376,10 @@ https-proxy-agent@^4.0.0: agent-base "5" debug "4" -iconv-lite-umd@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722" - integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg== +iconv-lite-umd@0.6.7: + version "0.6.7" + resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.7.tgz#ee437e34b30f15dc00ec93ea65065e672770777c" + integrity sha512-DT90zb7wL1B3I6DmYUMcfJeVdY19XigzDj5AtXbXEw9Jfi0+AVAxfn7ytvY7Xhr+GFn7nd7hPonapC37oo7iAQ== inherits@^2.0.1, inherits@^2.0.3: version "2.0.4" diff --git a/resources/serverless/code-web.js b/resources/serverless/code-web.js index bee6ca5b71..4345136b62 100644 --- a/resources/serverless/code-web.js +++ b/resources/serverless/code-web.js @@ -232,20 +232,23 @@ async function handleRoot(req, res) { if (match) { const qs = new URLSearchParams(match[1]); - let ghPath = qs.get('gh'); - if (ghPath) { - if (!ghPath.startsWith('/')) { - ghPath = '/' + ghPath; + let gh = qs.get('gh'); + if (gh) { + if (gh.startsWith('/')) { + gh = gh.substr(1); } - folderUri = { scheme: 'github', authority: 'HEAD', path: ghPath }; - } else { - let csPath = qs.get('cs'); - if (csPath) { - if (!csPath.startsWith('/')) { - csPath = '/' + csPath; + const [owner, repo, ...branch] = gh.split('/', 3); + folderUri = { scheme: 'github', authority: branch.join('/') || 'HEAD', path: `/${owner}/${repo}` }; + } else { + let cs = qs.get('cs'); + if (cs) { + if (cs.startsWith('/')) { + cs = cs.substr(1); } - folderUri = { scheme: 'codespace', authority: 'HEAD', path: csPath }; + + const [owner, repo, ...branch] = cs.split('/'); + folderUri = { scheme: 'codespace', authority: branch.join('/') || 'HEAD', path: `/${owner}/${repo}` }; } } } diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts index 25f9237179..6384b6454a 100644 --- a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts +++ b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts @@ -31,7 +31,7 @@ import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/t import { UNSAVED_GROUP_ID, mssqlProviderName } from 'sql/platform/connection/common/constants'; import { $ } from 'vs/base/browser/dom'; import { OEManageConnectionAction } from 'sql/workbench/contrib/dashboard/browser/dashboardActions'; -import { IViewsService, IView, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; +import { IViewsService, IView, ViewContainerLocation, ViewContainer, IViewPaneContainer } from 'vs/workbench/common/views'; import { ConsoleLogService } from 'vs/platform/log/common/log'; import { IProgressIndicator } from 'vs/platform/progress/common/progress'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; @@ -111,6 +111,9 @@ suite('SQL Connection Tree Action tests', () => { }); const viewsService = new class implements IViewsService { + getActiveViewPaneContainerWithId(viewContainerId: string): IViewPaneContainer { + throw new Error('Method not implemented.'); + } getViewProgressIndicator(id: string): IProgressIndicator { throw new Error('Method not implemented.'); } diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 691a9c3798ab212fc9c17e3dd0479ac2efb31f42..575a779d5dfe54d5c4dadd24032d0170ef45905d 100644 GIT binary patch delta 4941 zcmYM%dwkFJ9tZH(dl#D-yWcN{U6^fbW;eTU7-qQ+qh-u3!^%jaACiQmenoXu>Xb%K zr%WeDIyrKrPN#!zloLtf^C<~QJL0^)-hZ4u9?$Rl_xTsSU-u=lKHBiGauIISq@NJ?e3gzmMdL66Q9{ znbvJvblGy?_-?+yuX*15mSa*I1blS>2)z8*-X0|t5a%BPrziW5xFewO7j*JG=Un5f zUbUD1A8)pM{^)t0=f3c8p5+h!Mk?I#@`h|{J1mjjk9+vK&vgjS_PX^eUauXVxBht= zqaVQMy*xhVA9lI*Pdb>@W}SK8d|+$rHg|?krfUnnh1afccn$|KMHx6?>!?9>o$oBt66*jS?r`5-P#cLBb?fA|wzG zODBoq+xLPuI>3kTq9fl|C-|c?0udC5V1!@*3Q>e&l%d>x!KchM5Q8unLr{Ye7>Q9B zjWMXjIE=>x)L|kf;da#H4m4mYreOwVVm6vE2XiqG&A1D9qXi4F5cgma?!{ugVQ*J?{F61;~dVz;0OGO3-}2a;rbcB;3|H_HT)OXaRWC6;vt^mBfip6 z{G_vVkpKyj5DAw^iI#5CT@ocpk|jlYN~-jhG)b2X=_8qvCH*8@`b&=FNxl?HkrYdb zluDVDNxw#&A?f42I_5HdLViQHaJ)OvjygMEY`71)vhoVIQo-bHT5| z6A_4@PWG8b#pMVyP2m&9 z=}Hn9XDBXKn3;;p7G{>J#V$U$Q*kZB%vRtVGfj%?8)lB;N{5-NxaMKzDXxB)W`$Yq zeVrnPe8jW)3X>UGWE{*6m<5V?0<%ydo$(&U+<{r7FxH*mAK@zC*?o!`1+zpkw_uhk zW*N-=iunfPQp`MpF+*WiDm=vaAmDNi(t{_f6cZT6`6a>m zCRQtnV_c)en{lm@P{wsif*G9)l5}9)pd^g(F(t8#k1L5l9~d`Jh*bi{xrc;@8J|?r ziE*QnD0gG$cZ;0w?G?q60n@5jJYcpdmJyikiUkGcRfY1l$z9e8YYPnPxr0>(=7?gw zfjO#Jd0;+KtU)lJDpn(y&lKwt%;$;~3g!#NS_N}Vv1-8_SFB$!Uplv)2bMCJuM~?K z%n8Nv2IG9^#6k!2wPMMGIjLCuU`{EPL6~n83nI*E#nK4#tzwacIipxET`=GAgjhLY z&MMYWnC}&*>yf9EMxp#u~)#{Q0y8oHx>H__~CD1Ct*EQ>?yDwirod)Q?bv$dMOuM4y?Cg z^MPf$IM{|@eH0rKtgm8gg6*i-q+tCN+ZAjl#l{8euh_z1J1aIb*e(iBGY0Tu^8hwD z*g(Zr2OFf=^k9P(+aGL*Vk3kNRcwi{VT#QWHe9hy!bT`IOxQ@p)(IP>u${NvMl1GH z*cipm3LC4~V`1YIyDeH6BK(fY&XTO4BK6?Kf|)XIoPRT6UoH`n>K8c zV*7?oR?fs{Qxsb|Y){4J4%qc-O;;QTU^5g41GsD-o)BjQ z*uIL>0&J$@+yI-UI6=VnQ=BDWvlXWb*#3(11#FJuqyd|&ICH?}DNY}-`M2=?ALD}p z#gPPdfZ}ihTc|juz!oVEEU?9jqYG?_;t&H{syNQTmMIQ4u;q#)4s3)T3x zdKMl#P;ml+9i%u5!46iOieRf0=Ofr5ijxxTP{o-Ec9`Py1Y4~*N5KwPoTy-Hlrvkw zj!>MkU`Hy>Td<=PCokC1iZdAO7{zG}cC6xD23xB*p}~$*xXCzP2|uTupu~f*&iVd% z5T`IqRO00PBqhF#w=3z$Sg*v7@eU=O87C|0!q}iBfN_eFAjYXmoD^I2F`;`3}t*2U7Z6s;KOcE9Q9!z zQz&p>i|moPlV?vVj{mS56*mH~n-rW&Jf)|etCDA%6}Jhn&noO=bk50Xnf7_b zO$6)~H*cTPI7_iRWBW^ZeotsB^kMU*24G8R3Ak{f=F9Pe_N#d>qwpGFT zinl4reLQ-BYkNRoKvlrpfYkxd1ndad6L2)(bf9nGhQRGX2|+nQD}(k1eID!|oE$tf zxIXx~;6uSzLUKZ?Lr#ZAhE|5Y7M3*Osn_ z5`q(+>z32)bob=$U-wv)Se$q`DLg4PsU>N1(&^;tQo5wHq-^i$(X+ni&R*!1 z-D_QHP3p?jtGye0x2E}})ub&-JDcv2-aY+j`h|?}jGBx+eVY0l>T{!SYTprk7x#TF zvwLP?W>eySaZ$|6Tnr=1k7{FgGK&Id?~1XkO2} z`n=Y>bNOER+4>7|TV9kJQg-wM!3O_B%E~+hREY2(5ReYkPv1E5?R_T_~ z_OjZtHDw3OQ_G(yzh043F}Y$z#s12`%7V(fD_Vc4bW@apR7jl-*lFC4zR1~olvCe^I0*?-&W+Ya7# zeMHqrzmYj3503I0wPaN5sI#N*9Njwl(wMw4%f=iXbA4>}*p{(d$L_C9tX(;-VBC^% z>&I;ww{zTq@fqV6j{kUq$As|{PSs`A&8*uoF~Wc1_=(#m9-eq*Qud^olg#b@x1Xuc zt6$Z&GImpJrx841dB+NZ4KepDZq}Z>jJtcQrb$vgr%i2f JuQd&V{{hxLQAz*+ delta 4663 zcmXxn30zfW8V2y^y{ITE%ViM|71=}u5mZ1#b`fwxR6vw+L8SsEx75swMrLWIV(K_$ zwoGbMjfK;+jX91rPGd7>q^4zNW%hm8YF0)%&+(n{_xqpco(o>R=X~G$zUP$>z1lwT z+7#_s2*?Hi<+JN&He4Q(v=QiZ2?z|GSGRobWA9D~1|ph(*Vom~nOPIoe>Y#_Jf+t1 z4ewJOWBK|Gim9z%yzByB@bf+gI(DdAFni|7cCT|lINx(M)z4hk;F^xN`SVb|5jlTm z{hYr0!gE&xr@QeNd>R%kTAV$n@;q>czu@1x`Hkr8T!{05K-Y=x;#>VPe?(U={_lXZ zzR&ZM|DQLmd0qDUil?37a^B@1{vF9}?kz9Mo|fYh+OhfDPVU}b?KeLUuB-66`6_|w z1MvM0u6LRvy@O>>bExO1;v=EW$GrP=&A$e2dnry9yDS#?nM*s#{+l}>+ujC#wKjW7Hq{M*oN)c z;lb~)6Tio!*oEDA0#9QP_F^BN#dCNbFW^tukC$)&f5yxB3tq$Py!n5{A^Z(T@DCit zKk*KZ;a&V2|G{y*hgN)mlQ@Ob_z-7s79Zmrm(Qp844>l*T*Q~Sgs))HhRgUG-@x-N zuHZ*p#ZS0~U-28RdPtZ=N?(bV7>SiQ=_mbVfDDub zNt8h{Sdt}0hR9GECaIDp88SjLWu#rUN zYG1@0+=dO3#HHegd_0CXVWbz{!hQGw-{Y`&aXG)wh1Th*@4ASatBwm9@QZMc&idQZ zwTI_%z|2xyR4}s@mlsTp;zEO&qi}*bS8?&d%u`&3Ftv&c66Q9A7Uu2B*(>G_g`vzk zz~daaa$)Kf*D%a{#nlY6Kyh8eG$^icm^&5MIt;6XgR359k>dJ?S**AdV3sKE37Dk{ ziClH&E}jrK3d}OaEd#S$!I!y0aT~$ht+=6JRw`~Sn0pjA8O$oh?FQpf+;}jJ3a>I( zJNQHHMi}Q36Za*|y^1>&hV{&WwaojJL@}Ln;{JuXU%|O`>y+>wn+KJ2V6ImZ$lRbL zfLZ-840C-;k$~B%;QXVHC}FuW+Z0PkL-#jFX7Fr}Vljc) zt5{ZG_9+$^m}eDon9fZiKYx#TpIswPLk~`NqLT%EArvtzrp>`A)Hz!+ft;)?t27EbuT_ z6iYqKkBUVf=Bi@(hxtjd5y1Sc*b-oVQEU#}LDzU7_6e9@)y;3#&ey~X1oN9hBlEgq zKY_WS*jZS|6?+V|pxw>+t}#K3IRnCI}m#*bZUcij5IAP_aeA z1}W@j1}nBr*bv1A3fn`mmBNN9HdWX#g?+rdHe9jO!uC|`xv&w6-50i(VjqU>t=N%a z`zZEi*hs}L4ck|-U&FGfIoP>jqZNBNYz%pLU^|D6Rc!3Aaf&S-wx4n)JsYps_F?-g z9AXYo90gzpDh>s(35w$ZY@*_z06R!=WPrye@q{=%zz$ZNBVdyiCkogU#n}ROh~kt1 zJ5+JrfE}hddBCPB&LFU9iqi;ey5d{{JNzbI|5JRCp*XC-j!+z1U^5j57}$}DqYP}8 z;!p#dtvKGm<|v$J<|>Xnuz8BZ4{W~T7zA73-2Vc8`!ATI6lWvYLd7WwcC_NW1Y4vy zIl&eyXYbh(#c2w5jN)7cJ63VRf*q$gYr&3J&eR24syKhaPE?%4V9OL|GT2Fq(;4h! z#W@XjisHluTdp{}!B!|vaj=yNH<(kE2(!w$|2&9OB&I3p#GJ0gn^~=-GxHWDU6{8j z>BgL)q&st_5?|&lCH~CW${E&djgnwz|IgtGNe|{+CC>hwrzDbDtE4aUHYLthx?M>O z^A07k%sM4;%z7pLnDdqNXD)E?{)@AH8k9KO=T0RF%!Nu4nTwPRVlGxPn7KrWvtyPj zadymIN}L_DOvzB@awWrlBA}*asBHci0CN2YJ}_iX%Pj z28BH4LyBWQ>_&xR=ELaY9Kg{Zc9Y^m0PJRk;msYwqLW|b*&~V%2C&-{9}!@;D>(n$ z4kby=m0{7Ie4ah3_y7U>KYWyc-KAidk1IZ2!0uLj(13kH@sR`8xnjhJ4_N1d5g$Wf zpH?``bS@t8(FB$k&+QzXhi9Edp@>yFKcZHzT>330c? zJ=f3Ouc+Vde(mw4@w?(r^dHc_Z9vq3tpi;H3kU8U*p?8I(3G${;ZR~j;)cWniEV=_ zlYElqCOw*TH0kTXp@YW`J~a4DvQKhp^0wsDDejb_6wiv3{V6{Wi68RvP~TzhVP(TM z4!fROn|dtGH?1V?rL?!x+S3cu*QP(2ej@$q@D;-kW|U@B%C#x-Wpm`!62j4@+}jy2Ez_zlN%=QoNT7J zrxZ+?G39W1RQc(O@QQ?rtcuc#Srx}BgDUGPcUNAWx@zk2s!mlURokipFHQ5EmNISO zv}dNrPcNyCs$N=sq~*DY?GY{B8P~VBymcb7i{!RkJiEr#Qg?2FtHqwL>DpY?R{dY1 C%?wrm diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index b123935448..1374280514 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -854,6 +854,7 @@ export interface IListOptions { readonly horizontalScrolling?: boolean; readonly additionalScrollHeight?: number; readonly transformOptimization?: boolean; + readonly smoothScrolling?: boolean; } export interface IListStyles { diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 88caef66f0..520db591f4 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -17,6 +17,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { INewScrollDimensions, INewScrollPosition, IScrollDimensions, IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { getZoomFactor } from 'vs/base/browser/browser'; const HIDE_TIMEOUT = 500; const SCROLL_WHEEL_SENSITIVITY = 50; @@ -130,13 +131,18 @@ export class MouseWheelClassifier { // } } - if (Math.abs(item.deltaX - Math.round(item.deltaX)) > 0 || Math.abs(item.deltaY - Math.round(item.deltaY)) > 0) { + if (!this._isAlmostInt(item.deltaX) || !this._isAlmostInt(item.deltaY)) { // non-integer deltas => indicator that this is not a physical mouse wheel score += 0.25; } return Math.min(Math.max(score, 0), 1); } + + private _isAlmostInt(value: number): boolean { + const delta = Math.abs(Math.round(value) - value); + return (delta < 0.01); + } } export abstract class AbstractScrollableElement extends Widget { @@ -343,10 +349,11 @@ export abstract class AbstractScrollableElement extends Widget { const classifier = MouseWheelClassifier.INSTANCE; if (SCROLL_WHEEL_SMOOTH_SCROLL_ENABLED) { - if (platform.isWindows) { - // On Windows, the incoming delta events are multiplied with the device pixel ratio, - // so to get a better classification, simply undo that. - classifier.accept(Date.now(), e.deltaX / window.devicePixelRatio, e.deltaY / window.devicePixelRatio); + const osZoomFactor = window.devicePixelRatio / getZoomFactor(); + if (platform.isWindows || platform.isLinux) { + // On Windows and Linux, the incoming delta events are multiplied with the OS zoom factor. + // The OS zoom factor can be reverse engineered by using the device pixel ratio and the configured zoom factor into account. + classifier.accept(Date.now(), e.deltaX / osZoomFactor, e.deltaY / osZoomFactor); } else { classifier.accept(Date.now(), e.deltaX, e.deltaY); } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 13b707c2fd..d46b93ff42 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -951,6 +951,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions { readonly filterOnType?: boolean; readonly smoothScrolling?: boolean; readonly horizontalScrolling?: boolean; + readonly expandOnlyOnDoubleClick?: boolean; } export interface IAbstractTreeOptions extends IAbstractTreeOptionsUpdate, IListOptions { @@ -1094,7 +1095,10 @@ class TreeNodeListMouseController extends MouseController< return super.onViewPointer(e); } - const onTwistie = hasClass(e.browserEvent.target as HTMLElement, 'monaco-tl-twistie'); + const target = e.browserEvent.target as HTMLElement; + const onTwistie = hasClass(target, 'monaco-tl-twistie') + || (hasClass(target, 'monaco-icon-label') && hasClass(target, 'folder-icon') && e.browserEvent.offsetX < 16); + let expandOnlyOnTwistieClick = false; if (typeof this.tree.expandOnlyOnTwistieClick === 'function') { @@ -1107,6 +1111,10 @@ class TreeNodeListMouseController extends MouseController< return super.onViewPointer(e); } + if (this.tree.expandOnlyOnDoubleClick && e.browserEvent.detail !== 2 && !onTwistie) { + return super.onViewPointer(e); + } + if (node.collapsible) { const model = ((this.tree as any).model as ITreeModel); // internal const location = model.getNodeLocation(node); @@ -1244,6 +1252,7 @@ export abstract class AbstractTree implements IDisposable get filterOnType(): boolean { return !!this._options.filterOnType; } get onDidChangeTypeFilterPattern(): Event { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; } + get expandOnlyOnDoubleClick(): boolean { return this._options.expandOnlyOnDoubleClick ?? false; } get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; } private readonly _onDidUpdateOptions = new Emitter>(); diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index af7c151f0c..5324f7bd61 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -472,6 +472,8 @@ export namespace Codicon { export const stopCircle = new Codicon('stop-circle', { character: '\\eba5' }); export const playCircle = new Codicon('play-circle', { character: '\\eba6' }); export const record = new Codicon('record', { character: '\\eba7' }); + export const debugAltSmall = new Codicon('debug-alt-small', { character: '\\eba8' }); + export const vmConnect = new Codicon('vm-connect', { character: '\\eba9' }); } diff --git a/src/vs/base/parts/contextmenu/common/contextmenu.ts b/src/vs/base/parts/contextmenu/common/contextmenu.ts index ce3f446359..a5aaa61441 100644 --- a/src/vs/base/parts/contextmenu/common/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/common/contextmenu.ts @@ -36,8 +36,7 @@ export interface IPopupOptions { x?: number; y?: number; positioningItem?: number; - onHide?: () => void; } export const CONTEXT_MENU_CHANNEL = 'vscode:contextmenu'; -export const CONTEXT_MENU_CLOSE_CHANNEL = 'vscode:onCloseContextMenu'; \ No newline at end of file +export const CONTEXT_MENU_CLOSE_CHANNEL = 'vscode:onCloseContextMenu'; diff --git a/src/vs/base/parts/contextmenu/electron-sandbox/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-sandbox/contextmenu.ts index ab14d788a6..0379ab0925 100644 --- a/src/vs/base/parts/contextmenu/electron-sandbox/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-sandbox/contextmenu.ts @@ -8,7 +8,7 @@ import { IContextMenuItem, ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHAN let contextMenuIdPool = 0; -export function popup(items: IContextMenuItem[], options?: IPopupOptions): void { +export function popup(items: IContextMenuItem[], options?: IPopupOptions, onHide?: () => void): void { const processedItems: IContextMenuItem[] = []; const contextMenuId = contextMenuIdPool++; @@ -28,8 +28,8 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions): void ipcRenderer.removeListener(onClickChannel, onClickChannelHandler); - if (options?.onHide) { - options.onHide(); + if (onHide) { + onHide(); } }); diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 361b82f816..a1775a9ac9 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -1230,10 +1230,10 @@ export class QuickInputController extends Disposable { this.previousFocusElement = e.relatedTarget instanceof HTMLElement ? e.relatedTarget : undefined; }, true)); this._register(focusTracker.onDidBlur(() => { - this.previousFocusElement = undefined; if (!this.getUI().ignoreFocusOut && !this.options.ignoreFocusOut()) { - this.hide(true); + this.hide(); } + this.previousFocusElement = undefined; })); this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e: FocusEvent) => { inputBox.setFocus(); @@ -1574,13 +1574,14 @@ export class QuickInputController extends Disposable { } } - hide(focusLost?: boolean) { + hide() { const controller = this.controller; if (controller) { + const focusChanged = !this.ui?.container.contains(document.activeElement); this.controller = null; this.onHideEmitter.fire(); this.getUI().container.style.display = 'none'; - if (!focusLost) { + if (!focusChanged) { if (this.previousFocusElement && this.previousFocusElement.offsetParent) { this.previousFocusElement.focus(); this.previousFocusElement = undefined; diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index d414140029..f3b4288722 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -277,6 +277,9 @@ class WorkspaceProvider implements IWorkspaceProvider { (function () { + // Mark start of workbench + performance.mark('workbench-start'); + // Find config by checking for DOM const configElement = document.getElementById('vscode-workbench-web-configuration'); const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined; diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 51e7290dbe..84beaa6339 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -55,6 +55,8 @@ bootstrapWindow.load([ 'vs/css!vs/workbench/workbench.desktop.main' ], function (workbench, configuration) { + + // Mark start of workbench perf.mark('didLoadWorkbenchMain'); performance.mark('workbench-start'); diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 70a6b8cc9c..ea4d23160f 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -280,6 +280,12 @@ export class CommonFindController extends Disposable implements IEditorContribut if (!stateChanges.searchString && opts.seedSearchStringFromGlobalClipboard) { let selectionSearchString = await this.getGlobalBufferTerm(); + + if (!this._editor.hasModel()) { + // the editor has lost its model in the meantime + return; + } + if (selectionSearchString) { stateChanges.searchString = selectionSearchString; } @@ -364,13 +370,14 @@ export class CommonFindController extends Disposable implements IEditorContribut return ''; } - public async setGlobalBufferTerm(text: string) { + public setGlobalBufferTerm(text: string): void { if (this._editor.getOption(EditorOption.find).globalFindClipboard && this._clipboardService && this._editor.hasModel() && !this._editor.getModel().isTooLargeForSyncing() ) { - await this._clipboardService.writeFindText(text); + // intentionally not awaited + this._clipboardService.writeFindText(text); } } } @@ -424,10 +431,12 @@ export class FindController extends CommonFindController implements IFindControl await super._start(opts); - if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) { - this._widget!.focusReplaceInput(); - } else if (opts.shouldFocus === FindStartFocusAction.FocusFindInput) { - this._widget!.focusFindInput(); + if (this._widget) { + if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) { + this._widget.focusReplaceInput(); + } else if (opts.shouldFocus === FindStartFocusAction.FocusFindInput) { + this._widget.focusFindInput(); + } } } @@ -470,10 +479,10 @@ export class StartFindAction extends EditorAction { }); } - public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void { + public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise { let controller = CommonFindController.get(editor); if (controller) { - controller.start({ + await controller.start({ forceRevealReplace: false, seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection, seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).globalFindClipboard, @@ -508,7 +517,7 @@ export class StartFindWithSelectionAction extends EditorAction { public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { let controller = CommonFindController.get(editor); if (controller) { - controller.start({ + await controller.start({ forceRevealReplace: false, seedSearchStringFromSelection: true, seedSearchStringFromGlobalClipboard: false, @@ -518,15 +527,15 @@ export class StartFindWithSelectionAction extends EditorAction { loop: editor.getOption(EditorOption.find).loop }); - return controller.setGlobalBufferTerm(controller.getState().searchString); + controller.setGlobalBufferTerm(controller.getState().searchString); } } } export abstract class MatchFindAction extends EditorAction { - public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void { + public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise { let controller = CommonFindController.get(editor); if (controller && !this._run(controller)) { - controller.start({ + await controller.start({ forceRevealReplace: false, seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection, seedSearchStringFromGlobalClipboard: true, @@ -629,7 +638,7 @@ export class PreviousMatchFindAction2 extends MatchFindAction { } export abstract class SelectionMatchFindAction extends EditorAction { - public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void { + public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise { let controller = CommonFindController.get(editor); if (!controller) { return; @@ -639,7 +648,7 @@ export abstract class SelectionMatchFindAction extends EditorAction { controller.setSearchString(selectionSearchString); } if (!this._run(controller)) { - controller.start({ + await controller.start({ forceRevealReplace: false, seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection, seedSearchStringFromGlobalClipboard: false, @@ -720,7 +729,7 @@ export class StartFindReplaceAction extends EditorAction { }); } - public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void { + public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise { if (!editor.hasModel() || editor.getOption(EditorOption.readOnly)) { return; } @@ -745,7 +754,7 @@ export class StartFindReplaceAction extends EditorAction { if (controller) { - controller.start({ + await controller.start({ forceRevealReplace: true, seedSearchStringFromSelection: seedSearchStringFromSelection, seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).seedSearchStringFromSelection, diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 7792929cdc..b6a8c8c7d3 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -14,7 +14,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { CommonFindController, FindStartFocusAction, IFindStartOptions, NextMatchFindAction, NextSelectionMatchFindAction, StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/findController'; import { CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel'; -import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -55,7 +55,7 @@ function fromSelection(slc: Selection): number[] { return [slc.startLineNumber, slc.startColumn, slc.endLineNumber, slc.endColumn]; } -suite.skip('FindController', () => { +suite.skip('FindController', async () => { let queryState: { [key: string]: any; } = {}; let clipboardState = ''; let serviceCollection = new ServiceCollection(); @@ -77,13 +77,13 @@ suite.skip('FindController', () => { }); } - /* test('stores to the global clipboard buffer on start find action', () => { - withTestCodeEditor([ + /* test('stores to the global clipboard buffer on start find action', async () => { + await withAsyncTestCodeEditor([ 'ABC', 'ABC', 'XYZ', 'ABC' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; if (!platform.isMacintosh) { assert.ok(true); @@ -101,13 +101,13 @@ suite.skip('FindController', () => { }); }); - test('reads from the global clipboard buffer on next find action if buffer exists', () => { - withTestCodeEditor([ + test('reads from the global clipboard buffer on next find action if buffer exists', async () => { + await withAsyncTestCodeEditor([ 'ABC', 'ABC', 'XYZ', 'ABC' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = 'ABC'; if (!platform.isMacintosh) { @@ -128,13 +128,13 @@ suite.skip('FindController', () => { }); }); - test('writes to the global clipboard buffer when text changes', () => { - withTestCodeEditor([ + test('writes to the global clipboard buffer when text changes', async () => { + await withAsyncTestCodeEditor([ 'ABC', 'ABC', 'XYZ', 'ABC' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; if (!platform.isMacintosh) { assert.ok(true); @@ -152,13 +152,13 @@ suite.skip('FindController', () => { }); }); */ - test('issue #1857: F3, Find Next, acts like "Find Under Cursor"', () => { - withTestCodeEditor([ + test('issue #1857: F3, Find Next, acts like "Find Under Cursor"', async () => { + await withAsyncTestCodeEditor([ 'ABC', 'ABC', 'XYZ', 'ABC' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; // The cursor is at the very top, of the file, at the first ABC let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); @@ -167,7 +167,7 @@ suite.skip('FindController', () => { let nextMatchFindAction = new NextMatchFindAction(); // I hit Ctrl+F to show the Find dialog - startFindAction.run(null, editor); + await startFindAction.run(null, editor); // I type ABC. findState.change({ searchString: 'A' }, true); @@ -201,7 +201,7 @@ suite.skip('FindController', () => { assert.deepEqual(fromSelection(editor.getSelection()!), [1, 4, 1, 4]); // I hit F3 to "Find Next" to find the next occurrence of ABC, but instead it searches for XYZ. - nextMatchFindAction.run(null, editor); + await nextMatchFindAction.run(null, editor); assert.equal(findState.searchString, 'ABC'); assert.equal(findController.hasFocus, false); @@ -210,10 +210,10 @@ suite.skip('FindController', () => { }); }); - test('issue #3090: F3 does not loop with two matches on a single line', () => { - withTestCodeEditor([ + test('issue #3090: F3 does not loop with two matches on a single line', async () => { + await withAsyncTestCodeEditor([ 'import nls = require(\'vs/nls\');' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextMatchFindAction = new NextMatchFindAction(); @@ -223,22 +223,22 @@ suite.skip('FindController', () => { column: 9 }); - nextMatchFindAction.run(null, editor); + await nextMatchFindAction.run(null, editor); assert.deepEqual(fromSelection(editor.getSelection()!), [1, 26, 1, 29]); - nextMatchFindAction.run(null, editor); + await nextMatchFindAction.run(null, editor); assert.deepEqual(fromSelection(editor.getSelection()!), [1, 8, 1, 11]); findController.dispose(); }); }); - test('issue #6149: Auto-escape highlighted text for search and replace regex mode', () => { - withTestCodeEditor([ + test('issue #6149: Auto-escape highlighted text for search and replace regex mode', async () => { + await withAsyncTestCodeEditor([ 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); @@ -247,22 +247,22 @@ suite.skip('FindController', () => { editor.setSelection(new Selection(1, 9, 1, 13)); findController.toggleRegex(); - startFindAction.run(null, editor); + await startFindAction.run(null, editor); - nextMatchFindAction.run(null, editor); + await nextMatchFindAction.run(null, editor); assert.deepEqual(fromSelection(editor.getSelection()!), [2, 9, 2, 13]); - nextMatchFindAction.run(null, editor); + await nextMatchFindAction.run(null, editor); assert.deepEqual(fromSelection(editor.getSelection()!), [1, 9, 1, 13]); findController.dispose(); }); }); - test('issue #41027: Don\'t replace find input value on replace action if find input is active', () => { - withTestCodeEditor([ + test('issue #41027: Don\'t replace find input value on replace action if find input is active', async () => { + await withAsyncTestCodeEditor([ 'test', - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { let testRegexString = 'tes.'; let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextMatchFindAction = new NextMatchFindAction(); @@ -270,7 +270,7 @@ suite.skip('FindController', () => { findController.toggleRegex(); findController.setSearchString(testRegexString); - findController.start({ + await findController.start({ forceRevealReplace: false, seedSearchStringFromSelection: false, seedSearchStringFromGlobalClipboard: false, @@ -279,8 +279,8 @@ suite.skip('FindController', () => { updateSearchScope: false, loop: true }); - nextMatchFindAction.run(null, editor); - startFindReplaceAction.run(null, editor); + await nextMatchFindAction.run(null, editor); + await startFindReplaceAction.run(null, editor); assert.equal(findController.getState().searchString, testRegexString); @@ -288,15 +288,15 @@ suite.skip('FindController', () => { }); }); - test('issue #9043: Clear search scope when find widget is hidden', () => { - withTestCodeEditor([ + test('issue #9043: Clear search scope when find widget is hidden', async () => { + await withAsyncTestCodeEditor([ 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); - findController.start({ + await findController.start({ forceRevealReplace: false, seedSearchStringFromSelection: false, seedSearchStringFromGlobalClipboard: false, @@ -319,15 +319,15 @@ suite.skip('FindController', () => { }); }); - test('issue #18111: Regex replace with single space replaces with no space', () => { - withTestCodeEditor([ + test('issue #18111: Regex replace with single space replaces with no space', async () => { + await withAsyncTestCodeEditor([ 'HRESULT OnAmbientPropertyChange(DISPID dispid);' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); - startFindAction.run(null, editor); + await startFindAction.run(null, editor); findController.getState().change({ searchString: '\\b\\s{3}\\b', replaceString: ' ', isRegex: true }, false); findController.moveToNextMatch(); @@ -344,17 +344,17 @@ suite.skip('FindController', () => { }); }); - test('issue #24714: Regular expression with ^ in search & replace', () => { - withTestCodeEditor([ + test('issue #24714: Regular expression with ^ in search & replace', async () => { + await withAsyncTestCodeEditor([ '', 'line2', 'line3' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); - startFindAction.run(null, editor); + await startFindAction.run(null, editor); findController.getState().change({ searchString: '^', replaceString: 'x', isRegex: true }, false); findController.moveToNextMatch(); @@ -371,12 +371,12 @@ suite.skip('FindController', () => { }); }); - test('issue #38232: Find Next Selection, regex enabled', () => { - withTestCodeEditor([ + test('issue #38232: Find Next Selection, regex enabled', async () => { + await withAsyncTestCodeEditor([ '([funny]', '', '([funny]' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextSelectionMatchFindAction = new NextSelectionMatchFindAction(); @@ -388,7 +388,7 @@ suite.skip('FindController', () => { editor.setSelection(new Selection(1, 1, 1, 9)); // cmd+f3 - nextSelectionMatchFindAction.run(null, editor); + await nextSelectionMatchFindAction.run(null, editor); assert.deepEqual(editor.getSelections()!.map(fromSelection), [ [3, 1, 3, 9] @@ -398,19 +398,19 @@ suite.skip('FindController', () => { }); }); - test('issue #38232: Find Next Selection, regex enabled, find widget open', () => { - withTestCodeEditor([ + test('issue #38232: Find Next Selection, regex enabled, find widget open', async () => { + await withAsyncTestCodeEditor([ '([funny]', '', '([funny]' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); let nextSelectionMatchFindAction = new NextSelectionMatchFindAction(); // cmd+f - open find widget - startFindAction.run(null, editor); + await startFindAction.run(null, editor); // toggle regex findController.getState().change({ isRegex: true }, false); @@ -419,7 +419,7 @@ suite.skip('FindController', () => { editor.setSelection(new Selection(1, 1, 1, 9)); // cmd+f3 - nextSelectionMatchFindAction.run(null, editor); + await nextSelectionMatchFindAction.run(null, editor); assert.deepEqual(editor.getSelections()!.map(fromSelection), [ [3, 1, 3, 9] @@ -430,7 +430,7 @@ suite.skip('FindController', () => { }); }); -suite.skip('FindController query options persistence', () => { +suite.skip('FindController query options persistence', async () => { let queryState: { [key: string]: any; } = {}; queryState['editor.isRegex'] = false; queryState['editor.matchCase'] = false; @@ -447,13 +447,13 @@ suite.skip('FindController query options persistence', () => { remove: () => undefined } as any); - test('matchCase', () => { - withTestCodeEditor([ + test('matchCase', async () => { + await withAsyncTestCodeEditor([ 'abc', 'ABC', 'XYZ', 'ABC' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': true, 'editor.wholeWord': false }; // The cursor is at the very top, of the file, at the first ABC let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); @@ -461,7 +461,7 @@ suite.skip('FindController query options persistence', () => { let startFindAction = new StartFindAction(); // I hit Ctrl+F to show the Find dialog - startFindAction.run(null, editor); + await startFindAction.run(null, editor); // I type ABC. findState.change({ searchString: 'ABC' }, true); @@ -474,13 +474,13 @@ suite.skip('FindController query options persistence', () => { queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true }; - test('wholeWord', () => { - withTestCodeEditor([ + test('wholeWord', async () => { + await withAsyncTestCodeEditor([ 'ABC', 'AB', 'XYZ', 'ABC' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true }; // The cursor is at the very top, of the file, at the first ABC let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); @@ -488,7 +488,7 @@ suite.skip('FindController query options persistence', () => { let startFindAction = new StartFindAction(); // I hit Ctrl+F to show the Find dialog - startFindAction.run(null, editor); + await startFindAction.run(null, editor); // I type AB. findState.change({ searchString: 'AB' }, true); @@ -499,13 +499,13 @@ suite.skip('FindController query options persistence', () => { }); }); - test('toggling options is saved', () => { - withTestCodeEditor([ + test('toggling options is saved', async () => { + await withAsyncTestCodeEditor([ 'ABC', 'AB', 'XYZ', 'ABC' - ], { serviceCollection: serviceCollection }, (editor) => { + ], { serviceCollection: serviceCollection }, async (editor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true }; // The cursor is at the very top, of the file, at the first ABC let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); @@ -516,17 +516,17 @@ suite.skip('FindController query options persistence', () => { }); }); - test('issue #27083: Update search scope once find widget becomes visible', () => { - withTestCodeEditor([ + test('issue #27083: Update search scope once find widget becomes visible', async () => { + await withAsyncTestCodeEditor([ 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor) => { + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, async (editor) => { // clipboardState = ''; editor.setSelection(new Range(1, 1, 2, 1)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); - findController.start({ + await findController.start({ forceRevealReplace: false, seedSearchStringFromSelection: false, seedSearchStringFromGlobalClipboard: false, @@ -540,17 +540,17 @@ suite.skip('FindController query options persistence', () => { }); }); - test('issue #58604: Do not update searchScope if it is empty', () => { - withTestCodeEditor([ + test('issue #58604: Do not update searchScope if it is empty', async () => { + await withAsyncTestCodeEditor([ 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor) => { + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, async (editor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 2)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); - findController.start({ + await findController.start({ forceRevealReplace: false, seedSearchStringFromSelection: false, seedSearchStringFromGlobalClipboard: false, @@ -564,17 +564,17 @@ suite.skip('FindController query options persistence', () => { }); }); - test('issue #58604: Update searchScope if it is not empty', () => { - withTestCodeEditor([ + test('issue #58604: Update searchScope if it is not empty', async () => { + await withAsyncTestCodeEditor([ 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor) => { + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, async (editor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 3)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); - findController.start({ + await findController.start({ forceRevealReplace: false, seedSearchStringFromSelection: false, seedSearchStringFromGlobalClipboard: false, @@ -589,17 +589,17 @@ suite.skip('FindController query options persistence', () => { }); - test('issue #27083: Find in selection when multiple lines are selected', () => { - withTestCodeEditor([ + test('issue #27083: Find in selection when multiple lines are selected', async () => { + await withAsyncTestCodeEditor([ 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'multiline', globalFindClipboard: false } }, (editor) => { + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'multiline', globalFindClipboard: false } }, async (editor) => { // clipboardState = ''; editor.setSelection(new Range(1, 6, 2, 1)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); - findController.start({ + await findController.start({ forceRevealReplace: false, seedSearchStringFromSelection: false, seedSearchStringFromGlobalClipboard: false, diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index a04b8ecaac..64f5dfdfa5 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -96,6 +96,24 @@ export function withTestCodeEditor(text: string | string[] | null, options: Test editor.dispose(); } +export async function withAsyncTestCodeEditor(text: string | string[] | null, options: TestCodeEditorCreationOptions, callback: (editor: ITestCodeEditor, viewModel: ViewModel) => Promise): Promise { + // create a model if necessary and remember it in order to dispose it. + if (!options.model) { + if (typeof text === 'string') { + options.model = createTextModel(text); + } else if (text) { + options.model = createTextModel(text.join('\n')); + } + } + + const editor = createTestCodeEditor(options); + const viewModel = editor.getViewModel()!; + viewModel.setHasFocus(true); + await callback(editor, editor.getViewModel()!); + + editor.dispose(); +} + export function createTestCodeEditor(options: TestCodeEditorCreationOptions): ITestCodeEditor { const model = options.model; diff --git a/src/vs/loader.js b/src/vs/loader.js index 4b7859605a..abbdd820e3 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -870,6 +870,12 @@ var AMDLoader; } var cachedData = script.createCachedData(); if (cachedData.length === 0 || cachedData.length === lastSize || iteration >= 5) { + // done + return; + } + if (cachedData.length < lastSize) { + // less data than before: skip, try again next round + createLoop(); return; } lastSize = cachedData.length; diff --git a/src/vs/platform/browser/checkbox.ts b/src/vs/platform/browser/checkbox.ts index 009cf20994..96bca82db2 100644 --- a/src/vs/platform/browser/checkbox.ts +++ b/src/vs/platform/browser/checkbox.ts @@ -23,4 +23,3 @@ export class ThemableCheckboxActionViewItem extends CheckboxActionViewItem { } } - diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts new file mode 100644 index 0000000000..1706a10ae2 --- /dev/null +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -0,0 +1,246 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; + +import { Registry } from 'vs/platform/registry/common/platform'; +import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { URI } from 'vs/base/common/uri'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { Event } from 'vs/base/common/event'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { IFileService } from 'vs/platform/files/common/files'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; + +suite('ConfigurationService', () => { + + let fileService: IFileService; + let settingsResource: URI; + const disposables: DisposableStore = new DisposableStore(); + + setup(async () => { + fileService = disposables.add(new FileService(new NullLogService())); + const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + settingsResource = URI.file('settings.json'); + }); + + teardown(() => disposables.clear()); + + test('simple', async () => { + await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + await testObject.initialize(); + const config = testObject.getValue<{ + foo: string; + }>(); + + assert.ok(config); + assert.equal(config.foo, 'bar'); + }); + + test('config gets flattened', async () => { + await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); + + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + await testObject.initialize(); + const config = testObject.getValue<{ + testworkbench: { + editor: { + tabs: boolean; + }; + }; + }>(); + + assert.ok(config); + assert.ok(config.testworkbench); + assert.ok(config.testworkbench.editor); + assert.equal(config.testworkbench.editor.tabs, true); + }); + + test('error case does not explode', async () => { + await fileService.writeFile(settingsResource, VSBuffer.fromString(',,,,')); + + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + await testObject.initialize(); + const config = testObject.getValue<{ + foo: string; + }>(); + + assert.ok(config); + }); + + test('missing file does not explode', async () => { + const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService)); + await testObject.initialize(); + + const config = testObject.getValue<{ foo: string }>(); + + assert.ok(config); + }); + + test('trigger configuration change event when file does not exist', async () => { + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + await testObject.initialize(); + return new Promise(async (c, e) => { + disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => { + assert.equal(testObject.getValue('foo'), 'bar'); + c(); + })); + await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); + }); + + }); + + test('trigger configuration change event when file exists', async () => { + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); + await testObject.initialize(); + + return new Promise((c, e) => { + disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => { + assert.equal(testObject.getValue('foo'), 'barz'); + c(); + })); + fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "barz" }')); + }); + }); + + test('reloadConfiguration', async () => { + await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); + + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + await testObject.initialize(); + let config = testObject.getValue<{ + foo: string; + }>(); + assert.ok(config); + assert.equal(config.foo, 'bar'); + await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "changed" }')); + + // force a reload to get latest + await testObject.reloadConfiguration(); + config = testObject.getValue<{ + foo: string; + }>(); + assert.ok(config); + assert.equal(config.foo, 'changed'); + }); + + test('model defaults', async () => { + interface ITestSetting { + configuration: { + service: { + testSetting: string; + } + }; + } + + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configuration.service.testSetting': { + 'type': 'string', + 'default': 'isSet' + } + } + }); + + let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService)); + await testObject.initialize(); + let setting = testObject.getValue(); + + assert.ok(setting); + assert.equal(setting.configuration.service.testSetting, 'isSet'); + + await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); + testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + + setting = testObject.getValue(); + + assert.ok(setting); + assert.equal(setting.configuration.service.testSetting, 'isSet'); + + await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "configuration.service.testSetting": "isChanged" }')); + + await testObject.reloadConfiguration(); + setting = testObject.getValue(); + assert.ok(setting); + assert.equal(setting.configuration.service.testSetting, 'isChanged'); + }); + + test('lookup', async () => { + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'lookup.service.testSetting': { + 'type': 'string', + 'default': 'isSet' + } + } + }); + + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + testObject.initialize(); + + let res = testObject.inspect('something.missing'); + assert.strictEqual(res.value, undefined); + assert.strictEqual(res.defaultValue, undefined); + assert.strictEqual(res.userValue, undefined); + + res = testObject.inspect('lookup.service.testSetting'); + assert.strictEqual(res.defaultValue, 'isSet'); + assert.strictEqual(res.value, 'isSet'); + assert.strictEqual(res.userValue, undefined); + + await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "lookup.service.testSetting": "bar" }')); + + await testObject.reloadConfiguration(); + res = testObject.inspect('lookup.service.testSetting'); + assert.strictEqual(res.defaultValue, 'isSet'); + assert.strictEqual(res.userValue, 'bar'); + assert.strictEqual(res.value, 'bar'); + + }); + + test('lookup with null', async () => { + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + 'id': '_testNull', + 'type': 'object', + 'properties': { + 'lookup.service.testNullSetting': { + 'type': 'null', + } + } + }); + + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + testObject.initialize(); + + let res = testObject.inspect('lookup.service.testNullSetting'); + assert.strictEqual(res.defaultValue, null); + assert.strictEqual(res.value, null); + assert.strictEqual(res.userValue, undefined); + + await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "lookup.service.testNullSetting": null }')); + + await testObject.reloadConfiguration(); + + res = testObject.inspect('lookup.service.testNullSetting'); + assert.strictEqual(res.defaultValue, null); + assert.strictEqual(res.value, null); + assert.strictEqual(res.userValue, null); + }); +}); diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts deleted file mode 100644 index cd10df9e1c..0000000000 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ /dev/null @@ -1,304 +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 * as assert from 'assert'; -import * as os from 'os'; -import * as path from 'vs/base/common/path'; -import * as fs from 'fs'; - -import { Registry } from 'vs/platform/registry/common/platform'; -import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; -import * as uuid from 'vs/base/common/uuid'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { testFile } from 'vs/base/test/node/utils'; -import { URI } from 'vs/base/common/uri'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { Event } from 'vs/base/common/event'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { FileService } from 'vs/platform/files/common/fileService'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { Schemas } from 'vs/base/common/network'; -import { IFileService } from 'vs/platform/files/common/files'; -import { VSBuffer } from 'vs/base/common/buffer'; - -suite('ConfigurationService - Node', () => { - - let fileService: IFileService; - const disposables: IDisposable[] = []; - - setup(() => { - const logService = new NullLogService(); - fileService = new FileService(logService); - disposables.push(fileService); - const diskFileSystemProvider = new DiskFileSystemProvider(logService); - disposables.push(diskFileSystemProvider); - fileService.registerProvider(Schemas.file, diskFileSystemProvider); - }); - - test('simple', async () => { - const res = await testFile('config', 'config.json'); - fs.writeFileSync(res.testFile, '{ "foo": "bar" }'); - - const service = new ConfigurationService(URI.file(res.testFile), fileService); - await service.initialize(); - const config = service.getValue<{ - foo: string; - }>(); - - assert.ok(config); - assert.equal(config.foo, 'bar'); - service.dispose(); - - return res.cleanUp(); - }); - - test('config gets flattened', async () => { - const res = await testFile('config', 'config.json'); - - fs.writeFileSync(res.testFile, '{ "testworkbench.editor.tabs": true }'); - - const service = new ConfigurationService(URI.file(res.testFile), fileService); - await service.initialize(); - const config = service.getValue<{ - testworkbench: { - editor: { - tabs: boolean; - }; - }; - }>(); - assert.ok(config); - assert.ok(config.testworkbench); - assert.ok(config.testworkbench.editor); - assert.equal(config.testworkbench.editor.tabs, true); - - service.dispose(); - return res.cleanUp(); - }); - - test('error case does not explode', async () => { - const res = await testFile('config', 'config.json'); - - fs.writeFileSync(res.testFile, ',,,,'); - - const service = new ConfigurationService(URI.file(res.testFile), fileService); - await service.initialize(); - const config = service.getValue<{ - foo: string; - }>(); - assert.ok(config); - - service.dispose(); - return res.cleanUp(); - }); - - test('missing file does not explode', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'config', id); - const testFile = path.join(newDir, 'config.json'); - - const service = new ConfigurationService(URI.file(testFile), fileService); - await service.initialize(); - - const config = service.getValue<{ foo: string }>(); - assert.ok(config); - - service.dispose(); - }); - - test('trigger configuration change event when file does not exist', async () => { - const res = await testFile('config', 'config.json'); - const settingsFile = URI.file(res.testFile); - const service = new ConfigurationService(settingsFile, fileService); - await service.initialize(); - return new Promise(async (c, e) => { - const disposable = Event.filter(service.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => { - disposable.dispose(); - assert.equal(service.getValue('foo'), 'bar'); - service.dispose(); - await res.cleanUp(); - c(); - }); - await fileService.writeFile(settingsFile, VSBuffer.fromString('{ "foo": "bar" }')); - }); - - }); - - test('trigger configuration change event when file exists', async () => { - const res = await testFile('config', 'config.json'); - - const service = new ConfigurationService(URI.file(res.testFile), fileService); - fs.writeFileSync(res.testFile, '{ "foo": "bar" }'); - await service.initialize(); - return new Promise((c, e) => { - const disposable = Event.filter(service.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => { - disposable.dispose(); - assert.equal(service.getValue('foo'), 'barz'); - service.dispose(); - await res.cleanUp(); - c(); - }); - fs.writeFileSync(res.testFile, '{ "foo": "barz" }'); - }); - - }); - - test('reloadConfiguration', async () => { - const res = await testFile('config', 'config.json'); - - fs.writeFileSync(res.testFile, '{ "foo": "bar" }'); - - const service = new ConfigurationService(URI.file(res.testFile), fileService); - await service.initialize(); - let config = service.getValue<{ - foo: string; - }>(); - assert.ok(config); - assert.equal(config.foo, 'bar'); - fs.writeFileSync(res.testFile, '{ "foo": "changed" }'); - - // still outdated - config = service.getValue<{ - foo: string; - }>(); - assert.ok(config); - assert.equal(config.foo, 'bar'); - - // force a reload to get latest - await service.reloadConfiguration(); - config = service.getValue<{ - foo: string; - }>(); - assert.ok(config); - assert.equal(config.foo, 'changed'); - - service.dispose(); - return res.cleanUp(); - }); - - test('model defaults', async () => { - interface ITestSetting { - configuration: { - service: { - testSetting: string; - } - }; - } - - const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); - configurationRegistry.registerConfiguration({ - 'id': '_test', - 'type': 'object', - 'properties': { - 'configuration.service.testSetting': { - 'type': 'string', - 'default': 'isSet' - } - } - }); - - let serviceWithoutFile = new ConfigurationService(URI.file('__testFile'), fileService); - await serviceWithoutFile.initialize(); - let setting = serviceWithoutFile.getValue(); - - assert.ok(setting); - assert.equal(setting.configuration.service.testSetting, 'isSet'); - - return testFile('config', 'config.json').then(async res => { - fs.writeFileSync(res.testFile, '{ "testworkbench.editor.tabs": true }'); - - const service = new ConfigurationService(URI.file(res.testFile), fileService); - - let setting = service.getValue(); - - assert.ok(setting); - assert.equal(setting.configuration.service.testSetting, 'isSet'); - - fs.writeFileSync(res.testFile, '{ "configuration.service.testSetting": "isChanged" }'); - - await service.reloadConfiguration(); - let setting_1 = service.getValue(); - assert.ok(setting_1); - assert.equal(setting_1.configuration.service.testSetting, 'isChanged'); - service.dispose(); - serviceWithoutFile.dispose(); - return res.cleanUp(); - }); - }); - - test('lookup', async () => { - const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); - configurationRegistry.registerConfiguration({ - 'id': '_test', - 'type': 'object', - 'properties': { - 'lookup.service.testSetting': { - 'type': 'string', - 'default': 'isSet' - } - } - }); - - const r = await testFile('config', 'config.json'); - const service = new ConfigurationService(URI.file(r.testFile), fileService); - service.initialize(); - - let res = service.inspect('something.missing'); - assert.strictEqual(res.value, undefined); - assert.strictEqual(res.defaultValue, undefined); - assert.strictEqual(res.userValue, undefined); - - res = service.inspect('lookup.service.testSetting'); - assert.strictEqual(res.defaultValue, 'isSet'); - assert.strictEqual(res.value, 'isSet'); - assert.strictEqual(res.userValue, undefined); - - fs.writeFileSync(r.testFile, '{ "lookup.service.testSetting": "bar" }'); - - await service.reloadConfiguration(); - res = service.inspect('lookup.service.testSetting'); - assert.strictEqual(res.defaultValue, 'isSet'); - assert.strictEqual(res.userValue, 'bar'); - assert.strictEqual(res.value, 'bar'); - - service.dispose(); - return r.cleanUp(); - }); - - test('lookup with null', async () => { - const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); - configurationRegistry.registerConfiguration({ - 'id': '_testNull', - 'type': 'object', - 'properties': { - 'lookup.service.testNullSetting': { - 'type': 'null', - } - } - }); - - const r = await testFile('config', 'config.json'); - const service = new ConfigurationService(URI.file(r.testFile), fileService); - service.initialize(); - - let res = service.inspect('lookup.service.testNullSetting'); - assert.strictEqual(res.defaultValue, null); - assert.strictEqual(res.value, null); - assert.strictEqual(res.userValue, undefined); - - fs.writeFileSync(r.testFile, '{ "lookup.service.testNullSetting": null }'); - - await service.reloadConfiguration(); - - res = service.inspect('lookup.service.testNullSetting'); - assert.strictEqual(res.defaultValue, null); - assert.strictEqual(res.value, null); - assert.strictEqual(res.userValue, null); - - service.dispose(); - return r.cleanUp(); - }); -}); diff --git a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts index 1bba68b6dd..9fc7bb1ce4 100644 --- a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts +++ b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts @@ -63,8 +63,6 @@ export class IndexedDB { } - - class IndexedDBFileSystemProvider extends KeyValueFileSystemProvider { constructor(scheme: string, private readonly database: IDBDatabase, private readonly store: string) { diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index bd858b2bd7..dc181d03d1 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -26,19 +26,19 @@ export class FileService extends Disposable implements IFileService { private readonly BUFFER_SIZE = 64 * 1024; - constructor(@ILogService private logService: ILogService) { + constructor(@ILogService private readonly logService: ILogService) { super(); } //#region File System Provider - private _onDidChangeFileSystemProviderRegistrations = this._register(new Emitter()); + private readonly _onDidChangeFileSystemProviderRegistrations = this._register(new Emitter()); readonly onDidChangeFileSystemProviderRegistrations = this._onDidChangeFileSystemProviderRegistrations.event; - private _onWillActivateFileSystemProvider = this._register(new Emitter()); + private readonly _onWillActivateFileSystemProvider = this._register(new Emitter()); readonly onWillActivateFileSystemProvider = this._onWillActivateFileSystemProvider.event; - private _onDidChangeFileSystemProviderCapabilities = this._register(new Emitter()); + private readonly _onDidChangeFileSystemProviderCapabilities = this._register(new Emitter()); readonly onDidChangeFileSystemProviderCapabilities = this._onDidChangeFileSystemProviderCapabilities.event; private readonly provider = new Map(); @@ -146,10 +146,10 @@ export class FileService extends Disposable implements IFileService { //#endregion - private _onDidRunOperation = this._register(new Emitter()); + private readonly _onDidRunOperation = this._register(new Emitter()); readonly onDidRunOperation = this._onDidRunOperation.event; - private _onError = this._register(new Emitter()); + private readonly _onError = this._register(new Emitter()); readonly onError = this._onError.event; //#region File Metadata Resolving @@ -881,10 +881,10 @@ export class FileService extends Disposable implements IFileService { //#region File Watching - private _onDidFilesChange = this._register(new Emitter()); + private readonly _onDidFilesChange = this._register(new Emitter()); readonly onDidFilesChange = this._onDidFilesChange.event; - private activeWatchers = new Map(); + private readonly activeWatchers = new Map(); watch(resource: URI, options: IWatchOptions = { recursive: false, excludes: [] }): IDisposable { let watchDisposed = false; @@ -950,7 +950,7 @@ export class FileService extends Disposable implements IFileService { //#region Helpers - private writeQueues: Map> = new Map(); + private readonly writeQueues: Map> = new Map(); private ensureWriteQueue(provider: IFileSystemProvider, resource: URI): Queue { const { extUri } = this.getExtUri(provider); diff --git a/src/vs/platform/files/electron-browser/diskFileSystemProvider.ts b/src/vs/platform/files/electron-browser/diskFileSystemProvider.ts index 3da323fe96..146c7ba342 100644 --- a/src/vs/platform/files/electron-browser/diskFileSystemProvider.ts +++ b/src/vs/platform/files/electron-browser/diskFileSystemProvider.ts @@ -15,7 +15,7 @@ export class DiskFileSystemProvider extends NodeDiskFileSystemProvider { constructor( logService: ILogService, - private electronService: IElectronService, + private readonly electronService: IElectronService, options?: IDiskFileSystemProviderOptions ) { super(logService, options); diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 8856be9eda..13b2217b6a 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -46,7 +46,10 @@ export class DiskFileSystemProvider extends Disposable implements private readonly BUFFER_SIZE = this.options?.bufferSize || 64 * 1024; - constructor(private logService: ILogService, private options?: IDiskFileSystemProviderOptions) { + constructor( + private readonly logService: ILogService, + private readonly options?: IDiskFileSystemProviderOptions + ) { super(); } @@ -198,9 +201,9 @@ export class DiskFileSystemProvider extends Disposable implements } } - private mapHandleToPos: Map = new Map(); + private readonly mapHandleToPos: Map = new Map(); - private writeHandles: Set = new Set(); + private readonly writeHandles: Set = new Set(); private canFlush: boolean = true; async open(resource: URI, opts: FileOpenOptions): Promise { @@ -502,14 +505,14 @@ export class DiskFileSystemProvider extends Disposable implements //#region File Watching - private _onDidWatchErrorOccur = this._register(new Emitter()); + private readonly _onDidWatchErrorOccur = this._register(new Emitter()); readonly onDidErrorOccur = this._onDidWatchErrorOccur.event; - private _onDidChangeFile = this._register(new Emitter()); + private readonly _onDidChangeFile = this._register(new Emitter()); readonly onDidChangeFile = this._onDidChangeFile.event; private recursiveWatcher: WindowsWatcherService | UnixWatcherService | NsfwWatcherService | undefined; - private recursiveFoldersToWatch: { path: string, excludes: string[] }[] = []; + private readonly recursiveFoldersToWatch: { path: string, excludes: string[] }[] = []; private recursiveWatchRequestDelayer = this._register(new ThrottledDelayer(0)); private recursiveWatcherLogLevelListener: IDisposable | undefined; diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 0184293387..b67768e380 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -179,6 +179,8 @@ function toWorkbenchListOptions(options: IListOptions, configurationServic } }; + result.smoothScrolling = configurationService.getValue(listSmoothScrolling); + return [result, disposables]; } @@ -193,7 +195,6 @@ export interface IWorkbenchListOptions extends IWorkbenchListOptionsUpdate, I export class WorkbenchList extends List { readonly contextKeyService: IContextKeyService; - private readonly configurationService: IConfigurationService; private readonly themeService: IThemeService; private listHasSelectionOrFocus: IContextKey; @@ -231,7 +232,6 @@ export class WorkbenchList extends List { this.disposables.add(workbenchListOptionsDisposable); this.contextKeyService = createScopedContextKeyService(contextKeyService, this); - this.configurationService = configurationService; this.themeService = themeService; const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); @@ -265,8 +265,25 @@ export class WorkbenchList extends List { this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0); })); + this.disposables.add(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(multiSelectModifierSettingKey)) { + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); + } - this.registerListeners(); + let options: IListOptionsUpdate = {}; + + if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) { + const horizontalScrolling = configurationService.getValue(horizontalScrollingKey); + options = { ...options, horizontalScrolling }; + } + if (e.affectsConfiguration(listSmoothScrolling)) { + const smoothScrolling = configurationService.getValue(listSmoothScrolling); + options = { ...options, smoothScrolling }; + } + if (Object.keys(options).length > 0) { + this.updateOptions(options); + } + })); } updateOptions(options: IWorkbenchListOptionsUpdate): void { @@ -292,18 +309,6 @@ export class WorkbenchList extends List { this._styler = attachListStyler(this, this.themeService, styles); } - private registerListeners(): void { - this.disposables.add(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(multiSelectModifierSettingKey)) { - this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService); - } - if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) { - const horizontalScrolling = this.configurationService.getValue(horizontalScrollingKey); - this.updateOptions({ horizontalScrolling }); - } - })); - } - get useAltAsMultipleSelectionModifier(): boolean { return this._useAltAsMultipleSelectionModifier; } @@ -316,7 +321,6 @@ export interface IWorkbenchPagedListOptions extends IWorkbenchListOptionsUpda export class WorkbenchPagedList extends PagedList { readonly contextKeyService: IContextKeyService; - private readonly configurationService: IConfigurationService; private readonly disposables: DisposableStore; @@ -350,7 +354,6 @@ export class WorkbenchPagedList extends PagedList { this.disposables.add(workbenchListOptionsDisposable); this.contextKeyService = createScopedContextKeyService(contextKeyService, this); - this.configurationService = configurationService; this.horizontalScrolling = options.horizontalScrolling; const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); @@ -365,17 +368,23 @@ export class WorkbenchPagedList extends PagedList { this.disposables.add(attachListStyler(this, themeService, options.overrideStyles)); } - this.registerListeners(); - } - - private registerListeners(): void { - this.disposables.add(this.configurationService.onDidChangeConfiguration(e => { + this.disposables.add(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(multiSelectModifierSettingKey)) { - this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService); + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); } + + let options: IListOptionsUpdate = {}; + if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) { - const horizontalScrolling = this.configurationService.getValue(horizontalScrollingKey); - this.updateOptions({ horizontalScrolling }); + const horizontalScrolling = configurationService.getValue(horizontalScrollingKey); + options = { ...options, horizontalScrolling }; + } + if (e.affectsConfiguration(listSmoothScrolling)) { + const smoothScrolling = configurationService.getValue(listSmoothScrolling); + options = { ...options, smoothScrolling }; + } + if (Object.keys(options).length > 0) { + this.updateOptions(options); } })); } @@ -836,7 +845,8 @@ function workbenchTreeDataPreamble { const horizontalScrolling = configurationService.getValue(horizontalScrollingKey); newOptions = { ...newOptions, horizontalScrolling }; } + if (e.affectsConfiguration(openModeSettingKey)) { + newOptions = { ...newOptions, expandOnlyOnDoubleClick: configurationService.getValue(openModeSettingKey) === 'doubleClick' }; + } if (Object.keys(newOptions).length > 0) { tree.updateOptions(newOptions); } diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 92cf82f52a..907472c2ee 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -263,7 +263,7 @@ export class NativeStorageService extends Disposable implements IStorageService async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { if (this.workspaceStoragePath === SQLiteStorageDatabase.IN_MEMORY_PATH) { - return Promise.resolve(); // no migration needed if running in memory + return; // no migration needed if running in memory } // Close workspace DB to be able to copy diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index fb93d7874f..d90e13c8f8 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -219,8 +219,14 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i this.telemetryService.publicLog2<{ code: string }, AutoSyncErrorClassification>(`autosync/error`, { code: userDataSyncError.code }); } - // Turned off from another device or session got expired - if (userDataSyncError.code === UserDataSyncErrorCode.TurnedOff || userDataSyncError.code === UserDataSyncErrorCode.SessionExpired) { + // Session got expired + if (userDataSyncError.code === UserDataSyncErrorCode.SessionExpired) { + await this.turnOff(false, true /* force soft turnoff on error */); + this.logService.info('Auto Sync: Turned off sync because current session is expired'); + } + + // Turned off from another device + if (userDataSyncError.code === UserDataSyncErrorCode.TurnedOff) { await this.turnOff(false, true /* force soft turnoff on error */); this.logService.info('Auto Sync: Turned off sync because sync is turned off in the cloud'); } diff --git a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts index 39f68f11e3..6df0716659 100644 --- a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts @@ -156,8 +156,7 @@ export class WebviewProtocolProvider extends Disposable { rewriteUri = (uri) => { if (metadata.remoteConnectionData) { if (uri.scheme === Schemas.vscodeRemote || (metadata.extensionLocation?.scheme === REMOTE_HOST_SCHEME)) { - const scheme = metadata.remoteConnectionData.host === 'localhost' || metadata.remoteConnectionData.host === '127.0.0.1' ? 'http' : 'https'; - return URI.parse(`${scheme}://${metadata.remoteConnectionData.host}:${metadata.remoteConnectionData.port}`).with({ + return URI.parse(`http://${metadata.remoteConnectionData.host}:${metadata.remoteConnectionData.port}`).with({ path: '/vscode-remote-resource', query: `tkn=${metadata.remoteConnectionData.connectionToken}&path=${encodeURIComponent(uri.path)}`, }); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 1e38a3de30..49697255d4 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1734,8 +1734,8 @@ declare module 'vscode' { /** * Dialog title. * - * Depending on the underlying operating system this parameter might be ignored, since some - * systems do not present title on open dialogs. + * This parameter might be ignored, as not all operating systems display a title on open dialogs + * (for example, macOS). */ title?: string; } @@ -1769,8 +1769,8 @@ declare module 'vscode' { /** * Dialog title. * - * Depending on the underlying operating system this parameter might be ignored, since some - * systems do not present title on save dialogs. + * This parameter might be ignored, as not all operating systems display a title on save dialogs + * (for example, macOS). */ title?: string; } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 1db78c002b..aa1aadf0b4 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -830,12 +830,11 @@ declare module 'vscode' { noDebug?: boolean; /** - * Controls if the debug session created will be compacted with the parent in the CALL STACK view. - * Compact with the parent is only done if the session is the only child of it's parent session. - * Default is to compact. - * + * Controls if the debug session's parent session is shown in the CALL STACK view even if it has only a single child. + * By default, the debug session will never hide its parent. + * If compact is true, debug sessions with a single child are hidden in the CALL STACK view to make the tree more compact. */ - noCompact?: boolean; + compact?: boolean; } // deprecated debug API @@ -1053,7 +1052,9 @@ declare module 'vscode' { export interface TerminalLinkProvider { /** - * Provide terminal links for the given context. + * Provide terminal links for the given context. Note that this can be called multiple times + * even before previous calls resolve, make sure to not share global objects (eg. `RegExp`) + * that could have problems when asynchronous usage may overlap. * @param context Information about what links are being provided for. */ provideTerminalLinks(context: TerminalLinkContext): ProviderResult diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 343b42a568..70773192fb 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -232,7 +232,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb noDebug: options.noDebug, parentSession: this.getSession(options.parentSessionID), repl: options.repl, - noCompact: options.noCompact + compact: options.compact }; return this.debugService.startDebugging(launch, nameOrConfig, debugOptions).then(success => { return success; diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index a6eeaa27f9..4016734840 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -30,12 +30,14 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation): void { // if there are icons in the text use the tooltip for the aria label let ariaLabel: string; + let role: string | undefined = undefined; if (accessibilityInformation) { ariaLabel = accessibilityInformation.label; + role = accessibilityInformation.role; } else { ariaLabel = text ? text.replace(MainThreadStatusBar.CODICON_REGEXP, (_match, codiconName) => codiconName) : ''; } - const entry: IStatusbarEntry = { text, tooltip, command, color, ariaLabel }; + const entry: IStatusbarEntry = { text, tooltip, command, color, ariaLabel, role }; if (typeof priority === 'undefined') { priority = 0; diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 7a2d67679f..c3d4221cd1 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -368,7 +368,7 @@ namespace TaskDTO { const label = nls.localize('task.label', '{0}: {1}', source.label, task.name); const definition = TaskDefinitionDTO.to(task.definition, executeOnly)!; - const id = `${task.source.extensionId}.${definition._key}`; + const id = (CustomExecutionDTO.is(task.execution!) && task._id) ? task._id : `${task.source.extensionId}.${definition._key}`; const result: ContributedTask = new ContributedTask( id, // uuidMap.getUUID(identifier) source, @@ -534,20 +534,47 @@ export class MainThreadTask implements MainThreadTaskShape { } } - public $executeTask(value: TaskDTO): Promise { + // Passing in a TaskHandleDTO will cause the task to get re-resolved, which is important for tasks are coming from the core, + // such as those gotten from a fetchTasks, since they can have missing configuration properties. + public $executeTask(value: TaskHandleDTO | TaskDTO): Promise { return new Promise((resolve, reject) => { - const task = TaskDTO.to(value, this._workspaceContextServer, true)!; - this._taskService.run(task).then(undefined, reason => { - // eat the error, it has already been surfaced to the user and we don't care about it here - }); - const result: TaskExecutionDTO = { - id: task._id, - task: TaskDTO.from(task) - }; - resolve(result); + if (TaskHandleDTO.is(value)) { + const workspaceFolder = typeof value.workspaceFolder === 'string' ? value.workspaceFolder : this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder)); + if (workspaceFolder) { + this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task | undefined) => { + if (!task) { + reject(new Error('Task not found')); + } else { + this._taskService.run(task).then(undefined, reason => { + // eat the error, it has already been surfaced to the user and we don't care about it here + }); + const result: TaskExecutionDTO = { + id: value.id, + task: TaskDTO.from(task) + }; + resolve(result); + } + }, (_error) => { + reject(new Error('Task not found')); + }); + } else { + reject(new Error('No workspace folder')); + } + } else { + const task = TaskDTO.to(value, this._workspaceContextServer, true)!; + this._taskService.run(task).then(undefined, reason => { + // eat the error, it has already been surfaced to the user and we don't care about it here + }); + const result: TaskExecutionDTO = { + id: task._id, + task: TaskDTO.from(task) + }; + resolve(result); + } }); } + public $customExecutionComplete(id: string, result?: number): Promise { return new Promise((resolve, reject) => { this._taskService.getActiveTasks().then((tasks) => { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b3fde309ae..438c6610a9 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -784,7 +784,7 @@ export interface MainThreadTaskShape extends IDisposable { $unregisterTaskProvider(handle: number): Promise; $fetchTasks(filter?: tasks.TaskFilterDTO): Promise; $getTaskExecution(value: tasks.TaskHandleDTO | tasks.TaskDTO): Promise; - $executeTask(task: tasks.TaskDTO): Promise; + $executeTask(task: tasks.TaskHandleDTO | tasks.TaskDTO): Promise; $terminateTask(id: string): Promise; $registerTaskSystem(scheme: string, info: tasks.TaskSystemInfoDTO): void; $customExecutionComplete(id: string, result?: number): Promise; @@ -862,7 +862,7 @@ export interface IStartDebuggingOptions { parentSessionID?: DebugSessionUUID; repl?: IDebugSessionReplMode; noDebug?: boolean; - noCompact?: boolean; + compact?: boolean; } export interface MainThreadDebugServiceShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 8f195da15b..b93af2ef9a 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -297,7 +297,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E parentSessionID: options.parentSession ? options.parentSession.id : undefined, repl: options.consoleMode === DebugConsoleMode.MergeWithParent ? 'mergeWithParent' : 'separate', noDebug: options.noDebug, - noCompact: options.noCompact + compact: options.compact }); } diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index 8e7f92763e..d9338ed290 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -117,6 +117,11 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { this.update(); } + public set accessibilityInformation(accessibilityInformation: vscode.AccessibilityInformation | undefined) { + this._accessibilityInformation = accessibilityInformation; + this.update(); + } + public show(): void { this._visible = true; this.update(); diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index bb6ad49365..1adb2c47e0 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -367,7 +367,7 @@ export class ExtHostTreeView extends Disposable { if (node) { const resolve = await this.dataProvider.resolveTreeItem(element, node.extensionItem); // Resolvable elements. Currently only tooltip. - node.item.tooltip = resolve.tooltip; + node.item.tooltip = this.getTooltip(resolve.tooltip); return node.item; } } diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 1e95493ca7..2eb3769373 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -345,7 +345,7 @@ namespace schema { type: 'string' }, icon: { - description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path, an object with file paths for dark and light themes, or a theme icon references, like `$(zap)`'), + description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path, an object with file paths for dark and light themes, or a theme icon references, like `\\$(zap)`'), anyOf: [{ type: 'string' }, diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 51c2674d1e..2f92608221 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -54,12 +54,13 @@ export class ExtHostTask extends ExtHostTaskBase { // We have a preserved ID. So the task didn't change. if (tTask._id !== undefined) { // Always get the task execution first to prevent timing issues when retrieving it later - const executionDTO = await this._proxy.$getTaskExecution(TaskHandleDTO.from(tTask)); + const handleDto = TaskHandleDTO.from(tTask); + const executionDTO = await this._proxy.$getTaskExecution(handleDto); if (executionDTO.task === undefined) { throw new Error('Task from execution DTO is undefined'); } const execution = await this.getTaskExecution(executionDTO, task); - this._proxy.$executeTask(executionDTO.task).catch(() => { /* The error here isn't actionable. */ }); + this._proxy.$executeTask(handleDto).catch(() => { /* The error here isn't actionable. */ }); return execution; } else { const dto = TaskDTO.from(task, extension); diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 77263bfa7e..6ff9829ea0 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -210,7 +210,7 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorContext.set(activeEditorPane.getId()); this.activeEditorIsReadonly.set(activeEditorPane.input.isReadonly()); - const editors = activeEditorPane.input.resource ? this.editorService.getEditorOverrides(activeEditorPane.input.resource, undefined, activeGroup, undefined) : []; + const editors = activeEditorPane.input.resource ? this.editorService.getEditorOverrides(activeEditorPane.input.resource, undefined, activeGroup) : []; this.activeEditorAvailableEditorIds.set(editors.map(([_, entry]) => entry.id).join(',')); } else { this.activeEditorContext.reset(); diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index ad640525df..5ad5454aea 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -418,7 +418,7 @@ export class CompositeBar extends Widget implements ICompositeBar { // Case: we closed the last visible composite // Solv: we hide the part - else if (this.visibleComposites.length === 1) { + else if (this.visibleComposites.length === 0) { this.options.hidePart(); } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index ceee512188..aa8091c2f2 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -38,7 +38,7 @@ import { ResourceLabel } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; import { BreadcrumbsPicker, createBreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker'; -import { IEditorPartOptions, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorPartOptions, toResource, SideBySideEditor, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions } from 'vs/workbench/common/editor'; import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -49,6 +49,7 @@ import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { Registry } from 'vs/platform/registry/common/platform'; class Item extends BreadcrumbsItem { @@ -248,12 +249,23 @@ export class BreadcrumbsControl { } } + // display uri which can be derived from file input + let fileInfoUri = uri; + let input = this._editorGroup.activeEditor; + if (input instanceof SideBySideEditorInput) { + input = input.primary; + } + if (Registry.as(Extensions.EditorInputFactories).getFileEditorInputFactory().isFileEditorInput(input)) { + fileInfoUri = input.label; + } + this.domNode.classList.toggle('hidden', false); this._ckBreadcrumbsVisible.set(true); this._ckBreadcrumbsPossible.set(true); const editor = this._getActiveCodeEditor(); const model = new EditorBreadcrumbsModel( + fileInfoUri, uri, editor, this._configurationService, this._textResourceConfigurationService, diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 5b4c94269d..ec183398b2 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -52,6 +52,7 @@ export class EditorBreadcrumbsModel { readonly onDidUpdate: Event = this._onDidUpdate.event; constructor( + fileInfoUri: URI, private readonly _uri: URI, private readonly _editor: ICodeEditor | undefined, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -64,7 +65,7 @@ export class EditorBreadcrumbsModel { this._disposables.add(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this))); this._disposables.add(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this))); - this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(this._uri, workspaceService); + this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(fileInfoUri, workspaceService); this._bindToEditor(); this._onDidUpdate.fire(this); } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index d9bf6a23f7..7b95537ccd 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -457,7 +457,6 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCo MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '5_split', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '5_split', order: 30 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitRight', "Split Right") }, group: '5_split', order: 40 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: ReopenResourcesAction.ID, title: ReopenResourcesAction.LABEL }, group: '6_reopen', order: 20, when: ActiveEditorAvailableEditorIdsContext }); // Editor Title Menu MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_DIFF_SIDE_BY_SIDE, title: nls.localize('toggleInlineView', "Toggle Inline View") }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') }); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 9eebc906c5..a510eac4f7 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -765,6 +765,9 @@ class StatusbarEntryItem extends Disposable { this.container.setAttribute('aria-label', entry.ariaLabel); this.labelContainer.setAttribute('aria-label', entry.ariaLabel); } + if (!this.entry || entry.role !== this.entry.role) { + this.labelContainer.setAttribute('role', entry.role || 'button'); + } // Update: Tooltip (on the container, because label can be disabled) if (!this.entry || entry.tooltip !== this.entry.tooltip) { diff --git a/src/vs/workbench/browser/parts/views/viewsService.ts b/src/vs/workbench/browser/parts/views/viewsService.ts index 84688f451c..45ce6c5c04 100644 --- a/src/vs/workbench/browser/parts/views/viewsService.ts +++ b/src/vs/workbench/browser/parts/views/viewsService.ts @@ -275,6 +275,11 @@ export class ViewsService extends Disposable implements IViewsService { return viewContainerId ? this.viewDescriptorService.getViewContainerById(viewContainerId) : null; } + getActiveViewPaneContainerWithId(viewContainerId: string): IViewPaneContainer | null { + const viewContainer = this.viewDescriptorService.getViewContainerById(viewContainerId); + return viewContainer ? this.getActiveViewPaneContainer(viewContainer) : null; + } + async openViewContainer(id: string, focus?: boolean): Promise { const viewContainer = this.viewDescriptorService.getViewContainerById(id); if (viewContainer) { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 8a3a595afb..dc62f29ef7 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -484,6 +484,7 @@ export interface IViewsService { openViewContainer(id: string, focus?: boolean): Promise; closeViewContainer(id: string): void; getVisibleViewContainer(location: ViewContainerLocation): ViewContainer | null; + getActiveViewPaneContainerWithId(viewContainerId: string): IViewPaneContainer | null; // View APIs readonly onDidChangeViewVisibility: Event<{ id: string, visible: boolean }>; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index c040f27b5c..5f2b7d081c 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -451,7 +451,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo open: (editor, options, group) => { return this.onEditorOpening(editor, options, group); }, - getEditorOverrides: (resource: URI, _options: IEditorOptions | undefined, group: IEditorGroup | undefined, id: string | undefined): IOpenEditorOverrideEntry[] => { + getEditorOverrides: (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[] => { const currentEditor = group?.editors.find(editor => isEqual(editor.resource, resource)); const defaultEditorOverride: IOpenEditorOverrideEntry = { @@ -468,14 +468,14 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo }; }; - if (typeof id === 'string') { + if (typeof options?.override === 'string') { // A specific override was requested. Only return it. - if (id === defaultEditorOverride.id) { + if (options.override === defaultEditorOverride.id) { return [defaultEditorOverride]; } - const matchingEditor = this.customEditorService.getCustomEditor(id); + const matchingEditor = this.customEditorService.getCustomEditor(options.override); return matchingEditor ? [toOverride(matchingEditor)] : []; } diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 19b74336c5..165e1a8a12 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -217,7 +217,7 @@ export class CallStackView extends ViewPane { this.dataSource = new CallStackDataSource(this.debugService); const sessionsRenderer = this.instantiationService.createInstance(SessionsRenderer, this.menu); - this.tree = >this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), new CallStackCompressionDelegate(), [ + this.tree = >this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), new CallStackCompressionDelegate(this.debugService), [ sessionsRenderer, new ThreadsRenderer(this.instantiationService), this.instantiationService.createInstance(StackFramesRenderer), @@ -355,6 +355,10 @@ export class CallStackView extends ViewPane { const sessionListeners: IDisposable[] = []; sessionListeners.push(s.onDidChangeName(() => this.tree.rerender(s))); sessionListeners.push(s.onDidEndAdapter(() => dispose(sessionListeners))); + if (s.parentSession) { + // A session we already expanded has a new child session, allow to expand it again. + this.autoExpandedSessions.delete(s.parentSession); + } })); } @@ -517,18 +521,18 @@ class SessionsRenderer implements ICompressibleTreeRenderer, _: number, data: ISessionTemplateData): void { - this.doRenderElement(element.element, element.element.getLabel(), createMatches(element.filterData), data); + this.doRenderElement(element.element, createMatches(element.filterData), data); } renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: ISessionTemplateData, height: number | undefined): void { const lastElement = node.element.elements[node.element.elements.length - 1]; const matches = createMatches(node.filterData); - this.doRenderElement(lastElement, node.element.elements[0].getLabel(), matches, templateData); + this.doRenderElement(lastElement, matches, templateData); } - private doRenderElement(session: IDebugSession, label: string, matches: IMatch[], data: ISessionTemplateData): void { + private doRenderElement(session: IDebugSession, matches: IMatch[], data: ISessionTemplateData): void { data.session.title = nls.localize({ key: 'session', comment: ['Session is a noun'] }, "Session"); - data.label.set(label, matches); + data.label.set(session.getLabel(), matches); const thread = session.getAllThreads().find(t => t.stopped); const setActionBar = () => { @@ -1100,9 +1104,20 @@ class ContinueAction extends Action { } class CallStackCompressionDelegate implements ITreeCompressionDelegate { + + constructor(private readonly debugService: IDebugService) { } + isIncompressible(stat: CallStackItem): boolean { if (isDebugSession(stat)) { - return stat.noCompact; + if (stat.compact) { + return false; + } + const sessions = this.debugService.getModel().getSessions(); + if (sessions.some(s => s.parentSession === stat && s.compact)) { + return false; + } + + return true; } return true; diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 43ad6b8efe..a195f15ec1 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -348,8 +348,8 @@ export class DebugService implements IDebugService { } if (configOrName && !config) { - const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", typeof configOrName === 'string' ? configOrName : JSON.stringify(configOrName)) : - nls.localize('launchJsonDoesNotExist', "'launch.json' does not exist."); + const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", typeof configOrName === 'string' ? configOrName : configOrName.name) : + nls.localize('launchJsonDoesNotExist', "'launch.json' does not exist for passed workspace folder."); throw new Error(message); } @@ -358,6 +358,7 @@ export class DebugService implements IDebugService { return result; } catch (err) { // make sure to get out of initializing state, and propagate the result + this.notificationService.error(err); this.endInitializingState(); return Promise.reject(err); } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index f8689e410e..84386da97b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -129,8 +129,8 @@ export class DebugSession implements IDebugSession { return this._options.parentSession; } - get noCompact(): boolean { - return !!this._options.noCompact; + get compact(): boolean { + return !!this._options.compact; } setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig | undefined }) { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index a43ce3d3cb..60f4a6e84a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -142,6 +142,7 @@ .debug-pane .debug-call-stack .monaco-list-row .monaco-action-bar { display: none; flex-shrink: 0; + margin-right: 1px; } .debug-pane .debug-call-stack .monaco-list-row:hover .monaco-action-bar { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index abcf82f4fd..33a6e68472 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -606,10 +606,17 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { await this.clipboardService.writeText(this.getVisibleContent()); return Promise.resolve(); })); - actions.push(new Action('debug.replPaste', localize('paste', "Paste"), undefined, true, async () => { + actions.push(new Action('debug.replPaste', localize('paste', "Paste"), undefined, this.debugService.state !== State.Inactive, async () => { const clipboardText = await this.clipboardService.readText(); if (clipboardText) { this.replInput.setValue(this.replInput.getValue().concat(clipboardText)); + this.replInput.focus(); + const model = this.replInput.getModel(); + const lineNumber = model ? model.getLineCount() : 0; + const column = model?.getLineMaxColumn(lineNumber); + if (typeof lineNumber === 'number' && typeof column === 'number') { + this.replInput.setPosition({ lineNumber, column }); + } } })); actions.push(new Separator()); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 7bddb2e643..e21fa5eccb 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -157,7 +157,7 @@ export interface IDebugSessionOptions { parentSession?: IDebugSession; repl?: IDebugSessionReplMode; compoundRoot?: DebugCompoundRoot; - noCompact?: boolean; + compact?: boolean; } export interface IDebugSession extends ITreeElement { @@ -168,7 +168,7 @@ export interface IDebugSession extends ITreeElement { readonly root: IWorkspaceFolder | undefined; readonly parentSession: IDebugSession | undefined; readonly subId: string | undefined; - readonly noCompact: boolean; + readonly compact: boolean; setSubId(subId: string | undefined): void; diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 0c267d0b65..e0778f73e7 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -157,7 +157,7 @@ export class MockSession implements IDebugSession { subId: string | undefined; - get noCompact(): boolean { + get compact(): boolean { return false; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index eb466fdc99..c2c8396fae 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -225,24 +225,23 @@ export class ExtensionEditor extends BaseEditor { builtin.textContent = localize('builtin', "Built-in"); const subtitle = append(details, $('.subtitle')); - const publisher = append(subtitle, $('span.publisher.clickable', { title: localize('publisher', "Publisher name"), tabIndex: 0 })); - + const publisher = append(append(subtitle, $('.subtitle-entry')), $('span.publisher.clickable', { title: localize('publisher', "Publisher name"), tabIndex: 0 })); // {{SQL CARBON EDIT}} remove rating and install count widgets - // const installCount = append(subtitle, $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 })); + // const installCount = append(append(subtitle, $('.subtitle-entry')), $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 })); - // const rating = append(subtitle, $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 })); + // const rating = append(append(subtitle, $('.subtitle-entry')), $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 })); - const repository = append(subtitle, $('span.repository.clickable')); + const repository = append(append(subtitle, $('.subtitle-entry')), $('span.repository.clickable')); repository.textContent = localize('repository', 'Repository'); repository.style.display = 'none'; repository.tabIndex = 0; - const license = append(subtitle, $('span.license.clickable')); + const license = append(append(subtitle, $('.subtitle-entry')), $('span.license.clickable')); license.textContent = localize('license', 'License'); license.style.display = 'none'; license.tabIndex = 0; - const version = append(subtitle, $('span.version')); + const version = append(append(subtitle, $('.subtitle-entry')), $('span.version')); version.textContent = localize('version', 'Version'); const description = append(details, $('.description')); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f8251ef534..f081050c0f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -19,7 +19,7 @@ import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/brow import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, ShowBuiltInExtensionsAction, UpdateAllAction, - EnableAllAction, EnableAllWorkspaceAction, DisableAllAction, DisableAllWorkspaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction + EnableAllAction, EnableAllWorkspaceAction, DisableAllAction, DisableAllWorkspaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction, ClearExtensionsSearchResultsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; @@ -150,6 +150,7 @@ actionRegistry.registerWorkbenchAction(enableAllWorkspaceAction, 'Extensions: En const checkForUpdatesAction = SyncActionDescriptor.from(CheckForUpdatesAction); actionRegistry.registerWorkbenchAction(checkForUpdatesAction, `Extensions: Check for Extension Updates`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ClearExtensionsSearchResultsAction), 'Extensions: Clear Extensions Search Results', ExtensionsLabel); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(EnableAutoUpdateAction), `Extensions: Enable Auto Updating Extensions`, ExtensionsLabel); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(DisableAutoUpdateAction), `Extensions: Disable Auto Updating Extensions`, ExtensionsLabel); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(InstallSpecificVersionOfExtensionAction), 'Install Specific Version of Extension...', ExtensionsLabel); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 344628760d..8395a4c3ba 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -60,6 +60,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { Codicon } from 'vs/base/common/codicons'; +import { IViewsService } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; // {{SQL CARBON EDIT}} import product from 'vs/platform/product/common/product'; @@ -1662,19 +1663,39 @@ export class ShowDisabledExtensionsAction extends Action { } } -export class ClearExtensionsInputAction extends Action { +export class ClearExtensionsSearchResultsAction extends Action { - static readonly ID = 'workbench.extensions.action.clearExtensionsInput'; - static readonly LABEL = localize('clearExtensionsInput', "Clear Extensions Search Results"); + static readonly ID = 'workbench.extensions.action.clearExtensionsSearchResults'; + static readonly LABEL = localize('clearExtensionsSearchResults', "Clear Extensions Search Results"); + + constructor( + id: string, + label: string, + @IViewsService private readonly viewsService: IViewsService + ) { + super(id, label, 'codicon-clear-all', true); + } + + async run(): Promise { + const viewPaneContainer = this.viewsService.getActiveViewPaneContainerWithId(VIEWLET_ID); + if (viewPaneContainer) { + const extensionsViewPaneContainer = viewPaneContainer as IExtensionsViewPaneContainer; + extensionsViewPaneContainer.search(''); + extensionsViewPaneContainer.focus(); + } + } +} + +export class ClearExtensionsInputAction extends ClearExtensionsSearchResultsAction { constructor( id: string, label: string, onSearchChange: Event, value: string, - @IViewletService private readonly viewletService: IViewletService + @IViewsService viewsService: IViewsService ) { - super(id, label, 'codicon-clear-all', true); + super(id, label, viewsService); this.onSearchChange(value); this._register(onSearchChange(this.onSearchChange, this)); } @@ -1683,14 +1704,6 @@ export class ClearExtensionsInputAction extends Action { this.enabled = !!value; } - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search(''); - viewlet.focus(); - }); - } } export class ShowBuiltInExtensionsAction extends Action { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index a6efaa0e8e..fb5e853e40 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -113,7 +113,7 @@ line-height: 20px; } -.extension-editor > .header > .details > .subtitle > .publisher { +.extension-editor > .header > .details > .subtitle .publisher { font-size: 18px; } @@ -124,12 +124,11 @@ align-items: center; } -.extension-editor > .header > .details > .subtitle > .install > .count { +.extension-editor > .header > .details > .subtitle .install > .count { margin-left: 6px; } -.extension-editor > .header > .details > .subtitle > span:not(:first-child):not(:empty), -.extension-editor > .header > .details > .subtitle > a:not(:first-child):not(:empty) { +.extension-editor > .header > .details > .subtitle > div:not(:first-child):not(:empty) { border-left: 1px solid rgba(128, 128, 128, 0.7); margin-left: 14px; padding-left: 14px; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 2f1c296754..516af6f860 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -374,7 +374,12 @@ export class RuntimeExtensionsEditor extends BaseEditor { ] }, "Activated by {1} because searching for {0} took too long", glob, activationId); } else if (activationEvent === 'onStartupFinished') { - title = nls.localize('startupFinishedActivation', "Activated by {0} after start-up finished", activationId); + title = nls.localize({ + key: 'startupFinishedActivation', + comment: [ + 'This refers to an extension. {0} will be an activation event.' + ] + }, "Activated by {0} after start-up finished", activationId); } else if (/^onLanguage:/.test(activationEvent)) { let language = activationEvent.substr('onLanguage:'.length); title = nls.localize('languageActivation', "Activated by {1} because you opened a {0} file", language, activationId); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 225cdb55cf..b72c4653f8 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -486,7 +486,7 @@ export class ExplorerView extends ViewPane { this.rootContext.set(!stat || (stat && stat.isRoot)); if (resource) { - const overrides = resource ? this.editorService.getEditorOverrides(resource, undefined, undefined, undefined) : []; + const overrides = resource ? this.editorService.getEditorOverrides(resource, undefined, undefined) : []; this.availableEditorIdsContext.set(overrides.map(([, entry]) => entry.id).join(',')); } else { this.availableEditorIdsContext.reset(); diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index 83736270b3..088d2871ce 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -8,19 +8,20 @@ export const SCROLLABLE_ELEMENT_PADDING_TOP = 20; // Cell sizing related -export const CELL_MARGIN = 24; -export const CELL_RUN_GUTTER = 32; -export const CODE_CELL_LEFT_MARGIN = 45; +export const CELL_MARGIN = 8; +export const CELL_RUN_GUTTER = 28; +export const CODE_CELL_LEFT_MARGIN = 32; export const EDITOR_TOOLBAR_HEIGHT = 0; -export const BOTTOM_CELL_TOOLBAR_HEIGHT = 32; +export const BOTTOM_CELL_TOOLBAR_HEIGHT = 28; export const CELL_STATUSBAR_HEIGHT = 22; // Margin above editor -export const EDITOR_TOP_MARGIN = 8; -export const CELL_BOTTOM_MARGIN = 8; +export const EDITOR_TOP_MARGIN = 6; +export const CELL_BOTTOM_MARGIN = 6; // Top and bottom padding inside the monaco editor in a cell, which are included in `cell.editorHeight` export const EDITOR_TOP_PADDING = 12; -export const EDITOR_BOTTOM_PADDING = 12; +export const EDITOR_BOTTOM_PADDING = 4; +export const CELL_OUTPUT_PADDING = 14; diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index ab3d07a16f..e3e688b203 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -211,6 +211,48 @@ outline: none !important; } +/* top and bottom borders on cells */ +.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, +.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:before, +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:after { + content: ""; + position: absolute; + width: 100%; + height: 1px; +} + +/* top border */ +.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:before { + border-top: 1px solid transparent; +} + +.monaco-workbench.hc-black .notebookOverlay .monaco-list .monaco-list-row.focused.cell-editor-focus .cell-focus-indicator-top:before, +.monaco-workbench.hc-black .notebookOverlay .monaco-list .markdown-cell-row.focused.cell-editor-focus:before { + border-top-style: dashed; +} + +/* bottom border */ +.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:after { + border-bottom: 1px solid transparent; +} + +.monaco-workbench.hc-black .notebookOverlay .monaco-list .monaco-list-row.focused.cell-editor-focus .cell-focus-indicator-bottom:before, +.monaco-workbench.hc-black .notebookOverlay .monaco-list .markdown-cell-row.focused.cell-editor-focus:after { + border-bottom-style: dashed; +} + +.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:before { + top: 0; +} +.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, +.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:after { + bottom: 0px; +} + .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu.mouseover, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu:hover { cursor: pointer; @@ -314,7 +356,7 @@ .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container .monaco-toolbar .codicon { margin: 0; - padding: 0 4px; + padding-right: 4px; } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container .monaco-toolbar .actions-container { @@ -398,8 +440,7 @@ position: absolute; box-sizing: border-box; top: 0px; - visibility: hidden; - opacity: 1; + opacity: 0; } .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { @@ -428,7 +469,7 @@ .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row:hover .cell-focus-indicator, .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.cell-output-hover .cell-focus-indicator, .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator { - visibility: visible; + opacity: 1; } .monaco-workbench .notebookOverlay .monaco-list-row .cell-editor-part:before { @@ -582,7 +623,7 @@ /* Adjust margin of first item in markdown cell */ .monaco-workbench .notebookOverlay .cell.markdown div *:first-child { - margin-top: 4px; + margin-top: 0px; } /* h1 tags don't need top margin */ @@ -594,11 +635,12 @@ .monaco-workbench .notebookOverlay .cell.markdown div *:only-child, .monaco-workbench .notebookOverlay .cell.markdown div *:last-child { margin-bottom: 0; + padding-bottom: 0; } /* makes all markdown cells consistent */ .monaco-workbench .notebookOverlay .cell.markdown div { - min-height: 32px; + min-height: 24px; } .monaco-workbench .notebookOverlay .cell.markdown table { @@ -666,15 +708,15 @@ .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator { position: absolute; - top: 11px; - left: 0px; + top: 0; + left: 0; right: 0; + height: 100%; } .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator .codicon { visibility: visible; - padding: 4px; - width: calc(100% - 30px); + padding: 8px 0 0 10px; } /** Theming */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 3389193b58..01a36268a8 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -133,7 +133,7 @@ export class NotebookEditorInput extends EditorInput { } async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - if (this._textModel) { + if (this._textModel && this._textModel.object.isDirty()) { await this._textModel.object.revert(options); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 29e4b49ba6..9440c2daf5 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1313,11 +1313,11 @@ export const notebookCellBorder = registerColor('notebook.cellBorderColor', { hc: PANEL_BORDER }, nls.localize('notebook.cellBorderColor', "The border color for notebook cells.")); -export const focusedEditorIndicator = registerColor('notebook.focusedEditorIndicator', { +export const focusedEditorBorderColor = registerColor('notebook.focusedEditorBorder', { light: focusBorder, dark: focusBorder, hc: focusBorder -}, nls.localize('notebook.focusedEditorIndicator', "The color of the notebook cell editor indicator.")); +}, nls.localize('notebook.focusedEditorBorder', "The color of the notebook cell editor border.")); export const cellStatusIconSuccess = registerColor('notebookStatusSuccessIcon.foreground', { light: debugIconStartForeground, @@ -1348,19 +1348,31 @@ export const CELL_TOOLBAR_SEPERATOR = registerColor('notebook.cellToolbarSeperat dark: Color.fromHex('#808080').transparent(0.35), light: Color.fromHex('#808080').transparent(0.35), hc: contrastBorder -}, nls.localize('cellToolbarSeperator', "The color of the seperator in the cell bottom toolbar")); +}, nls.localize('notebook.cellToolbarSeperator', "The color of the seperator in the cell bottom toolbar")); -export const cellFocusBackground = registerColor('notebook.cellFocusBackground', { +export const focusedCellBackground = registerColor('notebook.focusedCellBackground', { dark: transparent(PANEL_BORDER, .4), light: transparent(listFocusBackground, .4), - hc: PANEL_BORDER -}, nls.localize('cellFocusBackground', "The background color of focused or hovered cells")); + hc: null +}, nls.localize('focusedCellBackground', "The background color of a cell when the cell is focused.")); + +export const cellHoverBackground = registerColor('notebook.cellHoverBackground', { + dark: transparent(focusedCellBackground, .5), + light: transparent(focusedCellBackground, .7), + hc: null +}, nls.localize('notebook.cellHoverBackground', "The background color of a cell when the cell is hovered.")); + +export const focusedCellBorder = registerColor('notebook.focusedCellBorder', { + dark: Color.white.transparent(0.12), + light: Color.black.transparent(0.12), + hc: focusBorder +}, nls.localize('notebook.focusedCellBorder', "The color of the cell's top and bottom border when the cell is focused.")); export const focusedCellShadow = registerColor('notebook.focusedCellShadow', { dark: transparent(widgetShadow, 0.6), light: transparent(widgetShadow, 0.4), hc: Color.transparent -}, nls.localize('cellShadow', "The color of the shadow on the focused or hovered cell")); +}, nls.localize('notebook.focusedCellShadow', "The color of the cell shadow when cells are focused.")); export const cellStatusBarItemHover = registerColor('notebook.cellStatusBarItemHoverBackground', { light: new Color(new RGBA(0, 0, 0, 0.08)), @@ -1457,18 +1469,30 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .monaco-list-row > .monaco-toolbar { border: solid 1px ${cellToolbarSeperator}; }`); } - const cellFocusBackgroundColor = theme.getColor(cellFocusBackground); - if (cellFocusBackgroundColor) { - collector.addRule(`.notebookOverlay .code-cell-row:hover .cell-focus-indicator, - .notebookOverlay .code-cell-row.focused .cell-focus-indicator, - .notebookOverlay .code-cell-row.cell-output-hover .cell-focus-indicator { background-color: ${cellFocusBackgroundColor} !important; }`); - collector.addRule(`.notebookOverlay .markdown-cell-row:hover, - .notebookOverlay .markdown-cell-row.focused { background-color: ${cellFocusBackgroundColor} !important; }`); + const focusedCellBackgroundColor = theme.getColor(focusedCellBackground); + if (focusedCellBackgroundColor) { + collector.addRule(`.notebookOverlay .code-cell-row.focused .cell-focus-indicator, + .notebookOverlay .markdown-cell-row.focused { background-color: ${focusedCellBackgroundColor} !important; }`); } - const focusedEditorIndicatorColor = theme.getColor(focusedEditorIndicator); - if (focusedEditorIndicatorColor) { - collector.addRule(`.notebookOverlay .monaco-list-row.cell-editor-focus .cell-editor-part:before { outline: solid 1px ${focusedEditorIndicatorColor}; }`); + const cellHoverBackgroundColor = theme.getColor(cellHoverBackground); + if (cellHoverBackgroundColor) { + collector.addRule(`.notebookOverlay .code-cell-row:not(.focused):hover .cell-focus-indicator, + .notebookOverlay .code-cell-row:not(.focused).cell-output-hover .cell-focus-indicator, + .notebookOverlay .markdown-cell-row:not(.focused):hover { background-color: ${cellHoverBackgroundColor} !important; }`); + } + + const focusedCellBorderColor = theme.getColor(focusedCellBorder); + collector.addRule(`.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, + .monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:before, + .monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:after { + border-color: ${focusedCellBorderColor} !important; + }`); + + const focusedEditorBorderColorColor = theme.getColor(focusedEditorBorderColor); + if (focusedEditorBorderColorColor) { + collector.addRule(`.notebookOverlay .monaco-list-row.cell-editor-focus .cell-editor-part:before { outline: solid 1px ${focusedEditorBorderColorColor}; }`); } const editorBorderColor = theme.getColor(notebookCellBorder); @@ -1534,23 +1558,22 @@ registerThemingParticipant((theme, collector) => { } // Cell Margin - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell { margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN}px; }`); + collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell { margin: 0px ${CELL_MARGIN * 2}px 0px ${CELL_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell.code { margin-left: ${CODE_CELL_LEFT_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { padding-top: ${EDITOR_TOP_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row { padding-bottom: ${CELL_BOTTOM_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row .cell-bottom-toolbar-container { margin-top: ${CELL_BOTTOM_MARGIN}px; }`); collector.addRule(`.notebookOverlay .output { margin: 0px ${CELL_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); - collector.addRule(`.notebookOverlay .output { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + CELL_MARGIN}px); }`); - collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container { width: calc(100% - ${CELL_MARGIN * 2 + CELL_RUN_GUTTER}px); margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px; }`); + collector.addRule(`.notebookOverlay .output { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + (CELL_MARGIN * 2)}px); }`); + collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container { width: calc(100% - ${CELL_MARGIN * 2 + CELL_RUN_GUTTER}px); margin: 0px ${CELL_MARGIN * 2}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px; }`); - collector.addRule(`.notebookOverlay .markdown-cell-row .cell .cell-editor-part { margin-left: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .cell .run-button-container { width: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .cell-drag-image .cell-editor-container > div { padding: ${EDITOR_TOP_PADDING}px 16px ${EDITOR_BOTTOM_PADDING}px 16px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top { height: ${EDITOR_TOP_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { bottom: ${BOTTOM_CELL_TOOLBAR_HEIGHT}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator { width: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${CELL_MARGIN}px; }`); + collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${CELL_MARGIN * 2}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { height: ${CELL_BOTTOM_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-shadow-container-bottom { top: ${CELL_BOTTOM_MARGIN}px; }`); }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 2e3c7f9286..5a00f1fbb3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; -import { CELL_MARGIN, CELL_RUN_GUTTER, CODE_CELL_LEFT_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CELL_MARGIN, CELL_RUN_GUTTER, CODE_CELL_LEFT_MARGIN, CELL_OUTPUT_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { CellOutputKind, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -242,7 +242,7 @@ export class BackLayerWebView extends Disposable { this.element = document.createElement('div'); - this.element.style.width = `calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_MARGIN + CELL_RUN_GUTTER}px)`; + this.element.style.width = `calc(100% - ${CODE_CELL_LEFT_MARGIN + (CELL_MARGIN * 2) + CELL_RUN_GUTTER}px)`; this.element.style.height = '1400px'; this.element.style.position = 'absolute'; this.element.style.margin = `0px 0 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px`; @@ -312,7 +312,7 @@ export class BackLayerWebView extends Disposable { if (!isWeb) { coreDependencies = ``; - const htmlContent = this.generateContent(8, coreDependencies, baseUrl.toString()); + const htmlContent = this.generateContent(CELL_OUTPUT_PADDING, coreDependencies, baseUrl.toString()); this.initialize(htmlContent); resolveFunc!(); } else { @@ -329,7 +329,7 @@ ${loaderJs} `; - const htmlContent = this.generateContent(8, coreDependencies, baseUrl.toString()); + const htmlContent = this.generateContent(CELL_OUTPUT_PADDING, coreDependencies, baseUrl.toString()); this.initialize(htmlContent); resolveFunc!(); }); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 28e978c3c1..7578484c53 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -92,7 +92,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } private computeEditorWidth(outerWidth: number): number { - return outerWidth - (CODE_CELL_LEFT_MARGIN + CELL_MARGIN + CELL_RUN_GUTTER); + return outerWidth - (CODE_CELL_LEFT_MARGIN + (CELL_MARGIN * 2) + CELL_RUN_GUTTER); } layoutChange(state: CodeCellLayoutChangeEvent) { diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 59cd44a1cc..a0b2840fa1 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -564,7 +564,7 @@ export class OutlinePane extends ViewPane { return; } - this._revealTreeSelection(newModel, e.element, !e.editorOptions.preserveFocus || !!e.editorOptions.pinned, e.sideBySide); + this._revealTreeSelection(newModel, e.element, !!e.editorOptions.preserveFocus || !e.editorOptions.pinned, e.sideBySide); })); // feature: reveal editor selection in outline @@ -618,12 +618,12 @@ export class OutlinePane extends ViewPane { })); } - private async _revealTreeSelection(model: OutlineModel, element: OutlineElement, focus: boolean, aside: boolean): Promise { + private async _revealTreeSelection(model: OutlineModel, element: OutlineElement, preserveFocus: boolean, aside: boolean): Promise { await this._editorService.openCodeEditor( { resource: model.uri, options: { - preserveFocus: !focus, + preserveFocus, selection: Range.collapseToStart(element.symbol.selectionRange), selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport, } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 116ed0f242..71af5bcefa 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -105,9 +105,17 @@ function getObjectValueType(schema: IJSONSchema): ObjectValue['type'] { } function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectDataItem[] { + const elementDefaultValue: Record = typeof element.defaultValue === 'object' + ? element.defaultValue ?? {} + : {}; + + const elementScopeValue: Record = typeof element.scopeValue === 'object' + ? element.scopeValue ?? {} + : {}; + const data = element.isConfigured ? - { ...element.defaultValue, ...element.scopeValue } : - element.defaultValue; + { ...elementDefaultValue, ...elementScopeValue } : + elementDefaultValue; const { objectProperties, objectPatternProperties, objectAdditionalProperties } = element.setting; const patternsAndSchemas = Object @@ -129,7 +137,7 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData return Object.keys(data).map(key => { if (isDefined(objectProperties) && key in objectProperties) { - const defaultValue = element.defaultValue[key]; + const defaultValue = elementDefaultValue[key]; const valueEnumOptions = getEnumOptionsFromSchema(objectProperties[key]); return { @@ -144,7 +152,7 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData options: valueEnumOptions, }, removable: isUndefinedOrNull(defaultValue), - }; + } as IObjectDataItem; } const schema = patternsAndSchemas.find(({ pattern }) => pattern.test(key))?.schema; @@ -159,7 +167,7 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData options: valueEnumOptions, }, removable: true, - }; + } as IObjectDataItem; } return { @@ -170,7 +178,7 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData options: additionalValueEnums, }, removable: true, - }; + } as IObjectDataItem; }); } @@ -1055,8 +1063,14 @@ export class SettingObjectRenderer extends AbstractSettingRenderer implements IT private onDidChangeObject(template: ISettingObjectItemTemplate, e: ISettingListChangeEvent): void { if (template.context) { - const defaultValue: Record = template.context.defaultValue; - const scopeValue: Record = template.context.scopeValue; + const defaultValue: Record = typeof template.context.defaultValue === 'object' + ? template.context.defaultValue ?? {} + : {}; + + const scopeValue: Record = typeof template.context.scopeValue === 'object' + ? template.context.scopeValue ?? {} + : {}; + const newValue: Record = {}; let newItems: IObjectDataItem[] = []; @@ -1118,6 +1132,7 @@ export class SettingObjectRenderer extends AbstractSettingRenderer implements IT const items = getObjectDisplayValue(dataElement); template.objectWidget.setValue(items, { + settingKey: dataElement.setting.key, showAddButton: ( typeof dataElement.setting.objectAdditionalProperties === 'object' || isDefined(dataElement.setting.objectPatternProperties) || @@ -1845,6 +1860,7 @@ export class SettingsTree extends ObjectTree { viewState: ISettingsEditorViewState, renderers: ITreeRenderer[], @IThemeService themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, ) { super('SettingsTree', container, @@ -1870,7 +1886,8 @@ export class SettingsTree extends ObjectTree { } }, styleController: id => new DefaultStyleController(DOM.createStyleSheet(container), id), - filter: instantiationService.createInstance(SettingsTreeFilter, viewState) + filter: instantiationService.createInstance(SettingsTreeFilter, viewState), + smoothScrolling: configurationService.getValue('workbench.list.smoothScrolling'), }); this.disposables.clear(); @@ -1945,6 +1962,14 @@ export class SettingsTree extends ObjectTree { }, colors => { this.style(colors); })); + + this.disposables.add(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('workbench.list.smoothScrolling')) { + this.updateOptions({ + smoothScrolling: configurationService.getValue('workbench.list.smoothScrolling') + }); + } + })); } protected createModel(user: string, view: IList>, options: IObjectTreeOptions): ITreeModel { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 0bdc899f37..865ff2fd6b 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -188,7 +188,7 @@ export class ListSettingListModel { this._dataItems = listData; } - select(idx: number): void { + select(idx: number | null): void { this._selectedIdx = idx; } @@ -664,6 +664,7 @@ export interface IObjectKeySuggester { } interface IObjectSetValueOptions { + settingKey: string; showAddButton: boolean; keySuggester: IObjectKeySuggester; valueSuggester: IObjectValueSuggester; @@ -678,6 +679,7 @@ interface IObjectRenderEditWidgetOptions { } export class ObjectSettingWidget extends AbstractListSettingWidget { + private currentSettingKey: string = ''; private showAddButton: boolean = true; private keySuggester: IObjectKeySuggester = () => undefined; private valueSuggester: IObjectValueSuggester = () => undefined; @@ -686,6 +688,13 @@ export class ObjectSettingWidget extends AbstractListSettingWidget .count, +.scm-view.auto-provider-counts .scm-provider > .count[data-count="0"] { + display: none; +} + .scm-view .scm-provider > .label { display: flex; flex-shrink: 1; overflow: hidden; } +.scm-view .scm-provider > .label > .name { + font-weight: bold; +} + +.scm-view .scm-provider > .label > .description { + opacity: 0.7; + margin-left: 0.5em; + font-size: 0.9em; +} + .scm-view .scm-provider > .actions { flex: 1; padding-left: 10px; @@ -46,27 +58,30 @@ justify-content: flex-end; } -.scm-view .scm-provider > .actions .monaco-action-bar .action-item { - margin-left: 4px; - text-overflow: ellipsis; +/** + * The following rules are very specific because of inline drop down menus + * https://github.com/microsoft/vscode/issues/101410 + */ +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item { + padding-left: 4px; display: flex; align-items: center; - min-width: 14px; + min-width: 16px; } -.scm-view .scm-provider > .actions .monaco-action-bar .action-label { - text-overflow: ellipsis; +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label, +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .monaco-dropdown > .dropdown-label > .action-label { + display: flex; + align-items: center; overflow: hidden; - min-width: 14px; /* for flex */ + min-width: 16px; /* for flex */ height: 100%; + margin: 0; + background-repeat: no-repeat; + background-position: center; } -.scm-view .scm-provider > .actions .monaco-action-bar .action-label .codicon { - vertical-align: sub; - display: inline-flex; -} - -.scm-view .scm-provider > .actions .monaco-action-bar .action-item:last-of-type { +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:last-of-type { padding-right: 0; } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index a67e0b439f..9ad7940abf 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -143,6 +143,17 @@ Registry.as(ConfigurationExtensions.Configuration).regis description: localize('scm.countBadge', "Controls the Source Control count badge."), default: 'all' }, + 'scm.providerCountBadge': { + type: 'string', + enum: ['hidden', 'auto', 'visible'], + enumDescriptions: [ + localize('scm.providerCountBadge.hidden', "Hide Source Control Providers count badges."), + localize('scm.providerCountBadge.auto', "Show Source Control Providers count badges if there are any changes."), + localize('scm.providerCountBadge.visible', "Show Source Control Providers count badges.") + ], + description: localize('scm.providerCountBadge', "Controls the Source Control Providers count badge."), + default: 'hidden' + }, 'scm.defaultViewMode': { type: 'string', enum: ['tree', 'list'], diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index a656b419d3..4395319435 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -47,7 +47,7 @@ import { flatten } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; -import { SIDE_BAR_BACKGROUND, SIDE_BAR_BORDER, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { SIDE_BAR_BACKGROUND, SIDE_BAR_BORDER, PANEL_BACKGROUND, PANEL_INPUT_BORDER } from 'vs/workbench/common/theme'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorConstructionOptions } from 'vs/editor/common/config/editorOptions'; @@ -59,7 +59,7 @@ import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEdito import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu'; import * as platform from 'vs/base/common/platform'; import { escape, compare, format } from 'vs/base/common/strings'; -import { inputPlaceholderForeground, inputValidationInfoBorder, inputValidationWarningBorder, inputValidationErrorBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBackground, inputValidationErrorForeground, inputBackground, inputForeground, inputBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; +import { inputPlaceholderForeground, inputValidationInfoBorder, inputValidationWarningBorder, inputValidationErrorBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBackground, inputValidationErrorForeground, inputBackground, inputForeground, inputBorder, focusBorder, registerColor, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { Schemas } from 'vs/base/common/network'; @@ -76,9 +76,11 @@ import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; import { Command } from 'vs/editor/common/modes'; -import { renderCodicons } from 'vs/base/common/codicons'; +import { renderCodicons, Codicon } from 'vs/base/common/codicons'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { domEvent } from 'vs/base/browser/event'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; type TreeElement = ISCMRepository | ISCMInput | ISCMResourceGroup | IResourceNode | ISCMResource; @@ -151,6 +153,7 @@ interface ISCMLayout { interface RepositoryTemplate { readonly name: HTMLElement; + readonly description: HTMLElement; readonly countContainer: HTMLElement; readonly count: CountBadge; readonly toolBar: ToolBar; @@ -178,6 +181,7 @@ class RepositoryRenderer implements ICompressibleTreeRenderer, index: number, templateData: RepositoryTemplate, height: number | undefined): void { @@ -199,8 +203,10 @@ class RepositoryRenderer implements ICompressibleTreeRenderer void, + private focusTree: () => void, @IInstantiationService private instantiationService: IInstantiationService, ) { } @@ -272,10 +280,16 @@ class InputRenderer implements ICompressibleTreeRenderer new StandardKeyboardEvent(e)); + const onEscape = Event.filter(onKeyDown, e => e.keyCode === KeyCode.Escape); + disposables.add(onEscape(this.focusTree)); + + return { inputWidget, disposable: Disposable.None, templateDisposable: disposables }; } renderElement(node: ITreeNode, index: number, templateData: InputTemplate): void { @@ -297,22 +311,24 @@ class InputRenderer implements ICompressibleTreeRenderer { - this.updateHeight(input, contentHeight + 10); - templateData.inputWidget.layout(); - }); - disposables.add({ dispose: () => clearTimeout(timeout) }); - } + this.updateHeight(input, contentHeight + 10); + templateData.inputWidget.layout(); } }; - disposables.add(templateData.inputWidget.onDidChangeContentHeight(onDidChangeContentHeight)); - onDidChangeContentHeight(); + const initialRender = () => { + disposables.add(templateData.inputWidget.onDidChangeContentHeight(onDidChangeContentHeight)); + onDidChangeContentHeight(); + }; + + const contentHeight = templateData.inputWidget.getContentHeight(); + + if (contentHeight !== InputRenderer.DEFAULT_HEIGHT) { + const timeout = setTimeout(initialRender, 0); + disposables.add({ dispose: () => clearTimeout(timeout) }); + } else { + initialRender(); + } // Layout the editor whenever the outer layout happens const layoutEditor = () => templateData.inputWidget.layout(); @@ -720,20 +736,20 @@ class SCMResourceIdentityProvider implements IIdentityProvider { getId(element: TreeElement): string { if (ResourceTree.isResourceNode(element)) { const group = element.context; - return `folder:${group.provider.contextValue}/${group.id}/$FOLDER/${element.uri.toString()}`; + return `folder:${group.provider.id}/${group.id}/$FOLDER/${element.uri.toString()}`; } else if (isSCMRepository(element)) { const provider = element.provider; - return `repo:${provider.contextValue}`; + return `repo:${provider.id}`; } else if (isSCMInput(element)) { const provider = element.repository.provider; - return `input:${provider.contextValue}`; + return `input:${provider.id}`; } else if (isSCMResource(element)) { const group = element.resourceGroup; const provider = group.provider; - return `resource:${provider.contextValue}/${group.id}/${element.sourceUri.toString()}`; + return `resource:${provider.id}/${group.id}/${element.sourceUri.toString()}`; } else { const provider = element.provider; - return `group:${provider.contextValue}/${element.id}`; + return `group:${provider.id}/${element.id}`; } } } @@ -816,6 +832,10 @@ class ViewModel { private readonly _onDidChangeMode = new Emitter(); readonly onDidChangeMode = this._onDidChangeMode.event; + private _onDidChangeRepositoryCollapseState = new Emitter(); + readonly onDidChangeRepositoryCollapseState: Event; + private visible: boolean = false; + get mode(): ViewModelMode { return this._mode; } set mode(mode: ViewModelMode) { this._mode = mode; @@ -848,10 +868,11 @@ class ViewModel { private visibilityDisposables = new DisposableStore(); private scrollTop: number | undefined; private firstVisible = true; + private repositoryCollapseStates: Map | undefined; private disposables = new DisposableStore(); constructor( - private repositories: ISequence, + readonly repositories: ISequence, private tree: WorkbenchCompressibleObjectTree, private menus: SCMMenus, private inputRenderer: InputRenderer, @@ -859,12 +880,17 @@ class ViewModel { private _sortKey: ViewModelSortKey, @IEditorService protected editorService: IEditorService, @IConfigurationService protected configurationService: IConfigurationService, - ) { } + ) { + this.onDidChangeRepositoryCollapseState = Event.any( + this._onDidChangeRepositoryCollapseState.event, + Event.signal(Event.filter(this.tree.onDidChangeCollapseState, e => isSCMRepository(e.node.element))) + ); + } - private onDidSpliceRepositories({ start, deleteCount, toInsert }: ISplice): void { + private _onDidSpliceRepositories({ start, deleteCount, toInsert }: ISplice): void { const itemsToInsert = toInsert.map(repository => { const disposable = combinedDisposable( - repository.provider.groups.onDidSplice(splice => this.onDidSpliceGroups(item, splice)), + repository.provider.groups.onDidSplice(splice => this._onDidSpliceGroups(item, splice)), repository.input.onDidChangeVisibility(() => this.refresh(item)) ); const groupItems = repository.provider.groups.elements.map(group => this.createGroupItem(group)); @@ -886,7 +912,7 @@ class ViewModel { this.refresh(); } - private onDidSpliceGroups(item: IRepositoryItem, { start, deleteCount, toInsert }: ISplice): void { + private _onDidSpliceGroups(item: IRepositoryItem, { start, deleteCount, toInsert }: ISplice): void { const itemsToInsert: IGroupItem[] = toInsert.map(group => this.createGroupItem(group)); const itemsToDispose = item.groupItems.splice(start, deleteCount, ...itemsToInsert); @@ -902,7 +928,7 @@ class ViewModel { const resources: ISCMResource[] = [...group.elements]; const disposable = combinedDisposable( group.onDidChange(() => this.tree.refilter()), - group.onDidSplice(splice => this.onDidSpliceGroup(item, splice)) + group.onDidSplice(splice => this._onDidSpliceGroup(item, splice)) ); const item: IGroupItem = { element: group, resources, tree, disposable }; @@ -916,7 +942,7 @@ class ViewModel { return item; } - private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice): void { + private _onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice): void { const before = item.resources.length; const deleted = item.resources.splice(start, deleteCount, ...toInsert); const after = item.resources.length; @@ -941,8 +967,9 @@ class ViewModel { setVisible(visible: boolean): void { if (visible) { this.visibilityDisposables = new DisposableStore(); - this.repositories.onDidSplice(this.onDidSpliceRepositories, this, this.visibilityDisposables); - this.onDidSpliceRepositories({ start: 0, deleteCount: 0, toInsert: this.repositories.elements }); + this.repositories.onDidSplice(this._onDidSpliceRepositories, this, this.visibilityDisposables); + this._onDidSpliceRepositories({ start: 0, deleteCount: 0, toInsert: this.repositories.elements }); + this.repositoryCollapseStates = undefined; if (typeof this.scrollTop === 'number') { this.tree.scrollTop = this.scrollTop; @@ -952,10 +979,21 @@ class ViewModel { this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables); this.onDidActiveEditorChange(); } else { + if (this.items.length > 1) { + this.repositoryCollapseStates = new Map(); + + for (const item of this.items) { + this.repositoryCollapseStates.set(item.element, this.tree.isCollapsed(item.element)); + } + } + this.visibilityDisposables.dispose(); - this.onDidSpliceRepositories({ start: 0, deleteCount: this.items.length, toInsert: [] }); + this._onDidSpliceRepositories({ start: 0, deleteCount: this.items.length, toInsert: [] }); this.scrollTop = this.tree.scrollTop; } + + this.visible = visible; + this._onDidChangeRepositoryCollapseState.fire(); } private refresh(item?: IRepositoryItem | IGroupItem): void { @@ -966,6 +1004,8 @@ class ViewModel { } else { this.tree.setChildren(null, this.items.map(item => this.render(item))); } + + this._onDidChangeRepositoryCollapseState.fire(); } private render(item: IRepositoryItem | IGroupItem): ICompressedTreeElement { @@ -981,7 +1021,8 @@ class ViewModel { children.push(...item.groupItems.map(i => this.render(i))); } - return { element: item.element, children, incompressible: true, collapsible: true }; + const collapsed = this.repositoryCollapseStates?.get(item.element); + return { element: item.element, children, incompressible: true, collapsed, collapsible: hasSomeChanges }; } else { const children = this.mode === ViewModelMode.List ? Iterable.map(item.resources, element => ({ element, incompressible: true })) @@ -1056,6 +1097,10 @@ class ViewModel { } getViewSecondaryActions(): IAction[] { + if (this.repositories.elements.length === 0) { + return []; + } + const viewAction = new SCMViewSubMenuAction(this); if (this.repositories.elements.length !== 1) { @@ -1080,6 +1125,38 @@ class ViewModel { return this.repositories.elements[0].provider; } + collapseAllProviders(): void { + for (const repository of this.repositories.elements) { + if (this.tree.isCollapsible(repository)) { + this.tree.collapse(repository); + } + } + } + + expandAllProviders(): void { + for (const repository of this.repositories.elements) { + if (this.tree.isCollapsible(repository)) { + this.tree.expand(repository); + } + } + } + + isAnyProviderCollapsible(): boolean { + if (!this.visible || this.repositories.elements.length === 1) { + return false; + } + + return this.repositories.elements.some(r => this.tree.hasElement(r) && this.tree.isCollapsible(r)); + } + + areAllProvidersCollapsed(): boolean { + if (!this.visible || this.repositories.elements.length === 1) { + return false; + } + + return this.repositories.elements.every(r => this.tree.hasElement(r) && (!this.tree.isCollapsible(r) || this.tree.isCollapsed(r))); + } + dispose(): void { this.visibilityDisposables.dispose(); this.disposables.dispose(); @@ -1464,6 +1541,34 @@ class SCMInputWidget extends Disposable { } } +class SCMCollapseAction extends Action { + + private allCollapsed = false; + + constructor(private viewModel: ViewModel) { + super('scm.collapse', undefined, undefined, true); + this._register(viewModel.onDidChangeRepositoryCollapseState(this.update, this)); + this.update(); + } + + async run(): Promise { + if (this.allCollapsed) { + this.viewModel.expandAllProviders(); + } else { + this.viewModel.collapseAllProviders(); + } + } + + private update(): void { + const isAnyProviderCollapsible = this.viewModel.isAnyProviderCollapsible(); + + this.enabled = isAnyProviderCollapsible; + this.allCollapsed = isAnyProviderCollapsible && this.viewModel.areAllProvidersCollapsed(); + this.label = this.allCollapsed ? localize('expand all', "Expand All Providers") : localize('collapse all', "Collapse All Providers"); + this.class = this.allCollapsed ? Codicon.expandAll.classNames : Codicon.collapseAll.classNames; + } +} + export class SCMViewPane extends ViewPane { private _onDidLayout = new Emitter(); @@ -1513,6 +1618,14 @@ export class SCMViewPane extends ViewPane { Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowActions'))(updateActionsVisibility); updateActionsVisibility(); + const updateProviderCountVisibility = () => { + const value = this.configurationService.getValue<'hidden' | 'auto' | 'visible'>('scm.providerCountBadge'); + toggleClass(this.listContainer, 'hide-provider-counts', value === 'hidden'); + toggleClass(this.listContainer, 'auto-provider-counts', value === 'auto'); + }; + Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.providerCountBadge'))(updateProviderCountVisibility); + updateProviderCountVisibility(); + const repositories = new SimpleSequence(this.scmService.repositories, this.scmService.onDidAddRepository, this.scmService.onDidRemoveRepository); this._register(repositories); @@ -1521,7 +1634,7 @@ export class SCMViewPane extends ViewPane { this._register(repositories.onDidSplice(() => this.updateActions())); - this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, (input, height) => this.tree.updateElementHeight(input, height)); + this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, (input, height) => this.tree.updateElementHeight(input, height), () => this.tree.domFocus()); const delegate = new ProviderListDelegate(this.inputRenderer); const actionViewItemProvider = (action: IAction) => this.getActionViewItem(action); @@ -1634,7 +1747,14 @@ export class SCMViewPane extends ViewPane { return []; } - return this.viewModel.getViewActions(); + if (this.viewModel.repositories.elements.length < 2) { + return this.viewModel.getViewActions(); + } + + return [ + new SCMCollapseAction(this.viewModel), + ...this.viewModel.getViewActions() + ]; } getSecondaryActions(): IAction[] { @@ -1768,13 +1888,15 @@ export class SCMViewPane extends ViewPane { } } +export const scmProviderSeparatorBorderColor = registerColor('scm.providerBorder', { dark: '#454545', light: '#C8C8C8', hc: contrastBorder }, localize('scm.providerBorder', "SCM Provider separator border.")); + registerThemingParticipant((theme, collector) => { const inputBackgroundColor = theme.getColor(inputBackground); if (inputBackgroundColor) { collector.addRule(`.scm-view .scm-editor-container .monaco-editor-background, .scm-view .scm-editor-container .monaco-editor, .scm-view .scm-editor-container .monaco-editor .margin - { background-color: ${inputBackgroundColor}; }`); + { background-color: ${inputBackgroundColor} !important; }`); } const inputForegroundColor = theme.getColor(inputForeground); @@ -1787,6 +1909,11 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.scm-view .scm-editor-container { outline: 1px solid ${inputBorderColor}; }`); } + const panelInputBorder = theme.getColor(PANEL_INPUT_BORDER); + if (panelInputBorder) { + collector.addRule(`.monaco-workbench .part.panel .scm-view .scm-editor-container { outline: 1px solid ${panelInputBorder}; }`); + } + const focusBorderColor = theme.getColor(focusBorder); if (focusBorderColor) { collector.addRule(`.scm-view .scm-editor-container.synthetic-focus { outline: 1px solid ${focusBorderColor}; }`); @@ -1799,7 +1926,7 @@ registerThemingParticipant((theme, collector) => { const inputValidationInfoBorderColor = theme.getColor(inputValidationInfoBorder); if (inputValidationInfoBorderColor) { - collector.addRule(`.scm-view .scm-editor-container.validation-info { outline: 1px solid ${inputValidationInfoBorderColor}; }`); + collector.addRule(`.scm-view .scm-editor-container.validation-info { outline: 1px solid ${inputValidationInfoBorderColor} !important; }`); collector.addRule(`.scm-editor-validation.validation-info { border-color: ${inputValidationInfoBorderColor}; }`); } @@ -1815,7 +1942,7 @@ registerThemingParticipant((theme, collector) => { const inputValidationWarningBorderColor = theme.getColor(inputValidationWarningBorder); if (inputValidationWarningBorderColor) { - collector.addRule(`.scm-view .scm-editor-container.validation-warning { outline: 1px solid ${inputValidationWarningBorderColor}; }`); + collector.addRule(`.scm-view .scm-editor-container.validation-warning { outline: 1px solid ${inputValidationWarningBorderColor} !important; }`); collector.addRule(`.scm-editor-validation.validation-warning { border-color: ${inputValidationWarningBorderColor}; }`); } @@ -1831,7 +1958,7 @@ registerThemingParticipant((theme, collector) => { const inputValidationErrorBorderColor = theme.getColor(inputValidationErrorBorder); if (inputValidationErrorBorderColor) { - collector.addRule(`.scm-view .scm-editor-container.validation-error { outline: 1px solid ${inputValidationErrorBorderColor}; }`); + collector.addRule(`.scm-view .scm-editor-container.validation-error { outline: 1px solid ${inputValidationErrorBorderColor} !important; }`); collector.addRule(`.scm-editor-validation.validation-error { border-color: ${inputValidationErrorBorderColor}; }`); } diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 96afea3f9c..97aefbba33 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -110,7 +110,7 @@ export class SearchEditorInput extends EditorInput { const input = this; const workingCopyAdapter = new class implements IWorkingCopy { - readonly resource = input.backingUri ?? input.modelUri; + readonly resource = input.modelUri; get name() { return input.getName(); } readonly capabilities = input.isUntitled() ? WorkingCopyCapabilities.Untitled : 0; readonly onDidChangeDirty = input.onDidChangeDirty; @@ -308,7 +308,7 @@ export const getOrMakeSearchEditorInput = ( let config = { ...defaultConfig, ...priorConfig, ...existingData.config }; if (defaultNumberOfContextLines !== null && defaultNumberOfContextLines !== undefined) { - config.contextLines = defaultNumberOfContextLines; + config.contextLines = existingData.config.contextLines ?? defaultNumberOfContextLines; } if (existingData.text) { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts index 649e157c09..c15444f6d3 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts @@ -34,7 +34,7 @@ export class SearchEditorModel { @IModeService private readonly modeService: IModeService) { this.onModelResolved = new Promise(resolve => this.resolveContents = resolve); this.onModelResolved.then(model => this.cachedContentsModel = model); - this.ongoingResolve = backupService.resolve(existingData.backingUri ?? modelUri) + this.ongoingResolve = backupService.resolve(modelUri) .then(backup => modelService.getModel(modelUri) ?? (backup ? modelService.createModel(backup.value, modeService.create('search-result'), modelUri) : undefined)) .then(model => { if (model) { this.resolveContents(model); } }); } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkProviderAdapter.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkProviderAdapter.ts index 66ffa4ad32..64f50f390f 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkProviderAdapter.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkProviderAdapter.ts @@ -9,6 +9,7 @@ import { TerminalLink } from 'vs/workbench/contrib/terminal/browser/links/termin import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalBaseLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider'; import { ITerminalExternalLinkProvider, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { XtermLinkMatcherHandler } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; /** * An adapter to convert a simple external link provider into an internal link provider that @@ -20,6 +21,7 @@ export class TerminalExternalLinkProviderAdapter extends TerminalBaseLinkProvide private readonly _xterm: Terminal, private readonly _instance: ITerminalInstance, private readonly _externalLinkProvider: ITerminalExternalLinkProvider, + private readonly _wrapLinkHandler: (handler: (event: MouseEvent | undefined, link: string) => void) => XtermLinkMatcherHandler, private readonly _tooltipCallback: (link: TerminalLink, viewportRange: IViewportRange, modifierDownCallback?: () => void, modifierUpCallback?: () => void) => void, @IInstantiationService private readonly _instantiationService: IInstantiationService ) { @@ -45,6 +47,10 @@ export class TerminalExternalLinkProviderAdapter extends TerminalBaseLinkProvide } const lineContent = getXtermLineContent(this._xterm.buffer.active, startLine, endLine, this._xterm.cols); + if (lineContent.trim().length === 0) { + return []; + } + const externalLinks = await this._externalLinkProvider.provideLinks(this._instance, lineContent); if (!externalLinks) { return []; @@ -58,7 +64,8 @@ export class TerminalExternalLinkProviderAdapter extends TerminalBaseLinkProvide endLineNumber: 1 }, startLine); const matchingText = lineContent.substr(link.startIndex, link.length) || ''; - return this._instantiationService.createInstance(TerminalLink, bufferRange, matchingText, this._xterm.buffer.active.viewportY, (_, text) => link.activate(text), this._tooltipCallback, true, link.label); + const activateLink = this._wrapLinkHandler((_, text) => link.activate(text)); + return this._instantiationService.createInstance(TerminalLink, bufferRange, matchingText, this._xterm.buffer.active.viewportY, activateLink, this._tooltipCallback, true, link.label); }); } } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts index 7ac793e6fd..74441c6eee 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts @@ -156,7 +156,7 @@ export class TerminalLinkManager extends DisposableStore { } public registerExternalLinkProvider(instance: ITerminalInstance, linkProvider: ITerminalExternalLinkProvider): IDisposable { - const wrappedLinkProvider = this._instantiationService.createInstance(TerminalExternalLinkProviderAdapter, this._xterm, instance, linkProvider, this._tooltipCallback2.bind(this)); + const wrappedLinkProvider = this._instantiationService.createInstance(TerminalExternalLinkProviderAdapter, this._xterm, instance, linkProvider, this._wrapLinkHandler.bind(this), this._tooltipCallback2.bind(this)); const newLinkProvider = this._xterm.registerLinkProvider(wrappedLinkProvider); // Re-register the standard link providers so they are a lower priority that the new one this._registerStandardLinkProviders(); diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index 7556e4a6ff..63da09f690 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -111,7 +111,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess try { const result = await stat(slc.executable); if (!result.isFile() && !result.isSymbolicLink()) { - return { message: localize('launchFail.executableIsNotFileOrSymlink', "Shell path \"{0}\" is not a file of a symlink", slc.executable) }; + return { message: localize('launchFail.executableIsNotFileOrSymlink', "Path to shell executable \"{0}\" is not a file of a symlink", slc.executable) }; } } catch (err) { if (err?.code === 'ENOENT') { @@ -120,7 +120,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess const envPaths: string[] | undefined = (slc.env && slc.env.PATH) ? slc.env.PATH.split(path.delimiter) : undefined; const executable = await findExecutable(slc.executable!, cwd, envPaths); if (!executable) { - return { message: localize('launchFail.executableDoesNotExist', "Shell path \"{0}\" does not exist", slc.executable) }; + return { message: localize('launchFail.executableDoesNotExist', "Path to shell executable \"{0}\" does not exist", slc.executable) }; } } } diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index ad7dd69002..8aa5d29134 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -885,6 +885,12 @@ export class TimelinePane extends ViewPane { } return element.accessibilityInformation ? element.accessibilityInformation.label : localize('timeline.aria.item', "{0}: {1}", element.relativeTime ?? '', element.label); }, + getRole(element: TreeElement): string { + if (isLoadMoreCommand(element)) { + return 'treeitem'; + } + return element.accessibilityInformation && element.accessibilityInformation.role ? element.accessibilityInformation.role : 'treeitem'; + }, getWidgetAriaLabel(): string { return localize('timeline', "Timeline"); } diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 455bdbdd1f..9f75ead8a3 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -16,7 +16,6 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IUpdateService, State as UpdateState, StateType, IUpdate } from 'vs/platform/update/common/update'; import * as semver from 'semver-umd'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; // import { ReleaseNotesManager } from './releaseNotesEditor'; import { isWindows } from 'vs/base/common/platform'; @@ -178,7 +177,6 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu @IStorageService private readonly storageService: IStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, - @IDialogService private readonly dialogService: IDialogService, @IUpdateService private readonly updateService: IUpdateService, @IActivityService private readonly activityService: IActivityService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -277,11 +275,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu } private onUpdateNotAvailable(): void { - this.dialogService.show( - severity.Info, - nls.localize('noUpdatesAvailable', "There are currently no updates available."), - [nls.localize('ok', "OK")] - ); + this.notificationService.info(nls.localize('noUpdatesAvailable', "There are currently no updates available.")); } // linux diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index c65e9a930f..db16f25dad 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -75,10 +75,10 @@ const syncNowCommand = { title: localize('sync now', "Preferences Sync: Sync Now"), description(userDataSyncService: IUserDataSyncService): string | undefined { if (userDataSyncService.status === SyncStatus.Syncing) { - return localize('sync is on with syncing', "syncing"); + return localize('syncing', "syncing"); } if (userDataSyncService.lastSyncTime) { - return localize('sync is on with time', "synced {0}", fromNow(userDataSyncService.lastSyncTime, true)); + return localize('synced with time', "synced {0}", fromNow(userDataSyncService.lastSyncTime, true)); } return undefined; } @@ -270,11 +270,19 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private onAutoSyncError(error: UserDataSyncError): void { switch (error.code) { - case UserDataSyncErrorCode.TurnedOff: case UserDataSyncErrorCode.SessionExpired: this.notificationService.notify({ severity: Severity.Info, - message: localize('turned off', "Preferences sync was turned off from another device."), + message: localize('session expired', "Preferences sync was turned off because current session is expired, please sign in again to turn on sync."), + actions: { + primary: [new Action('turn on sync', localize('turn on sync', "Turn on Preferences Sync..."), undefined, true, () => this.turnOn())] + } + }); + break; + case UserDataSyncErrorCode.TurnedOff: + this.notificationService.notify({ + severity: Severity.Info, + message: localize('turned off', "Preferences sync was turned off from another device, please sign in again to turn on sync."), actions: { primary: [new Action('turn on sync', localize('turn on sync', "Turn on Preferences Sync..."), undefined, true, () => this.turnOn())] } diff --git a/src/vs/workbench/contrib/views/browser/treeView.ts b/src/vs/workbench/contrib/views/browser/treeView.ts index e48b9fc804..ad1ad97807 100644 --- a/src/vs/workbench/contrib/views/browser/treeView.ts +++ b/src/vs/workbench/contrib/views/browser/treeView.ts @@ -38,7 +38,7 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IHoverService, IHoverOptions } from 'vs/workbench/services/hover/browser/hover'; class Root implements ITreeItem { label = { label: 'root' }; @@ -795,6 +795,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer impleme ) { super(id, options, contentOptions, extension, _webviewThemeDataProvider, _myLogService, telemetryService, environmentService, workbenchEnvironmentService); + /* __GDPR__ + "webview.createWebview" : { + "extension": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "enableFindWidget": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + telemetryService.publicLog('webview.createWebview', { + enableFindWidget: !!options.enableFindWidget, + extension: extension?.id.value, + }); + this._myLogService.debug(`Webview(${this.id}): init`); const webviewId = new Promise((resolve, reject) => { diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index 14e872e306..33d578607b 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -118,8 +118,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService x: Math.floor(x), y: Math.floor(y), positioningItem: delegate.autoSelectFirstItem ? 0 : undefined, - onHide: () => onHide() - }); + }, () => onHide()); } } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index a92b187351..d996a36b50 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -490,10 +490,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { return toDisposable(() => remove()); } - getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined, id: string | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { + getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { const overrides = []; for (const handler of this.openEditorHandlers) { - const handlers = handler.getEditorOverrides ? handler.getEditorOverrides(resource, options, group, id).map(val => [handler, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]) : []; + const handlers = handler.getEditorOverrides ? handler.getEditorOverrides(resource, options, group).map(val => [handler, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]) : []; overrides.push(...handlers); } @@ -1347,7 +1347,7 @@ export class DelegatingEditorService implements IEditorService { isOpen(editor: IEditorInput | IResourceEditorInput): boolean { return this.editorService.isOpen(editor as IResourceEditorInput /* TS fail */); } overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { return this.editorService.overrideOpenEditor(handler); } - getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined, id: string | undefined) { return this.editorService.getEditorOverrides(resource, options, group, id); } + getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) { return this.editorService.getEditorOverrides(resource, options, group); } invokeWithinEditorContext(fn: (accessor: ServicesAccessor) => T): T { return this.editorService.invokeWithinEditorContext(fn); } diff --git a/src/vs/workbench/services/editor/common/editorOpenWith.ts b/src/vs/workbench/services/editor/common/editorOpenWith.ts index 8ef0a255ef..d19e16ed7a 100644 --- a/src/vs/workbench/services/editor/common/editorOpenWith.ts +++ b/src/vs/workbench/services/editor/common/editorOpenWith.ts @@ -42,14 +42,16 @@ export async function openEditorWith( return undefined; // {{SQL CARBON EDIT}} strict-null-checks } - const allEditorOverrides = getAllAvailableEditors(resource, id, options, group, editorService); + const overrideOptions = { ...options, override: id }; + + const allEditorOverrides = getAllAvailableEditors(resource, id, overrideOptions, group, editorService); if (!allEditorOverrides.length) { return undefined; // {{SQL CARBON EDIT}} strict-null-checks } const overrideToUse = typeof id === 'string' && allEditorOverrides.find(([_, entry]) => entry.id === id); if (overrideToUse) { - return overrideToUse[0].open(input, { ...options, override: id }, group, OpenEditorContext.NEW_EDITOR)?.override; + return overrideToUse[0].open(input, overrideOptions, group, OpenEditorContext.NEW_EDITOR)?.override; } // Prompt @@ -134,7 +136,7 @@ export function getAllAvailableEditors( editorService: IEditorService ): Array<[IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]> { const fileEditorInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileEditorInputFactory(); - const overrides = editorService.getEditorOverrides(resource, options, group, id); + const overrides = editorService.getEditorOverrides(resource, options, group); if (!overrides.some(([_, entry]) => entry.id === DEFAULT_EDITOR_ID)) { overrides.unshift([ { diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 93ce4d0ca9..10f2716a8f 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -36,7 +36,7 @@ export interface IOpenEditorOverrideEntry { export interface IOpenEditorOverrideHandler { open(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, context: OpenEditorContext): IOpenEditorOverride | undefined; - getEditorOverrides?(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined, id: string | undefined): IOpenEditorOverrideEntry[]; + getEditorOverrides?(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[]; } export interface IOpenEditorOverride { @@ -236,7 +236,7 @@ export interface IEditorService { /** * Get all available editor overrides for the editor input. */ - getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined, id: string | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][]; + getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][]; /** * Allows to override the opening of editors by installing a handler that will diff --git a/src/vs/workbench/services/hover/browser/hover.ts b/src/vs/workbench/services/hover/browser/hover.ts index 0ed20a2f0e..6961c0dc23 100644 --- a/src/vs/workbench/services/hover/browser/hover.ts +++ b/src/vs/workbench/services/hover/browser/hover.ts @@ -16,7 +16,7 @@ export interface IHoverService { readonly _serviceBrand: undefined; /** - * Shows a hover. + * Shows a hover, provided a hover with the same options object is not already visible. * @param options A set of options defining the characteristics of the hover. * @param focus Whether to focus the hover (useful for keyboard accessibility). * diff --git a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts index aabd1ae91b..162a12259d 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts @@ -6,6 +6,7 @@ import { localize } from 'vs/nls'; import { Queue } from 'vs/base/common/async'; import * as json from 'vs/base/common/json'; +import * as objects from 'vs/base/common/objects'; import { setProperty } from 'vs/base/common/jsonEdit'; import { Edit } from 'vs/base/common/jsonFormatter'; import { Disposable, IReference } from 'vs/base/common/lifecycle'; @@ -143,7 +144,11 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding const eol = model.getEOL(); const key = keybindingItem.resolvedKeybinding ? keybindingItem.resolvedKeybinding.getUserSettingsLabel() : null; if (key) { - this.applyEditsToBuffer(setProperty(model.getValue(), [-1], this.asObject(key, keybindingItem.command, keybindingItem.when ? keybindingItem.when.serialize() : undefined, true), { tabSize, insertSpaces, eol })[0], model); + const entry: IUserFriendlyKeybinding = this.asObject(key, keybindingItem.command, keybindingItem.when ? keybindingItem.when.serialize() : undefined, true); + const userKeybindingEntries = json.parse(model.getValue()); + if (userKeybindingEntries.every(e => !this.areSame(e, entry))) { + this.applyEditsToBuffer(setProperty(model.getValue(), [-1], entry, { tabSize, insertSpaces, eol })[0], model); + } } } @@ -196,6 +201,26 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding return object; } + private areSame(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean { + if (a.command !== b.command) { + return false; + } + if (a.key !== b.key) { + return false; + } + const whenA = ContextKeyExpr.deserialize(a.when); + const whenB = ContextKeyExpr.deserialize(b.when); + if ((whenA && !whenB) || (!whenA && whenB)) { + return false; + } + if (whenA && whenB && !whenA.equals(whenB)) { + return false; + } + if (!objects.equals(a.args, b.args)) { + return false; + } + return true; + } private applyEditsToBuffer(edit: Edit, model: ITextModel): void { const startPosition = model.getPositionAt(edit.offset); @@ -206,7 +231,6 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []); } - private resolveModelReference(): Promise> { return this.fileService.exists(this.resource) .then(exists => { diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index f965e82325..8436af0817 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -220,6 +220,16 @@ suite('KeybindingsEditing', () => { .then(() => assert.deepEqual(getUserKeybindings(), expected)); }); + test('remove a default keybinding should not ad duplicate entries', async () => { + const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }]; + await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } })); + await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } })); + await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } })); + await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } })); + await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } })); + assert.deepEqual(getUserKeybindings(), expected); + }); + test('remove a user keybinding', () => { writeToKeybindingsFile({ key: 'alt+c', command: 'b' }); return testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'b', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } }, isDefault: false })) diff --git a/src/vs/workbench/services/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts index 7b2479255f..faed9a6467 100644 --- a/src/vs/workbench/services/statusbar/common/statusbar.ts +++ b/src/vs/workbench/services/statusbar/common/statusbar.ts @@ -33,6 +33,12 @@ export interface IStatusbarEntry { */ readonly ariaLabel: string; + /** + * Role of the status bar entry which defines how a screen reader interacts with it. + * Default is 'button'. + */ + readonly role?: string; + /** * An optional tooltip text to show when you hover over the entry */ diff --git a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts index cb73544dd3..7036ef18a8 100644 --- a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts +++ b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts @@ -11,6 +11,7 @@ import * as streams from 'vs/base/common/stream'; import * as iconv from 'iconv-lite-umd'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { newWriteableBufferStream, VSBuffer, VSBufferReadableStream, streamToBufferReadableStream } from 'vs/base/common/buffer'; +import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; export async function detectEncodingByBOM(file: string): Promise { try { @@ -399,4 +400,14 @@ suite('Encoding', () => { assert.equal(actual, expected); }); }); + + test('encodingExists', async function () { + for (const enc in SUPPORTED_ENCODINGS) { + if (enc === encoding.UTF8_with_bom) { + continue; // skip over encodings from us + } + + assert.equal(iconv.encodingExists(enc), true, enc); + } + }); }); diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index e33a18e2e2..05c7b0ef7d 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -374,13 +374,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return null; } return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => { - if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) { - this.currentColorTheme.clearCaches(); - // the loaded theme is identical to the perisisted theme. Don't need to send an event. - this.currentColorTheme = themeData; - themeData.setCustomizations(this.settings); - return Promise.resolve(themeData); - } themeData.setCustomizations(this.settings); return this.applyTheme(themeData, settingsTarget); }, error => { diff --git a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts index 593ada2c44..e41dc4c470 100644 --- a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts @@ -32,7 +32,7 @@ suite('Breadcrumb Model', function () { test('only uri, inside workspace', function () { - let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, configService, workspaceService); + let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, configService, workspaceService); let elements = model.getElements(); assert.equal(elements.length, 3); @@ -45,9 +45,24 @@ suite('Breadcrumb Model', function () { assert.equal(three.uri.toString(), 'foo:/bar/baz/ws/some/path/file.ts'); }); + test('display uri matters for FileElement', function () { + + let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/PATH/file.ts'), URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, configService, workspaceService); + let elements = model.getElements(); + + assert.equal(elements.length, 3); + let [one, two, three] = elements as FileElement[]; + assert.equal(one.kind, FileKind.FOLDER); + assert.equal(two.kind, FileKind.FOLDER); + assert.equal(three.kind, FileKind.FILE); + assert.equal(one.uri.toString(), 'foo:/bar/baz/ws/some'); + assert.equal(two.uri.toString(), 'foo:/bar/baz/ws/some/PATH'); + assert.equal(three.uri.toString(), 'foo:/bar/baz/ws/some/PATH/file.ts'); + }); + test('only uri, outside workspace', function () { - let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, configService, configService, workspaceService); + let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), URI.parse('foo:/outside/file.ts'), undefined, configService, configService, workspaceService); let elements = model.getElements(); assert.equal(elements.length, 2); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 18a20a50f5..1675abd334 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -495,6 +495,7 @@ export class TestViewsService implements IViewsService { openView(id: string, focus?: boolean | undefined): Promise { return Promise.resolve(null); } closeView(id: string): void { } getViewProgressIndicator(id: string) { return null!; } + getActiveViewPaneContainerWithId(id: string) { return null; } } export class TestEditorGroupsService implements IEditorGroupsService { diff --git a/yarn.lock b/yarn.lock index 1c7e8adcfe..ff2f8f27ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2132,6 +2132,21 @@ concat-with-sourcemaps@^1.0.0: dependencies: source-map "^0.5.1" +concurrently@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.2.0.tgz#ead55121d08a0fc817085584c123cedec2e08975" + integrity sha512-XxcDbQ4/43d6CxR7+iV8IZXhur4KbmEJk1CetVMUqCy34z9l0DkszbY+/9wvmSnToTej0SYomc2WSRH+L0zVJw== + dependencies: + chalk "^2.4.2" + date-fns "^2.0.1" + lodash "^4.17.15" + read-pkg "^4.0.1" + rxjs "^6.5.2" + spawn-command "^0.0.2-1" + supports-color "^6.1.0" + tree-kill "^1.2.2" + yargs "^13.3.0" + config-chain@^1.1.11, config-chain@^1.1.12: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" @@ -2436,6 +2451,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +date-fns@^2.0.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.14.0.tgz#359a87a265bb34ef2e38f93ecf63ac453f9bc7ba" + integrity sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw== + date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" @@ -2941,6 +2961,13 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: version "0.10.35" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.35.tgz#18ee858ce6a3c45c7d79e91c15fcca9ec568494f" @@ -4697,10 +4724,10 @@ husky@^0.13.1: is-ci "^1.0.9" normalize-path "^1.0.0" -iconv-lite-umd@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722" - integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg== +iconv-lite-umd@0.6.7: + version "0.6.7" + resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.7.tgz#ee437e34b30f15dc00ec93ea65065e672770777c" + integrity sha512-DT90zb7wL1B3I6DmYUMcfJeVdY19XigzDj5AtXbXEw9Jfi0+AVAxfn7ytvY7Xhr+GFn7nd7hPonapC37oo7iAQ== iconv-lite@^0.4.19: version "0.4.19" @@ -5425,7 +5452,7 @@ json-edm-parser@0.1.2: dependencies: jsonparse "~1.2.0" -json-parse-better-errors@^1.0.2: +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== @@ -7001,6 +7028,14 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + parse-node-version@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" @@ -7842,6 +7877,15 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237" + integrity sha1-ljYlN48+HE1IyFhytabsfV0JMjc= + dependencies: + normalize-package-data "^2.3.2" + parse-json "^4.0.0" + pify "^3.0.0" + read@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" @@ -8324,6 +8368,13 @@ rxjs@^6.4.0: dependencies: tslib "^1.9.0" +rxjs@^6.5.2: + version "6.6.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84" + integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg== + dependencies: + tslib "^1.9.0" + rxjs@^6.5.3: version "6.5.3" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" @@ -8707,6 +8758,11 @@ sparkles@^1.0.0: resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" integrity sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM= +spawn-command@^0.0.2-1: + version "0.0.2-1" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" + integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= + spdlog@^0.11.1: version "0.11.1" resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.11.1.tgz#29721b31018a5fe6a3ce2531f9d8d43e0bd6b825" @@ -10408,7 +10464,7 @@ yargs@^13.2.4: y18n "^4.0.0" yargs-parser "^13.1.0" -yargs@^13.3.2: +yargs@^13.3.0, yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==