From 52771b92e2ea28299a7be4880faa096c24031bfb Mon Sep 17 00:00:00 2001 From: tisilent Date: Thu, 6 Apr 2023 23:33:52 +0800 Subject: [PATCH 001/826] Fix #178135 --- .../browser/find/simpleFindWidget.css | 11 ++++ .../browser/find/simpleFindWidget.ts | 52 ++++++++++++++++++- .../find/browser/terminalFindWidget.ts | 6 ++- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css index e15ab55dacb..5d6ea3106c3 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css @@ -99,3 +99,14 @@ div.simple-find-part-wrapper div.button:hover:not(.disabled) { outline: 1px dashed var(--vscode-toolbar-hoverOutline); outline-offset: -1px; } + +.monaco-workbench .simple-find-part .monaco-sash { + left: 0 !important; + border-left: 1px solid; + border-bottom-left-radius: 4px; +} + +.monaco-workbench .simple-find-part .monaco-sash.vertical:before{ + width: 2px; + left: calc(50% - (var(--vscode-sash-hover-size) / 4)); +} diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index a59a6decbd5..3bdb003f59f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -17,12 +17,15 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ContextScopedFindInput } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import * as strings from 'vs/base/common/strings'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint'; import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; +import { registerColor } from 'vs/platform/theme/common/colorRegistry'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find (\u21C5 for history)"); @@ -38,12 +41,14 @@ interface IFindOptions { appendRegexLabel?: string; appendWholeWordsLabel?: string; type?: 'Terminal' | 'Webview'; + initialWidth?: number; + disableSash?: boolean; } const SIMPLE_FIND_WIDGET_INITIAL_WIDTH = 310; const MATCHES_COUNT_WIDTH = 68; -export abstract class SimpleFindWidget extends Widget { +export abstract class SimpleFindWidget extends Widget implements IVerticalSashLayoutProvider { private readonly _findInput: FindInput; private readonly _domNode: HTMLElement; private readonly _innerDomNode: HTMLElement; @@ -197,6 +202,42 @@ export abstract class SimpleFindWidget extends Widget { this._delayedUpdateHistory(); })); } + + if (options?.initialWidth) { + this._domNode.style.width = `${options.initialWidth}px`; + } + + if (options?.disableSash) { + const initialMinWidth = options?.initialWidth ?? SIMPLE_FIND_WIDGET_INITIAL_WIDTH; + let originalWidth = dom.getTotalWidth(this._domNode); + + // sash + const resizeSash = new Sash(this._innerDomNode, this, { orientation: Orientation.VERTICAL, size: 1 }); + this._register(resizeSash.onDidStart(() => { + originalWidth = parseFloat(dom.getComputedStyle(this._domNode).width); + })); + + this._register(resizeSash.onDidChange((e: ISashEvent) => { + const width = originalWidth + e.startX - e.currentX; + if (width < initialMinWidth) { + return; + } + this._domNode.style.width = `${width}px`; + })); + + this._register(resizeSash.onDidReset(e => { + const currentWidth = parseFloat(dom.getComputedStyle(this._domNode).width); + if (currentWidth === initialMinWidth) { + this._domNode.style.width = '100%'; + } else { + this._domNode.style.width = `${initialMinWidth}px`; + } + })); + } + } + + public getVerticalSashLeft(_sash: Sash): number { + return 0; } public abstract find(previous: boolean): void; @@ -363,6 +404,8 @@ export abstract class SimpleFindWidget extends Widget { } } else if (count?.resultCount) { label = strings.format(NLS_MATCHES_LOCATION, count.resultIndex + 1, count?.resultCount); + } else { + label = NLS_NO_RESULTS; } alertFn(this._announceSearchResults(label, this.inputValue)); this._matchesCount.appendChild(document.createTextNode(label)); @@ -387,3 +430,10 @@ export abstract class SimpleFindWidget extends Widget { return nls.localize('ariaSearchNoResultWithLineNumNoCurrentMatch', "{0} found for '{1}'", label, searchString); } } + +export const simpleFindWidgetSashBorder = registerColor('simpleFindWidget.sashBorder', { dark: '#454545', light: '#C8C8C8', hcDark: '6FC3DF', hcLight: '0F4A85' }, nls.localize('simpleFindWidget.sashBorder', 'Border color of the sash border.')); + +registerThemingParticipant((theme, collector) => { + const resizeBorderBackground = theme.getColor(simpleFindWidgetSashBorder); + collector.addRule(`.monaco-workbench .simple-find-part .monaco-sash { background-color: ${resizeBorderBackground}; border-color: ${resizeBorderBackground} }`); +}); diff --git a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts index 8a7d706834c..bcea9177225 100644 --- a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts @@ -14,6 +14,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Event } from 'vs/base/common/event'; import { ISearchOptions } from 'xterm-addon-search'; +const TERMINAL_FIND_WIDGET_INITIAL_WIDTH = 419; + export class TerminalFindWidget extends SimpleFindWidget { private _findInputFocused: IContextKey; private _findWidgetFocused: IContextKey; @@ -27,7 +29,7 @@ export class TerminalFindWidget extends SimpleFindWidget { @IThemeService private readonly _themeService: IThemeService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { - super({ showCommonFindToggles: true, checkImeCompletionState: true, showResultCount: true, type: 'Terminal' }, _contextViewService, _contextKeyService, keybindingService); + super({ showCommonFindToggles: true, checkImeCompletionState: true, showResultCount: true, type: 'Terminal', initialWidth: TERMINAL_FIND_WIDGET_INITIAL_WIDTH, disableSash: true }, _contextViewService, _contextKeyService, keybindingService); this._register(this.state.onFindReplaceStateChange(() => { this.show(); @@ -45,6 +47,8 @@ export class TerminalFindWidget extends SimpleFindWidget { this.find(true, true); } })); + + this.updateResultCount(); } find(previous: boolean, update?: boolean) { From 0676fe42b1daab8dd7e39fc56bfee798d45061d8 Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 7 Apr 2023 14:36:22 +0800 Subject: [PATCH 002/826] Rename enableSash --- .../browser/find/simpleFindWidget.ts | 20 ++++++++++--------- .../find/browser/terminalFindWidget.ts | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 3bdb003f59f..3181551d61d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -42,7 +42,7 @@ interface IFindOptions { appendWholeWordsLabel?: string; type?: 'Terminal' | 'Webview'; initialWidth?: number; - disableSash?: boolean; + enableSash?: boolean; } const SIMPLE_FIND_WIDGET_INITIAL_WIDTH = 310; @@ -203,13 +203,15 @@ export abstract class SimpleFindWidget extends Widget implements IVerticalSashLa })); } - if (options?.initialWidth) { - this._domNode.style.width = `${options.initialWidth}px`; + let initialMinWidth = options?.initialWidth; + if (initialMinWidth) { + initialMinWidth = initialMinWidth < SIMPLE_FIND_WIDGET_INITIAL_WIDTH ? SIMPLE_FIND_WIDGET_INITIAL_WIDTH : initialMinWidth; + this._domNode.style.width = `${initialMinWidth}px`; } - if (options?.disableSash) { - const initialMinWidth = options?.initialWidth ?? SIMPLE_FIND_WIDGET_INITIAL_WIDTH; - let originalWidth = dom.getTotalWidth(this._domNode); + if (options?.enableSash) { + const _initialMinWidth = initialMinWidth ?? SIMPLE_FIND_WIDGET_INITIAL_WIDTH; + let originalWidth = _initialMinWidth; // sash const resizeSash = new Sash(this._innerDomNode, this, { orientation: Orientation.VERTICAL, size: 1 }); @@ -219,7 +221,7 @@ export abstract class SimpleFindWidget extends Widget implements IVerticalSashLa this._register(resizeSash.onDidChange((e: ISashEvent) => { const width = originalWidth + e.startX - e.currentX; - if (width < initialMinWidth) { + if (width < _initialMinWidth) { return; } this._domNode.style.width = `${width}px`; @@ -227,10 +229,10 @@ export abstract class SimpleFindWidget extends Widget implements IVerticalSashLa this._register(resizeSash.onDidReset(e => { const currentWidth = parseFloat(dom.getComputedStyle(this._domNode).width); - if (currentWidth === initialMinWidth) { + if (currentWidth === _initialMinWidth) { this._domNode.style.width = '100%'; } else { - this._domNode.style.width = `${initialMinWidth}px`; + this._domNode.style.width = `${_initialMinWidth}px`; } })); } diff --git a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts index bcea9177225..bb781e33070 100644 --- a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts @@ -29,7 +29,7 @@ export class TerminalFindWidget extends SimpleFindWidget { @IThemeService private readonly _themeService: IThemeService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { - super({ showCommonFindToggles: true, checkImeCompletionState: true, showResultCount: true, type: 'Terminal', initialWidth: TERMINAL_FIND_WIDGET_INITIAL_WIDTH, disableSash: true }, _contextViewService, _contextKeyService, keybindingService); + super({ showCommonFindToggles: true, checkImeCompletionState: true, showResultCount: true, type: 'Terminal', initialWidth: TERMINAL_FIND_WIDGET_INITIAL_WIDTH, enableSash: true }, _contextViewService, _contextKeyService, keybindingService); this._register(this.state.onFindReplaceStateChange(() => { this.show(); From 01586a4d44779ff7f0cbed7976ab6654bbbd08c3 Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 7 Apr 2023 17:59:06 +0800 Subject: [PATCH 003/826] fix color --- .../contrib/codeEditor/browser/find/simpleFindWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 3181551d61d..93828af2c52 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -433,7 +433,7 @@ export abstract class SimpleFindWidget extends Widget implements IVerticalSashLa } } -export const simpleFindWidgetSashBorder = registerColor('simpleFindWidget.sashBorder', { dark: '#454545', light: '#C8C8C8', hcDark: '6FC3DF', hcLight: '0F4A85' }, nls.localize('simpleFindWidget.sashBorder', 'Border color of the sash border.')); +export const simpleFindWidgetSashBorder = registerColor('simpleFindWidget.sashBorder', { dark: '#454545', light: '#C8C8C8', hcDark: '#6FC3DF', hcLight: '#0F4A85' }, nls.localize('simpleFindWidget.sashBorder', 'Border color of the sash border.')); registerThemingParticipant((theme, collector) => { const resizeBorderBackground = theme.getColor(simpleFindWidgetSashBorder); From 85586bd1f4858870568b4e1c5c40e9213c0d9b9c Mon Sep 17 00:00:00 2001 From: Jean Pierre Date: Mon, 8 May 2023 12:49:36 -0500 Subject: [PATCH 004/826] Fix continuation #175107 when the remote takes more than 20s to resolve, e.g. codespaces --- .../browser/terminalProfileService.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts index 73efd09b3f7..d161a40cb6e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts @@ -31,7 +31,8 @@ export class TerminalProfileService implements ITerminalProfileService { declare _serviceBrand: undefined; private _webExtensionContributedProfileContextKey: IContextKey; - private _profilesReadyBarrier: AutoOpenBarrier; + private _profilesReadyBarrier: AutoOpenBarrier | undefined; + private _profilesReadyPromise: Promise; private _availableProfiles: ITerminalProfile[] | undefined; private _contributedProfiles: IExtensionTerminalProfile[] = []; private _defaultProfileName?: string; @@ -41,7 +42,7 @@ export class TerminalProfileService implements ITerminalProfileService { private readonly _onDidChangeAvailableProfiles = new Emitter(); get onDidChangeAvailableProfiles(): Event { return this._onDidChangeAvailableProfiles.event; } - get profilesReady(): Promise { return this._profilesReadyBarrier.wait().then(() => { }); } + get profilesReady(): Promise { return this._profilesReadyPromise; } get availableProfiles(): ITerminalProfile[] { if (!this._platformConfigJustRefreshed) { this.refreshAvailableProfiles(); @@ -67,10 +68,14 @@ export class TerminalProfileService implements ITerminalProfileService { this._webExtensionContributedProfileContextKey = TerminalContextKeys.webExtensionContributedProfile.bindTo(this._contextKeyService); this._updateWebContextKey(); - // Wait up to 5 seconds for profiles to be ready so it's assured that we know the actual - // default terminal before launching the first terminal. This isn't expected to ever take - // this long. - this._profilesReadyBarrier = new AutoOpenBarrier(20000); + this._profilesReadyPromise = this._remoteAgentService.getEnvironment() + .then(() => { + // Wait up to 20 seconds for profiles to be ready so it's assured that we know the actual + // default terminal before launching the first terminal. This isn't expected to ever take + // this long. + this._profilesReadyBarrier = new AutoOpenBarrier(20000); + return this._profilesReadyBarrier.wait().then(() => { }); + }); this.refreshAvailableProfiles(); this._setupConfigListener(); } @@ -138,7 +143,7 @@ export class TerminalProfileService implements ITerminalProfileService { if (profilesChanged || contributedProfilesChanged) { this._availableProfiles = profiles; this._onDidChangeAvailableProfiles.fire(this._availableProfiles); - this._profilesReadyBarrier.open(); + this._profilesReadyBarrier!.open(); this._updateWebContextKey(); await this._refreshPlatformConfig(this._availableProfiles); } From 8d1a21408fd25c8977fb417b68b3197b879262da Mon Sep 17 00:00:00 2001 From: Daniel McCormack <52194107+demccormack@users.noreply.github.com> Date: Sat, 17 Jun 2023 17:46:01 +1200 Subject: [PATCH 005/826] Enable zsh and bash shell integration with set -u --- .../terminal/browser/media/shellIntegration-bash.sh | 10 +++++----- .../terminal/browser/media/shellIntegration-rc.zsh | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index 8c4f4fbbef0..292b77cdfdc 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -46,7 +46,7 @@ if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then fi # Apply EnvironmentVariableCollections if needed -if [ -n "$VSCODE_ENV_REPLACE" ]; then +if [ -n "${VSCODE_ENV_REPLACE:-}" ]; then IFS=':' read -ra ADDR <<< "$VSCODE_ENV_REPLACE" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" @@ -55,7 +55,7 @@ if [ -n "$VSCODE_ENV_REPLACE" ]; then done builtin unset VSCODE_ENV_REPLACE fi -if [ -n "$VSCODE_ENV_PREPEND" ]; then +if [ -n "${VSCODE_ENV_PREPEND:-}" ]; then IFS=':' read -ra ADDR <<< "$VSCODE_ENV_PREPEND" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" @@ -64,7 +64,7 @@ if [ -n "$VSCODE_ENV_PREPEND" ]; then done builtin unset VSCODE_ENV_PREPEND fi -if [ -n "$VSCODE_ENV_APPEND" ]; then +if [ -n "${VSCODE_ENV_APPEND:-}" ]; then IFS=':' read -ra ADDR <<< "$VSCODE_ENV_APPEND" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" @@ -275,10 +275,10 @@ __vsc_prompt_cmd() { # PROMPT_COMMAND arrays and strings seem to be handled the same (handling only the first entry of # the array?) -__vsc_original_prompt_command=$PROMPT_COMMAND +__vsc_original_prompt_command=${PROMPT_COMMAND:-} if [[ -z "${bash_preexec_imported:-}" ]]; then - if [[ -n "$__vsc_original_prompt_command" && "$__vsc_original_prompt_command" != "__vsc_prompt_cmd" ]]; then + if [[ -n "${__vsc_original_prompt_command:-}" && "${__vsc_original_prompt_command:-}" != "__vsc_prompt_cmd" ]]; then PROMPT_COMMAND=__vsc_prompt_cmd_original else PROMPT_COMMAND=__vsc_prompt_cmd diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh index d4e21dacda7..09718cfbab2 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh @@ -39,7 +39,7 @@ if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then fi # Apply EnvironmentVariableCollections if needed -if [ -n "$VSCODE_ENV_REPLACE" ]; then +if [ -n "${VSCODE_ENV_REPLACE:-}" ]; then IFS=':' read -rA ADDR <<< "$VSCODE_ENV_REPLACE" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo ${ITEM%%=*})" @@ -47,7 +47,7 @@ if [ -n "$VSCODE_ENV_REPLACE" ]; then done unset VSCODE_ENV_REPLACE fi -if [ -n "$VSCODE_ENV_PREPEND" ]; then +if [ -n "${VSCODE_ENV_PREPEND:-}" ]; then IFS=':' read -rA ADDR <<< "$VSCODE_ENV_PREPEND" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo ${ITEM%%=*})" @@ -55,7 +55,7 @@ if [ -n "$VSCODE_ENV_PREPEND" ]; then done unset VSCODE_ENV_PREPEND fi -if [ -n "$VSCODE_ENV_APPEND" ]; then +if [ -n "${VSCODE_ENV_APPEND:-}" ]; then IFS=':' read -rA ADDR <<< "$VSCODE_ENV_APPEND" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo ${ITEM%%=*})" From f0c543908ad0435605ee5845bb6339cb9d8d7790 Mon Sep 17 00:00:00 2001 From: weartist Date: Sat, 17 Jun 2023 16:31:21 +0800 Subject: [PATCH 006/826] Fix #185343 --- .../contrib/terminal/common/terminalConfiguration.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index f247f219344..62275b88a3a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -503,16 +503,13 @@ const terminalConfiguration: IConfigurationNode = { [TerminalSettingId.LocalEchoStyle]: { description: localize('terminal.integrated.localEchoStyle', "Terminal style of locally echoed text; either a font style or an RGB color."), default: 'dim', - oneOf: [ + anyOf: [ { - type: 'string', - default: 'dim', - enum: ['bold', 'dim', 'italic', 'underlined', 'inverted'], + enum: ['bold', 'dim', 'italic', 'underlined', 'inverted', '#ff0000'], }, { type: 'string', format: 'color-hex', - default: '#ff0000', } ] }, From c6bcad53df7bef121bf804aa7add13f861469caf Mon Sep 17 00:00:00 2001 From: Sandeep Sen Date: Mon, 26 Jun 2023 21:02:27 +0000 Subject: [PATCH 007/826] Initial code for go telemetry --- .../electron-sandbox/workspaceTagsService.ts | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index 39a65eac896..b832b1d639d 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -244,6 +244,32 @@ const PyModulesToLookFor = [ 'playwright' ]; +const GoMetaModulesToLookFor = [ + 'github.com/Azure/azure-sdk-for-go/sdk/' +]; + +const GoModulesToLookFor = [ + 'github.com/Azure/azure-sdk-for-go/sdk/storage/azblob', + 'github.com/Azure/azure-sdk-for-go/sdk/storage/azfile', + 'github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue', + 'github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel', + 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azadmin', + 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates', + 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys', + 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets', + 'github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery', + 'github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs', + 'github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus', + 'github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig', + 'github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos', + 'github.com/Azure/azure-sdk-for-go/sdk/data/aztables', + 'github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry', + 'github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai', + 'github.com/Azure/azure-sdk-for-go/sdk/azidentity', + 'github.com/Azure/azure-sdk-for-go/sdk/azcore' +]; + + export class WorkspaceTagsService implements IWorkspaceTagsService { declare readonly _serviceBrand: undefined; private _tags: Tags | undefined; @@ -566,6 +592,24 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.py.azure-security-attestation" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-data-nspkg" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-data-tables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azadmin" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/aztables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azidentity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azcore" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ private async resolveWorkspaceTags(): Promise { @@ -624,6 +668,8 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { tags['workspace.py.app'] = nameSet.has('app.py'); tags['workspace.py.pyproject'] = nameSet.has('pyproject.toml'); + tags['workspace.go.mod'] = nameSet.has('go.mod'); + const mainActivity = nameSet.has('mainactivity.cs') || nameSet.has('mainactivity.fs'); const appDelegate = nameSet.has('appdelegate.cs') || nameSet.has('appdelegate.fs'); const androidManifest = nameSet.has('androidmanifest.xml'); @@ -752,6 +798,29 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { } }); + const goModPromises = getFilePromises('go.mod', this.fileService, this.textFileService, content => { + // TODO: Richard to write the code for parsing the go.mod file + // look for everything in require() and get the string value only discard version + const dependencies: string[] = splitLines(content.value); + for (const dependency of dependencies) { + // Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo` + const format1 = dependency.split('=='); + const format2 = dependency.split('>='); + const packageName = (format1.length === 2 ? format1[0] : format2[0]).trim(); + + // copied from line 728 function addPythonTags + if (GoModulesToLookFor.indexOf(packageName) > -1) { + tags['workspace.go.mod' + packageName] = true; + } + // not sure if we should keep this + for (const metaModule of GoMetaModulesToLookFor) { + if (packageName.startsWith(metaModule)) { + tags['workspace.go.mod' + metaModule] = true; + } + } + } + }); + const pomPromises = getFilePromises('pom.xml', this.fileService, this.textFileService, content => { try { let dependenciesContent; @@ -792,7 +861,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { }); }); - return Promise.all([...packageJsonPromises, ...requirementsTxtPromises, ...pipfilePromises, ...pomPromises, ...gradlePromises, ...androidPromises]).then(() => tags); + return Promise.all([...packageJsonPromises, ...requirementsTxtPromises, ...pipfilePromises, ...pomPromises, ...gradlePromises, ...androidPromises, ...goModPromises]).then(() => tags); }); } From b62087b19d5fd3525dab5481500cde91c795a4cb Mon Sep 17 00:00:00 2001 From: Sandeep Sen Date: Mon, 26 Jun 2023 21:58:54 +0000 Subject: [PATCH 008/826] Adding go mod parser --- .../electron-sandbox/workspaceTagsService.ts | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index b832b1d639d..4d5ff69fa1d 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -801,21 +801,36 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { const goModPromises = getFilePromises('go.mod', this.fileService, this.textFileService, content => { // TODO: Richard to write the code for parsing the go.mod file // look for everything in require() and get the string value only discard version - const dependencies: string[] = splitLines(content.value); - for (const dependency of dependencies) { - // Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo` - const format1 = dependency.split('=='); - const format2 = dependency.split('>='); - const packageName = (format1.length === 2 ? format1[0] : format2[0]).trim(); + const lines: string[] = splitLines(content.value); + let firstRequireBlockFound: boolean = false; - // copied from line 728 function addPythonTags - if (GoModulesToLookFor.indexOf(packageName) > -1) { - tags['workspace.go.mod' + packageName] = true; + for (let i = 0; i < lines.length; i++) { + const line: string = lines[i].trim(); + + if (line.startsWith('require (')) { + if (!firstRequireBlockFound) { + firstRequireBlockFound = true; + continue; + } else { + break; } - // not sure if we should keep this - for (const metaModule of GoMetaModulesToLookFor) { - if (packageName.startsWith(metaModule)) { - tags['workspace.go.mod' + metaModule] = true; + } + + if (line.startsWith(')')) { + break; + } + + if (firstRequireBlockFound && line !== '') { + const packageName: string = line.split(' ')[0].trim(); + // copied from line 728 function addPythonTags + if (GoModulesToLookFor.indexOf(packageName) > -1) { + tags['workspace.go.mod' + packageName] = true; + } + // not sure if we should keep this + for (const metaModule of GoMetaModulesToLookFor) { + if (packageName.startsWith(metaModule)) { + tags['workspace.go.mod' + metaModule] = true; + } } } } From af15aac09c3ed2df82bdc774fa539aa9e4751417 Mon Sep 17 00:00:00 2001 From: Sandeep Sen Date: Tue, 27 Jun 2023 20:54:56 +0000 Subject: [PATCH 009/826] Removing meta modules as Go does not need it --- .../electron-sandbox/workspaceTagsService.ts | 56 ++++++++----------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index 4d5ff69fa1d..10be76329c4 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -244,10 +244,6 @@ const PyModulesToLookFor = [ 'playwright' ]; -const GoMetaModulesToLookFor = [ - 'github.com/Azure/azure-sdk-for-go/sdk/' -]; - const GoModulesToLookFor = [ 'github.com/Azure/azure-sdk-for-go/sdk/storage/azblob', 'github.com/Azure/azure-sdk-for-go/sdk/storage/azfile', @@ -799,41 +795,33 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { }); const goModPromises = getFilePromises('go.mod', this.fileService, this.textFileService, content => { - // TODO: Richard to write the code for parsing the go.mod file - // look for everything in require() and get the string value only discard version - const lines: string[] = splitLines(content.value); - let firstRequireBlockFound: boolean = false; - - for (let i = 0; i < lines.length; i++) { - const line: string = lines[i].trim(); - - if (line.startsWith('require (')) { - if (!firstRequireBlockFound) { - firstRequireBlockFound = true; - continue; - } else { - break; - } - } - - if (line.startsWith(')')) { - break; - } - - if (firstRequireBlockFound && line !== '') { - const packageName: string = line.split(' ')[0].trim(); - // copied from line 728 function addPythonTags - if (GoModulesToLookFor.indexOf(packageName) > -1) { - tags['workspace.go.mod' + packageName] = true; + try { + const lines: string[] = splitLines(content.value); + let firstRequireBlockFound: boolean = false; + for (let i = 0; i < lines.length; i++) { + const line: string = lines[i].trim(); + if (line.startsWith('require (')) { + if (!firstRequireBlockFound) { + firstRequireBlockFound = true; + continue; + } else { + break; + } } - // not sure if we should keep this - for (const metaModule of GoMetaModulesToLookFor) { - if (packageName.startsWith(metaModule)) { - tags['workspace.go.mod' + metaModule] = true; + if (line.startsWith(')')) { + break; + } + if (firstRequireBlockFound && line !== '') { + const packageName: string = line.split(' ')[0].trim(); + if (GoModulesToLookFor.indexOf(packageName) > -1) { + tags['workspace.go.mod' + packageName] = true; } } } } + catch (e) { + // Ignore errors when resolving file or parsing file contents + } }); const pomPromises = getFilePromises('pom.xml', this.fileService, this.textFileService, content => { From a98595156a09ccdad70b12844653e3c0632be59c Mon Sep 17 00:00:00 2001 From: Sandeep Sen Date: Tue, 27 Jun 2023 21:57:31 -0700 Subject: [PATCH 010/826] adding missing commas to GDPR segment --- .../electron-sandbox/workspaceTagsService.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index 10be76329c4..04d93982b2a 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -587,24 +587,24 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.py.azure-communication-administration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-security-attestation" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-data-nspkg" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-data-tables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azadmin" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/aztables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azidentity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.py.azure-data-tables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azadmin" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/aztables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azidentity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azcore" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ From fd89ea0423bccce0df5a38f8956bfe0aac88c3f7 Mon Sep 17 00:00:00 2001 From: kernel-sanders <1490292+kernel-sanders@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:13:34 -0400 Subject: [PATCH 011/826] fix: spawn code_serer with CREATE_NO_WINDOW fixes #184058 --- cli/src/tunnels/code_server.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cli/src/tunnels/code_server.rs b/cli/src/tunnels/code_server.rs index 1246e1c9441..fe257a64314 100644 --- a/cli/src/tunnels/code_server.rs +++ b/cli/src/tunnels/code_server.rs @@ -541,6 +541,15 @@ impl<'a> ServerBuilder<'a> { debug!(self.logger, "Starting server with command... {:?}", cmd); + // On Windows spawning a code-server binary will run cmd.exe /c C:\path\to\code-server.cmd... + // This spawns a cmd.exe window for the user, which if they close will kill the code-server process + // and disconnect the tunnel. To prevent this, pass the CREATE_NO_WINDOW flag to the Command + // only on Windows. + // Original issue: https://github.com/microsoft/vscode/issues/184058 + // Partial fix: https://github.com/microsoft/vscode/pull/184621 + #[cfg(target_os = "windows")] + let cmd = cmd.creation_flags(0x08000000); // 0x08000000 == CREATE_NO_WINDOW see https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags + let child = cmd .stderr(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) From 7a5f6f0007d43b772f5a1e5a0a2da2328b53d709 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 30 Jun 2023 16:12:57 +0600 Subject: [PATCH 012/826] when the content is not focused it should not disappear --- .../editor/contrib/hover/browser/contentHover.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index f03cf308b61..a00c9943d92 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -195,7 +195,9 @@ export class ContentHoverController extends Disposable { private _setCurrentResult(hoverResult: HoverResult | null): void { if (this._currentResult === hoverResult) { - // avoid updating the DOM to avoid resetting the user selection + if (hoverResult === null && !this._widget.isFocused) { + this._widget.hide(); + } return; } if (hoverResult && hoverResult.messages.length === 0) { @@ -205,6 +207,9 @@ export class ContentHoverController extends Disposable { if (this._currentResult) { this._renderMessages(this._currentResult.anchor, this._currentResult.messages); } else { + if (this._widget.isFocused) { + return; + } this._widget.hide(); } } @@ -479,6 +484,10 @@ export class ContentHoverWidget extends ResizableContentWidget { return this._hoverVisibleKey.get() ?? false; } + public get isFocused(): boolean { + return this._hoverFocusedKey.get() ?? false; + } + constructor( editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService @@ -727,10 +736,7 @@ export class ContentHoverWidget extends ResizableContentWidget { } public hide(): void { - if (!this._visibleData) { - return; - } - const stoleFocus = this._visibleData.stoleFocus; + const stoleFocus = this._visibleData?.stoleFocus; this._setHoverData(undefined); this._resizableNode.maxSize = new dom.Dimension(Infinity, Infinity); this._resizableNode.clearSashHoverState(); From 721629bd1a78ef1f3facfae766f3ba2f05556eab Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 30 Jun 2023 17:15:37 +0600 Subject: [PATCH 013/826] removing the assert type on the active session to be able to spawn several sessions one after the other --- .../workbench/contrib/inlineChat/browser/inlineChatController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index ba825ebb44a..1e975917f3d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -214,7 +214,6 @@ export class InlineChatController implements IEditorContribution { } private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { - assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); let session: Session | undefined = options.existingSession; From cc92e04a08a1d4f0b94ac26696a2aa1bebbbf142 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 30 Jun 2023 17:32:10 +0600 Subject: [PATCH 014/826] adding some code, need to verify what causes the error in dev tools --- .../contrib/inlineChat/browser/inlineChatController.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 1e975917f3d..8f6bc67f54a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -214,6 +214,12 @@ export class InlineChatController implements IEditorContribution { } private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { + if (this._activeSession) { + this._sessionStore.clear(); + this._inlineChatSessionService.releaseSession(this._activeSession); + await this[State.PAUSE](); + } + assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); let session: Session | undefined = options.existingSession; From 7389ceeaded34c2d9d7186bc9141522cd9376f85 Mon Sep 17 00:00:00 2001 From: yshaojun Date: Mon, 3 Jul 2023 23:43:31 +0800 Subject: [PATCH 015/826] fix: color hints may display twice(#175476) --- src/vs/editor/common/viewModel/modelLineProjection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/viewModel/modelLineProjection.ts b/src/vs/editor/common/viewModel/modelLineProjection.ts index 81be5db0a10..e66499cd98e 100644 --- a/src/vs/editor/common/viewModel/modelLineProjection.ts +++ b/src/vs/editor/common/viewModel/modelLineProjection.ts @@ -193,7 +193,7 @@ class ModelLineProjection implements IModelLineProjection { if (options.inlineClassName) { const offset = (outputLineIndex > 0 ? lineBreakData.wrappedTextIndentLength : 0); const start = offset + Math.max(injectedTextStartOffsetInInputWithInjections - lineStartOffsetInInputWithInjections, 0); - const end = offset + Math.min(injectedTextEndOffsetInInputWithInjections - lineStartOffsetInInputWithInjections, lineEndOffsetInInputWithInjections); + const end = offset + Math.min(injectedTextEndOffsetInInputWithInjections - lineStartOffsetInInputWithInjections, lineEndOffsetInInputWithInjections - lineStartOffsetInInputWithInjections); if (start !== end) { inlineDecorations.push(new SingleLineInlineDecoration(start, end, options.inlineClassName, options.inlineClassNameAffectsLetterSpacing!)); } From 7fd34784adde658e335489da69e71262ca1e1340 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 3 Jul 2023 10:24:58 -0700 Subject: [PATCH 016/826] defer buffer creation --- .../browser/terminal.accessibility.contribution.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index a121b0c4019..23a3f640347 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -26,6 +26,7 @@ import { Terminal } from 'xterm'; class AccessibleBufferContribution extends DisposableStore implements ITerminalContribution { static readonly ID = 'terminal.accessible-buffer'; + private _xterm: IXtermTerminal & { raw: Terminal } | undefined; static get(instance: ITerminalInstance): AccessibleBufferContribution | null { return instance.getContribution(AccessibleBufferContribution.ID); } @@ -40,11 +41,15 @@ class AccessibleBufferContribution extends DisposableStore implements ITerminalC super(); } layout(xterm: IXtermTerminal & { raw: Terminal }): void { - if (!this._accessibleBufferWidget) { - this._accessibleBufferWidget = this.add(this._instantiationService.createInstance(AccessibleBufferWidget, this._instance, xterm)); - } + this._xterm = xterm; } async show(): Promise { + if (!this._xterm) { + return; + } + if (!this._accessibleBufferWidget) { + this._accessibleBufferWidget = this.add(this._instantiationService.createInstance(AccessibleBufferWidget, this._instance, this._xterm)); + } await this._accessibleBufferWidget?.show(); } From 0ef9e9a0fa1d13e8b20f96fb7f142daabc4b6e8e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 3 Jul 2023 10:27:04 -0700 Subject: [PATCH 017/826] rm ? --- .../browser/terminal.accessibility.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index 23a3f640347..c59c6db03e2 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -50,7 +50,7 @@ class AccessibleBufferContribution extends DisposableStore implements ITerminalC if (!this._accessibleBufferWidget) { this._accessibleBufferWidget = this.add(this._instantiationService.createInstance(AccessibleBufferWidget, this._instance, this._xterm)); } - await this._accessibleBufferWidget?.show(); + await this._accessibleBufferWidget.show(); } async createCommandQuickPick(): Promise | undefined> { From c4ab63fb37198ce82609149be50c86d19d1a9a25 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 7 Jul 2023 11:19:12 -0700 Subject: [PATCH 018/826] treat soon-to-be-autosaved as saving --- .../contrib/customEditor/browser/customEditorInput.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 964a6a0443b..3f40d264574 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -22,7 +22,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { IOverlayWebview, IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; interface CustomEditorInputInitInfo { @@ -302,6 +302,14 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return (await this.rename(groupId, target))?.editor; } + override isSaving(): boolean { + if (this.isDirty() && !this.hasCapability(EditorInputCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + return true; // will be saved soon + } + + return super.isSaving(); + } + public override async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { if (this._modelRef) { return this._modelRef.object.revert(options); From 7eaae680dc341be01f6563bdd0e43c7519ecd086 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 10 Jul 2023 10:59:42 +0200 Subject: [PATCH 019/826] adding some comments that will be removed later --- .../inlineChat/browser/inlineChatController.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 8f6bc67f54a..7a9d3d2ee99 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -215,10 +215,14 @@ export class InlineChatController implements IEditorContribution { private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { if (this._activeSession) { + console.log('before clearing the session store'); this._sessionStore.clear(); + console.log('before releasing teh session'); this._inlineChatSessionService.releaseSession(this._activeSession); + console.log('before calling pause'); await this[State.PAUSE](); } + console.log('this._activeSession after the cleaning : ', this._activeSession); assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); @@ -282,6 +286,8 @@ export class InlineChatController implements IEditorContribution { } private async [State.INIT_UI](options: InlineChatRunOptions): Promise { + console.log('inside of init ui'); + console.log('this._activeSession : ', this._activeSession); assertType(this._activeSession); // hide/cancel inline completions when invoking IE @@ -347,11 +353,14 @@ export class InlineChatController implements IEditorContribution { })); if (!this._activeSession.lastExchange) { + console.log('before waiting for input'); return State.WAIT_FOR_INPUT; } else if (options.isUnstashed) { delete options.isUnstashed; + console.log('before apply response'); return State.APPLY_RESPONSE; } else { + console.log('before show response'); return State.SHOW_RESPONSE; } } @@ -631,6 +640,8 @@ export class InlineChatController implements IEditorContribution { private async [State.PAUSE]() { + console.log('entered into pause state'); + this._ctxDidEdit.reset(); this._ctxUserDidEdit.reset(); this._ctxLastResponseType.reset(); @@ -650,6 +661,7 @@ export class InlineChatController implements IEditorContribution { } private async [State.ACCEPT]() { + console.log('inside of accept'); assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); From 55613b5f13d009a2c0d32eb95b45b88024d4eecd Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 10 Jul 2023 15:31:45 +0200 Subject: [PATCH 020/826] adding some console logs, added todo --- .../browser/inlineChatController.ts | 50 ++++++++++++++++--- .../browser/inlineChatStrategies.ts | 5 +- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 7a9d3d2ee99..05cb6b8e8df 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -134,6 +134,7 @@ export class InlineChatController implements IEditorContribution { } this._log('session RESUMING', e); + console.log('before calling create session of the constructor'); await this._nextState(State.CREATE_SESSION, { existingSession }); this._log('session done or paused'); })); @@ -141,6 +142,7 @@ export class InlineChatController implements IEditorContribution { } dispose(): void { + console.log('inside of dispose'); this._stashedSession.clear(); this.finishExistingSession(); this._store.dispose(); @@ -174,10 +176,10 @@ export class InlineChatController implements IEditorContribution { } async run(options: InlineChatRunOptions | undefined = {}): Promise { - this._log('session starting'); + this._log('session starting inside of run'); await this.finishExistingSession(); this._stashedSession.clear(); - + console.log('before calling create session inside of run'); await this._nextState(State.CREATE_SESSION, options); this._log('session done or paused'); } @@ -214,6 +216,8 @@ export class InlineChatController implements IEditorContribution { } private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { + console.log('inside of CREATE_SESSION'); + console.log('this._activeSession : ', this._activeSession); if (this._activeSession) { console.log('before clearing the session store'); this._sessionStore.clear(); @@ -235,6 +239,7 @@ export class InlineChatController implements IEditorContribution { if (!session) { const createSessionCts = new CancellationTokenSource(); const msgListener = Event.once(this._messages.event)(m => { + console.log('inside of the msgListener code of CREATE_SESSION'); this._log('state=_createSession) message received', m); if (m === Message.ACCEPT_INPUT) { // user accepted the input before having a session @@ -348,6 +353,7 @@ export class InlineChatController implements IEditorContribution { if (editIsOutsideOfWholeRange) { this._log('text changed outside of whole range, FINISH session'); + console.log('before the third finish existing session'); this.finishExistingSession(); } })); @@ -379,7 +385,7 @@ export class InlineChatController implements IEditorContribution { } - private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { + private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { assertType(this._activeSession); assertType(this._strategy); @@ -400,6 +406,7 @@ export class InlineChatController implements IEditorContribution { } else { const barrier = new Barrier(); const msgListener = Event.once(this._messages.event)(m => { + console.log('inside of msgListener of WAIT FOR INPUT'); this._log('state=_waitForInput) message received', m); message = m; barrier.open(); @@ -411,11 +418,19 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.selectAll(); if (message & (Message.CANCEL_INPUT | Message.CANCEL_SESSION)) { - return State.CANCEL; + console.log('inside of wait for input'); + console.log('entered into the case when message cancel session'); + await this[State.CANCEL](); + return; + // return State.CANCEL; } if (message & Message.ACCEPT_SESSION) { - return State.ACCEPT; + console.log('inside of wait for input'); + console.log('entered into the case when message accept'); + await this[State.ACCEPT](); + return; + // return State.ACCEPT; } if (message & Message.PAUSE_SESSION) { @@ -467,6 +482,7 @@ export class InlineChatController implements IEditorContribution { let message = Message.NONE; const msgListener = Event.once(this._messages.event)(m => { + console.log('inside of msgListener of MAKE REQUEST'); this._log('state=_makeRequest) message received', m); message = m; requestCts.cancel(); @@ -521,10 +537,14 @@ export class InlineChatController implements IEditorContribution { this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); if (message & Message.CANCEL_SESSION) { + console.log('inside of make request'); + console.log('cancelling the session'); return State.CANCEL; } else if (message & Message.PAUSE_SESSION) { return State.PAUSE; } else if (message & Message.ACCEPT_SESSION) { + console.log('inside of make request'); + console.log('accepting'); return State.ACCEPT; } else { return State.APPLY_RESPONSE; @@ -665,21 +685,31 @@ export class InlineChatController implements IEditorContribution { assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); + console.log('after assert type'); try { + console.log('before strategy apply'); await this._strategy.apply(); + console.log('after strategy apply'); + // TODO: ASK WHY DESPITE AWAIT AFTER STRATEFY NOT PRINTED BEFORE CREATE SESSION } catch (err) { + console.log('when error obtained'); this._dialogService.error(localize('err.apply', "Failed to apply changes.", toErrorMessage(err))); this._log('FAILED to apply changes'); this._log(err); } - this._inlineChatSessionService.releaseSession(this._activeSession); + console.log('before release session'); - this[State.PAUSE](); + await this._inlineChatSessionService.releaseSession(this._activeSession); + + console.log('before state pause'); + await this[State.PAUSE](); } private async [State.CANCEL]() { + console.log('inside of cancel the session'); + assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); @@ -694,7 +724,7 @@ export class InlineChatController implements IEditorContribution { this._log(err); } - this[State.PAUSE](); + await this[State.PAUSE](); this._stashedSession.clear(); if (!mySession.isUnstashed && mySession.lastExchange) { @@ -780,10 +810,12 @@ export class InlineChatController implements IEditorContribution { } acceptSession(): void { + console.log('inside of acceptSession method'); this._messages.fire(Message.ACCEPT_SESSION); } cancelSession() { + console.log('inside of cancelSession'); let result: string | undefined; if (this._strategy && this._activeSession) { const changedText = this._activeSession.asChangedText(); @@ -798,6 +830,8 @@ export class InlineChatController implements IEditorContribution { } async finishExistingSession(): Promise { + console.log('inside of finish existing session'); + console.log(this._activeSession); if (this._activeSession) { if (this._activeSession.editMode === EditMode.Preview) { this._log('finishing existing session, using CANCEL', this._activeSession.editMode); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 36e64ebf09a..019a4ecfd58 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -86,7 +86,7 @@ export class PreviewStrategy extends EditModeStrategy { } async apply() { - + console.log('at the beginning of the apply method of preview strategy'); if (!(this._session.lastExchange?.response instanceof EditResponse)) { return; } @@ -107,6 +107,7 @@ export class PreviewStrategy extends EditModeStrategy { modelN.pushStackElement(); } } + console.log('at the end of the apply method of preview strategy'); } async cancel(): Promise { @@ -279,6 +280,7 @@ export class LiveStrategy extends EditModeStrategy { } async apply() { + console.log('inside of apply of live strategy'); if (this._editCount > 0) { this._editor.pushUndoStop(); } @@ -286,6 +288,7 @@ export class LiveStrategy extends EditModeStrategy { await this._bulkEditService.apply(this._lastResponse.workspaceEdits); this._instaService.invokeFunction(showSingleCreateFile, this._lastResponse); } + console.log('at the end of apply for live strategy'); } async cancel() { From 29f37cc240b394df6fd5494514a153e55686d81b Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 11 Jul 2023 14:54:06 +0200 Subject: [PATCH 021/826] adding code in order to make the picked color representation disappear when resizing if it overlaps with the icon --- .../colorPicker/browser/colorPickerWidget.ts | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index 24b6e6408b3..6466b09430c 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -22,10 +22,22 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; const $ = dom.$; +function elementsOverlap(el1: HTMLElement, el2: HTMLElement) { + const domRect1 = el1.getBoundingClientRect(); + const domRect2 = el2.getBoundingClientRect(); + return !( + domRect1.top > domRect2.bottom || + domRect1.right < domRect2.left || + domRect1.bottom < domRect2.top || + domRect1.left > domRect2.right + ); +} + export class ColorPickerHeader extends Disposable { private readonly _domNode: HTMLElement; private readonly _pickedColorNode: HTMLElement; + private readonly _pickedColorRepresentation: HTMLElement; private readonly _originalColorNode: HTMLElement; private readonly _closeButton: CloseButton | null = null; private backgroundColor: Color; @@ -37,6 +49,8 @@ export class ColorPickerHeader extends Disposable { dom.append(container, this._domNode); this._pickedColorNode = dom.append(this._domNode, $('.picked-color')); + this._pickedColorRepresentation = dom.append(this._pickedColorNode, document.createElement('div')); + const icon = dom.append(this._pickedColorNode, $('.codicon.codicon-color-mode')); const tooltip = localize('clickToToggleColorOptions', "Click to toggle color options (rgb/hsl/hex)"); this._pickedColorNode.setAttribute('title', tooltip); @@ -66,6 +80,16 @@ export class ColorPickerHeader extends Disposable { this._domNode.classList.add('standalone-colorpicker'); this._closeButton = this._register(new CloseButton(this._domNode)); } + + const resizeObserver = new ResizeObserver(() => { + this._pickedColorRepresentation.style.display = 'block'; + if (elementsOverlap(this._pickedColorRepresentation, icon)) { + this._pickedColorRepresentation.style.display = 'none'; + } else { + this._pickedColorRepresentation.style.display = 'block'; + } + }); + resizeObserver.observe(this._domNode); } public get domNode(): HTMLElement { @@ -91,8 +115,7 @@ export class ColorPickerHeader extends Disposable { } private onDidChangePresentation(): void { - this._pickedColorNode.textContent = this.model.presentation ? this.model.presentation.label : ''; - this._pickedColorNode.prepend($('.codicon.codicon-color-mode')); + this._pickedColorRepresentation.textContent = this.model.presentation ? this.model.presentation.label : ''; } } From 65b21886d07e23bc34aba2acdec46c7f43e3ffb2 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 11 Jul 2023 15:04:55 +0200 Subject: [PATCH 022/826] Removing the check on top and bottom which are not needed --- .../editor/contrib/colorPicker/browser/colorPickerWidget.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index 6466b09430c..2f267fcaf0c 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -22,13 +22,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; const $ = dom.$; -function elementsOverlap(el1: HTMLElement, el2: HTMLElement) { +function elementsOverlapHorizontally(el1: HTMLElement, el2: HTMLElement) { const domRect1 = el1.getBoundingClientRect(); const domRect2 = el2.getBoundingClientRect(); return !( - domRect1.top > domRect2.bottom || domRect1.right < domRect2.left || - domRect1.bottom < domRect2.top || domRect1.left > domRect2.right ); } @@ -83,7 +81,7 @@ export class ColorPickerHeader extends Disposable { const resizeObserver = new ResizeObserver(() => { this._pickedColorRepresentation.style.display = 'block'; - if (elementsOverlap(this._pickedColorRepresentation, icon)) { + if (elementsOverlapHorizontally(this._pickedColorRepresentation, icon)) { this._pickedColorRepresentation.style.display = 'none'; } else { this._pickedColorRepresentation.style.display = 'block'; From d363a2fd7666c5efcad397132ebe21bb7e4f35e4 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 11 Jul 2023 15:37:41 +0200 Subject: [PATCH 023/826] setting some minimum dimensions on the content hover. --- src/vs/editor/contrib/hover/browser/contentHover.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index f03cf308b61..5cc8ab5028e 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -504,6 +504,7 @@ export class ContentHoverWidget extends ResizableContentWidget { this._hoverFocusedKey.set(false); })); this._setHoverData(undefined); + this._setMinimumDimensions(); this._layout(); this._editor.addContentWidget(this); } @@ -518,6 +519,15 @@ export class ContentHoverWidget extends ResizableContentWidget { return ContentHoverWidget.ID; } + private _setMinimumDimensions(): void { + const width = 50; + const height = this._editor.getOption(EditorOption.lineHeight) + 8; + const contentsDomNode = this._hover.contentsDomNode; + contentsDomNode.style.minWidth = width + 'px'; + contentsDomNode.style.minHeight = height + 'px'; + this._resizableNode.minSize = new dom.Dimension(width, height); + } + private static _applyDimensions(container: HTMLElement, width: number | string, height: number | string): void { const transformedWidth = typeof width === 'number' ? `${width}px` : width; const transformedHeight = typeof height === 'number' ? `${height}px` : height; From 9b8e6edaf1280d0f698d363b3f238365ede2c02a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 11 Jul 2023 16:28:24 +0200 Subject: [PATCH 024/826] using instead the CSS to set size --- .../colorPicker/browser/colorPicker.css | 6 +++++ .../colorPicker/browser/colorPickerWidget.ts | 22 ++----------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPicker.css b/src/vs/editor/contrib/colorPicker/browser/colorPicker.css index 62f891080fe..9b588999dd7 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPicker.css +++ b/src/vs/editor/contrib/colorPicker/browser/colorPicker.css @@ -50,6 +50,12 @@ flex: 1; } +.colorpicker-header .picked-color .picked-color-representation { + white-space: nowrap; + margin-left: 30px; + margin-right: 5px; +} + .colorpicker-header .picked-color .codicon { color: inherit; font-size: 14px; diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index 2f267fcaf0c..efc7053fe26 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -22,15 +22,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; const $ = dom.$; -function elementsOverlapHorizontally(el1: HTMLElement, el2: HTMLElement) { - const domRect1 = el1.getBoundingClientRect(); - const domRect2 = el2.getBoundingClientRect(); - return !( - domRect1.right < domRect2.left || - domRect1.left > domRect2.right - ); -} - export class ColorPickerHeader extends Disposable { private readonly _domNode: HTMLElement; @@ -48,7 +39,8 @@ export class ColorPickerHeader extends Disposable { this._pickedColorNode = dom.append(this._domNode, $('.picked-color')); this._pickedColorRepresentation = dom.append(this._pickedColorNode, document.createElement('div')); - const icon = dom.append(this._pickedColorNode, $('.codicon.codicon-color-mode')); + this._pickedColorRepresentation.classList.add('picked-color-representation'); + dom.append(this._pickedColorNode, $('.codicon.codicon-color-mode')); const tooltip = localize('clickToToggleColorOptions', "Click to toggle color options (rgb/hsl/hex)"); this._pickedColorNode.setAttribute('title', tooltip); @@ -78,16 +70,6 @@ export class ColorPickerHeader extends Disposable { this._domNode.classList.add('standalone-colorpicker'); this._closeButton = this._register(new CloseButton(this._domNode)); } - - const resizeObserver = new ResizeObserver(() => { - this._pickedColorRepresentation.style.display = 'block'; - if (elementsOverlapHorizontally(this._pickedColorRepresentation, icon)) { - this._pickedColorRepresentation.style.display = 'none'; - } else { - this._pickedColorRepresentation.style.display = 'block'; - } - }); - resizeObserver.observe(this._domNode); } public get domNode(): HTMLElement { From 0e34605600704b03450880c4cf8368f312049d6a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 11 Jul 2023 16:36:41 +0200 Subject: [PATCH 025/826] changing the word representation to presentation --- src/vs/editor/contrib/colorPicker/browser/colorPicker.css | 2 +- .../contrib/colorPicker/browser/colorPickerWidget.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPicker.css b/src/vs/editor/contrib/colorPicker/browser/colorPicker.css index 9b588999dd7..f84c7ba6f2b 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPicker.css +++ b/src/vs/editor/contrib/colorPicker/browser/colorPicker.css @@ -50,7 +50,7 @@ flex: 1; } -.colorpicker-header .picked-color .picked-color-representation { +.colorpicker-header .picked-color .picked-color-presentation { white-space: nowrap; margin-left: 30px; margin-right: 5px; diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index efc7053fe26..6c8cc3ab4e0 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -26,7 +26,7 @@ export class ColorPickerHeader extends Disposable { private readonly _domNode: HTMLElement; private readonly _pickedColorNode: HTMLElement; - private readonly _pickedColorRepresentation: HTMLElement; + private readonly _pickedColorPresentation: HTMLElement; private readonly _originalColorNode: HTMLElement; private readonly _closeButton: CloseButton | null = null; private backgroundColor: Color; @@ -38,8 +38,8 @@ export class ColorPickerHeader extends Disposable { dom.append(container, this._domNode); this._pickedColorNode = dom.append(this._domNode, $('.picked-color')); - this._pickedColorRepresentation = dom.append(this._pickedColorNode, document.createElement('div')); - this._pickedColorRepresentation.classList.add('picked-color-representation'); + this._pickedColorPresentation = dom.append(this._pickedColorNode, document.createElement('div')); + this._pickedColorPresentation.classList.add('picked-color-presentation'); dom.append(this._pickedColorNode, $('.codicon.codicon-color-mode')); const tooltip = localize('clickToToggleColorOptions', "Click to toggle color options (rgb/hsl/hex)"); @@ -95,7 +95,7 @@ export class ColorPickerHeader extends Disposable { } private onDidChangePresentation(): void { - this._pickedColorRepresentation.textContent = this.model.presentation ? this.model.presentation.label : ''; + this._pickedColorPresentation.textContent = this.model.presentation ? this.model.presentation.label : ''; } } From 4a6fe0869e16799f425acca556dfcd9d7df17e51 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 11 Jul 2023 22:38:49 +0200 Subject: [PATCH 026/826] Fixes https://github.com/microsoft/vscode-internalbacklog/issues/4466 --- .../diff/algorithms/joinSequenceDiffs.ts | 10 +- .../test/node/diffing/diffingFixture.test.ts | 5 +- .../fixtures/invalid-diff-trimws/1.tst | 815 +++++++++++++++++ .../fixtures/invalid-diff-trimws/2.tst | 832 ++++++++++++++++++ .../advanced.expected.diff.json | 60 ++ .../legacy.expected.diff.json | 55 ++ 6 files changed, 1771 insertions(+), 6 deletions(-) create mode 100644 src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/1.tst create mode 100644 src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/2.tst create mode 100644 src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json create mode 100644 src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/legacy.expected.diff.json diff --git a/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts b/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts index 78fc69c2044..5afffbae370 100644 --- a/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts +++ b/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts @@ -45,14 +45,16 @@ export function smoothenSequenceDiffs(sequence1: ISequence, sequence2: ISequence * Improved diff: [{Add ", Foo" after Bar}] */ export function joinSequenceDiffs(sequence1: ISequence, sequence2: ISequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] { - const result: SequenceDiff[] = []; - if (sequenceDiffs.length > 0) { - result.push(sequenceDiffs[0]); + if (sequenceDiffs.length === 0) { + return sequenceDiffs; } + const result: SequenceDiff[] = []; + result.push(sequenceDiffs[0]); + // First move them all to the left as much as possible and join them if possible for (let i = 1; i < sequenceDiffs.length; i++) { - const prevResult = sequenceDiffs[i - 1]; + const prevResult = result[result.length - 1]; let cur = sequenceDiffs[i]; if (cur.seq1Range.isEmpty || cur.seq2Range.isEmpty) { diff --git a/src/vs/editor/test/node/diffing/diffingFixture.test.ts b/src/vs/editor/test/node/diffing/diffingFixture.test.ts index a66961863a3..2cdcb08d472 100644 --- a/src/vs/editor/test/node/diffing/diffingFixture.test.ts +++ b/src/vs/editor/test/node/diffing/diffingFixture.test.ts @@ -40,7 +40,8 @@ suite('diff fixtures', () => { const diffingAlgo = diffingAlgoName === 'legacy' ? new SmartLinesDiffComputer() : new StandardLinesDiffComputer(); - const diff = diffingAlgo.computeDiff(firstContentLines, secondContentLines, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }); + const ignoreTrimWhitespace = folder.indexOf('trimws') >= 0; + const diff = diffingAlgo.computeDiff(firstContentLines, secondContentLines, { ignoreTrimWhitespace, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }); function getDiffs(changes: readonly LineRangeMapping[]): IDetailedDiff[] { return changes.map(c => ({ @@ -112,7 +113,7 @@ suite('diff fixtures', () => { } test(`test`, () => { - runTest('issue-185779', 'advanced'); + runTest('invalid-diff-trimws', 'advanced'); }); for (const folder of folders) { diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/1.tst b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/1.tst new file mode 100644 index 00000000000..a859fd42b23 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/1.tst @@ -0,0 +1,815 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, WorkspaceFolder } from 'vscode'; +import TelemetryReporter from '@vscode/extension-telemetry'; +import { Repository, RepositoryState } from './repository'; +import { memoize, sequentialize, debounce } from './decorators'; +import { dispose, anyEvent, filterEvent, isDescendant, pathEquals, toDisposable, eventToPromise } from './util'; +import { Git } from './git'; +import * as path from 'path'; +import * as fs from 'fs'; +import { fromGitUri } from './uri'; +import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider } from './api/git'; +import { Askpass } from './askpass'; +import { IPushErrorHandlerRegistry } from './pushError'; +import { ApiRepository } from './api/api1'; +import { IRemoteSourcePublisherRegistry } from './remotePublisher'; +import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; +import { IBranchProtectionProviderRegistry } from './branchProtection'; + +class ClosedRepositoriesManager { + + private _repositories: Set; + get repositories(): string[] { + return [...this._repositories.values()]; + } + + constructor(private readonly workspaceState: Memento) { + this._repositories = new Set(workspaceState.get('closedRepositories', [])); + this.onDidChangeRepositories(); + } + + addRepository(repository: string): void { + this._repositories.add(repository); + this.onDidChangeRepositories(); + } + + deleteRepository(repository: string): boolean { + const result = this._repositories.delete(repository); + if (result) { + this.onDidChangeRepositories(); + } + + return result; + } + + isRepositoryClosed(repository: string): boolean { + return this._repositories.has(repository); + } + + private onDidChangeRepositories(): void { + this.workspaceState.update('closedRepositories', [...this._repositories.values()]); + commands.executeCommand('setContext', 'git.closedRepositoryCount', this._repositories.size); + } +} + +class ParentRepositoriesManager { + + /** + * Key - normalized path used in user interface + * Value - value indicating whether the repository should be opened + */ + private _repositories = new Set; + get repositories(): string[] { + return [...this._repositories.values()]; + } + + constructor(private readonly globalState: Memento) { + this.onDidChangeRepositories(); + } + + addRepository(repository: string): void { + this._repositories.add(repository); + this.onDidChangeRepositories(); + } + + deleteRepository(repository: string): boolean { + const result = this._repositories.delete(repository); + if (result) { + this.onDidChangeRepositories(); + } + + return result; + } + + hasRepository(repository: string): boolean { + return this._repositories.has(repository); + } + + openRepository(repository: string): void { + this.globalState.update(`parentRepository:${repository}`, true); + this.deleteRepository(repository); + } + + private onDidChangeRepositories(): void { + commands.executeCommand('setContext', 'git.parentRepositoryCount', this._repositories.size); + } +} + +class UnsafeRepositoriesManager { + + /** + * Key - normalized path used in user interface + * Value - path extracted from the output of the `git status` command + * used when calling `git config --global --add safe.directory` + */ + private _repositories = new Map(); + get repositories(): string[] { + return [...this._repositories.keys()]; + } + + constructor() { + this.onDidChangeRepositories(); + } + + addRepository(repository: string, path: string): void { + this._repositories.set(repository, path); + this.onDidChangeRepositories(); + } + + deleteRepository(repository: string): boolean { + const result = this._repositories.delete(repository); + if (result) { + this.onDidChangeRepositories(); + } + + return result; + } + + getRepositoryPath(repository: string): string | undefined { + return this._repositories.get(repository); + } + + hasRepository(repository: string): boolean { + return this._repositories.has(repository); + } + + private onDidChangeRepositories(): void { + commands.executeCommand('setContext', 'git.unsafeRepositoryCount', this._repositories.size); + } +} + +export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry { + + private _onDidOpenRepository = new EventEmitter(); + readonly onDidOpenRepository: Event = this._onDidOpenRepository.event; + + private _onDidCloseRepository = new EventEmitter(); + readonly onDidCloseRepository: Event = this._onDidCloseRepository.event; + + private _onDidChangeRepository = new EventEmitter(); + readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; + + private _onDidChangeOriginalResource = new EventEmitter(); + readonly onDidChangeOriginalResource: Event = this._onDidChangeOriginalResource.event; + + private openRepositories: OpenRepository[] = []; + get repositories(): Repository[] { return this.openRepositories.map(r => r.repository); } + + private possibleGitRepositoryPaths = new Set(); + + private _onDidChangeState = new EventEmitter(); + readonly onDidChangeState = this._onDidChangeState.event; + + private _onDidPublish = new EventEmitter(); + readonly onDidPublish = this._onDidPublish.event; + + firePublishEvent(repository: Repository, branch?: string) { + this._onDidPublish.fire({ repository: new ApiRepository(repository), branch: branch }); + } + + private _state: State = 'uninitialized'; + get state(): State { return this._state; } + + setState(state: State): void { + this._state = state; + this._onDidChangeState.fire(state); + commands.executeCommand('setContext', 'git.state', state); + } + + @memoize + get isInitialized(): Promise { + if (this._state === 'initialized') { + return Promise.resolve(); + } + + return eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized')) as Promise; + } + + private remoteSourcePublishers = new Set(); + + private _onDidAddRemoteSourcePublisher = new EventEmitter(); + readonly onDidAddRemoteSourcePublisher = this._onDidAddRemoteSourcePublisher.event; + + private _onDidRemoveRemoteSourcePublisher = new EventEmitter(); + readonly onDidRemoveRemoteSourcePublisher = this._onDidRemoveRemoteSourcePublisher.event; + + private postCommitCommandsProviders = new Set(); + + private _onDidChangePostCommitCommandsProviders = new EventEmitter(); + readonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event; + + private branchProtectionProviders = new Map>(); + + private _onDidChangeBranchProtectionProviders = new EventEmitter(); + readonly onDidChangeBranchProtectionProviders = this._onDidChangeBranchProtectionProviders.event; + + private pushErrorHandlers = new Set(); + + private _unsafeRepositoriesManager: UnsafeRepositoriesManager; + get unsafeRepositories(): string[] { + return this._unsafeRepositoriesManager.repositories; + } + + private _parentRepositoriesManager: ParentRepositoriesManager; + get parentRepositories(): string[] { + return this._parentRepositoriesManager.repositories; + } + + private _closedRepositoriesManager: ClosedRepositoriesManager; + get closedRepositories(): string[] { + return [...this._closedRepositoriesManager.repositories]; + } + + /** + * We maintain a map containing both the path and the canonical path of the + * workspace folders. We are doing this as `git.exe` expands the symbolic links + * while there are scenarios in which VS Code does not. + * + * Key - path of the workspace folder + * Value - canonical path of the workspace folder + */ + private _workspaceFolders = new Map(); + + private disposables: Disposable[] = []; + + constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) { + // Repositories managers + this._closedRepositoriesManager = new ClosedRepositoriesManager(workspaceState); + this._parentRepositoriesManager = new ParentRepositoriesManager(globalState); + this._unsafeRepositoriesManager = new UnsafeRepositoriesManager(); + + workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables); + window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables); + workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); + + const fsWatcher = workspace.createFileSystemWatcher('**'); + this.disposables.push(fsWatcher); + + const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete); + const onGitRepositoryChange = filterEvent(onWorkspaceChange, uri => /\/\.git/.test(uri.path)); + const onPossibleGitRepositoryChange = filterEvent(onGitRepositoryChange, uri => !this.getRepository(uri)); + onPossibleGitRepositoryChange(this.onPossibleGitRepositoryChange, this, this.disposables); + + this.setState('uninitialized'); + this.doInitialScan().finally(() => this.setState('initialized')); + } + + private async doInitialScan(): Promise { + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); + const parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt'); + + // Initial repository scan function + const initialScanFn = () => Promise.all([ + this.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }), + this.onDidChangeVisibleTextEditors(window.visibleTextEditors), + this.scanWorkspaceFolders() + ]); + + if (config.get('showProgress', true)) { + await window.withProgress({ location: ProgressLocation.SourceControl }, initialScanFn); + } else { + await initialScanFn(); + } + + if (this.parentRepositories.length !== 0 && + parentRepositoryConfig === 'prompt') { + // Parent repositories notification + this.showParentRepositoryNotification(); + } else if (this.unsafeRepositories.length !== 0) { + // Unsafe repositories notification + this.showUnsafeRepositoryNotification(); + } + + /* __GDPR__ + "git.repositoryInitialScan" : { + "owner": "lszomoru", + "autoRepositoryDetection": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Setting that controls the initial repository scan" }, + "repositoryCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Number of repositories opened during initial repository scan" } + } + */ + this.telemetryReporter.sendTelemetryEvent('git.repositoryInitialScan', { autoRepositoryDetection: String(autoRepositoryDetection) }, { repositoryCount: this.openRepositories.length }); + } + + /** + * Scans each workspace folder, looking for git repositories. By + * default it scans one level deep but that can be changed using + * the git.repositoryScanMaxDepth setting. + */ + private async scanWorkspaceFolders(): Promise { + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); + this.logger.trace(`[swsf] Scan workspace sub folders. autoRepositoryDetection=${autoRepositoryDetection}`); + + if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') { + return; + } + + await Promise.all((workspace.workspaceFolders || []).map(async folder => { + const root = folder.uri.fsPath; + this.logger.trace(`[swsf] Workspace folder: ${root}`); + + // Workspace folder children + const repositoryScanMaxDepth = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanMaxDepth', 1); + const repositoryScanIgnoredFolders = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanIgnoredFolders', []); + + const subfolders = new Set(await this.traverseWorkspaceFolder(root, repositoryScanMaxDepth, repositoryScanIgnoredFolders)); + + // Repository scan folders + const scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || []; + this.logger.trace(`[swsf] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`); + + for (const scanPath of scanPaths) { + if (scanPath === '.git') { + this.logger.trace('[swsf] \'.git\' not supported in \'git.scanRepositories\' setting.'); + continue; + } + + if (path.isAbsolute(scanPath)) { + const notSupportedMessage = l10n.t('Absolute paths not supported in "git.scanRepositories" setting.'); + this.logger.warn(notSupportedMessage); + console.warn(notSupportedMessage); + continue; + } + + subfolders.add(path.join(root, scanPath)); + } + + this.logger.trace(`[swsf] Workspace scan sub folders: [${[...subfolders].join(', ')}]`); + await Promise.all([...subfolders].map(f => this.openRepository(f))); + })); + } + + private async traverseWorkspaceFolder(workspaceFolder: string, maxDepth: number, repositoryScanIgnoredFolders: string[]): Promise { + const result: string[] = []; + const foldersToTravers = [{ path: workspaceFolder, depth: 0 }]; + + while (foldersToTravers.length > 0) { + const currentFolder = foldersToTravers.shift()!; + + if (currentFolder.depth < maxDepth || maxDepth === -1) { + const children = await fs.promises.readdir(currentFolder.path, { withFileTypes: true }); + const childrenFolders = children + .filter(dirent => + dirent.isDirectory() && dirent.name !== '.git' && + !repositoryScanIgnoredFolders.find(f => pathEquals(dirent.name, f))) + .map(dirent => path.join(currentFolder.path, dirent.name)); + + result.push(...childrenFolders); + foldersToTravers.push(...childrenFolders.map(folder => { + return { path: folder, depth: currentFolder.depth + 1 }; + })); + } + } + + return result; + } + + private onPossibleGitRepositoryChange(uri: Uri): void { + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); + + if (autoRepositoryDetection === false) { + return; + } + + this.eventuallyScanPossibleGitRepository(uri.fsPath.replace(/\.git.*$/, '')); + } + + private eventuallyScanPossibleGitRepository(path: string) { + this.possibleGitRepositoryPaths.add(path); + this.eventuallyScanPossibleGitRepositories(); + } + + @debounce(500) + private eventuallyScanPossibleGitRepositories(): void { + for (const path of this.possibleGitRepositoryPaths) { + this.openRepository(path); + } + + this.possibleGitRepositoryPaths.clear(); + } + + private async onDidChangeWorkspaceFolders({ added, removed }: WorkspaceFoldersChangeEvent): Promise { + const possibleRepositoryFolders = added + .filter(folder => !this.getOpenRepository(folder.uri)); + + const activeRepositoriesList = window.visibleTextEditors + .map(editor => this.getRepository(editor.document.uri)) + .filter(repository => !!repository) as Repository[]; + + const activeRepositories = new Set(activeRepositoriesList); + const openRepositoriesToDispose = removed + .map(folder => this.getOpenRepository(folder.uri)) + .filter(r => !!r) + .filter(r => !activeRepositories.has(r!.repository)) + .filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[]; + + openRepositoriesToDispose.forEach(r => r.dispose()); + this.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); + await Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath))); + } + + private onDidChangeConfiguration(): void { + const possibleRepositoryFolders = (workspace.workspaceFolders || []) + .filter(folder => workspace.getConfiguration('git', folder.uri).get('enabled') === true) + .filter(folder => !this.getOpenRepository(folder.uri)); + + const openRepositoriesToDispose = this.openRepositories + .map(repository => ({ repository, root: Uri.file(repository.repository.root) })) + .filter(({ root }) => workspace.getConfiguration('git', root).get('enabled') !== true) + .map(({ repository }) => repository); + + this.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); + possibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath)); + openRepositoriesToDispose.forEach(r => r.dispose()); + } + + private async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise { + if (!workspace.isTrusted) { + this.logger.trace('[svte] Workspace is not trusted.'); + return; + } + + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); + this.logger.trace(`[svte] Scan visible text editors. autoRepositoryDetection=${autoRepositoryDetection}`); + + if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') { + return; + } + + await Promise.all(editors.map(async editor => { + const uri = editor.document.uri; + + if (uri.scheme !== 'file') { + return; + } + + const repository = this.getRepository(uri); + + if (repository) { + this.logger.trace(`[svte] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`); + return; + } + + this.logger.trace(`[svte] Open repository for editor resource ${uri.fsPath}`); + await this.openRepository(path.dirname(uri.fsPath)); + })); + } + + @sequentialize + async openRepository(repoPath: string, openIfClosed = false): Promise { + this.logger.trace(`Opening repository: ${repoPath}`); + const existingRepository = await this.getRepositoryExact(repoPath); + if (existingRepository) { + this.logger.trace(`Repository for path ${repoPath} already exists: ${existingRepository.root})`); + return; + } + + const config = workspace.getConfiguration('git', Uri.file(repoPath)); + const enabled = config.get('enabled') === true; + + if (!enabled) { + this.logger.trace('Git is not enabled'); + return; + } + + if (!workspace.isTrusted) { + // Check if the folder is a bare repo: if it has a file named HEAD && `rev-parse --show -cdup` is empty + try { + fs.accessSync(path.join(repoPath, 'HEAD'), fs.constants.F_OK); + const result = await this.git.exec(repoPath, ['-C', repoPath, 'rev-parse', '--show-cdup']); + if (result.stderr.trim() === '' && result.stdout.trim() === '') { + this.logger.trace(`Bare repository: ${repoPath}`); + return; + } + } catch { + // If this throw, we should be good to open the repo (e.g. HEAD doesn't exist) + } + } + + try { + const { repositoryRoot, unsafeRepositoryMatch } = await this.getRepositoryRoot(repoPath); + this.logger.trace(`Repository root for path ${repoPath} is: ${repositoryRoot}`); + + const existingRepository = await this.getRepositoryExact(repositoryRoot); + if (existingRepository) { + this.logger.trace(`Repository for path ${repositoryRoot} already exists: ${existingRepository.root}`); + return; + } + + if (this.shouldRepositoryBeIgnored(repositoryRoot)) { + this.logger.trace(`Repository for path ${repositoryRoot} is ignored`); + return; + } + + // Handle git repositories that are in parent folders + const parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt'); + if (parentRepositoryConfig !== 'always' && this.globalState.get(`parentRepository:${repositoryRoot}`) !== true) { + const isRepositoryOutsideWorkspace = await this.isRepositoryOutsideWorkspace(repositoryRoot); + if (isRepositoryOutsideWorkspace) { + this.logger.trace(`Repository in parent folder: ${repositoryRoot}`); + + if (!this._parentRepositoriesManager.hasRepository(repositoryRoot)) { + // Show a notification if the parent repository is opened after the initial scan + if (this.state === 'initialized' && parentRepositoryConfig === 'prompt') { + this.showParentRepositoryNotification(); + } + + this._parentRepositoriesManager.addRepository(repositoryRoot); + } + + return; + } + } + + // Handle unsafe repositories + if (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) { + this.logger.trace(`Unsafe repository: ${repositoryRoot}`); + + // Show a notification if the unsafe repository is opened after the initial scan + if (this._state === 'initialized' && !this._unsafeRepositoriesManager.hasRepository(repositoryRoot)) { + this.showUnsafeRepositoryNotification(); + } + + this._unsafeRepositoriesManager.addRepository(repositoryRoot, unsafeRepositoryMatch[2]); + + return; + } + + // Handle repositories that were closed by the user + if (!openIfClosed && this._closedRepositoriesManager.isRepositoryClosed(repositoryRoot)) { + this.logger.trace(`Repository for path ${repositoryRoot} is closed`); + return; + } + + // Open repository + const dotGit = await this.git.getRepositoryDotGit(repositoryRoot); + const repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this, this.globalState, this.logger, this.telemetryReporter); + + this.open(repository); + this._closedRepositoriesManager.deleteRepository(repository.root); + + // Do not await this, we want SCM + // to know about the repo asap + repository.status(); + } catch (err) { + // noop + this.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${err}`); + } + } + + async openParentRepository(repoPath: string): Promise { + await this.openRepository(repoPath); + this._parentRepositoriesManager.openRepository(repoPath); + } + + private async getRepositoryRoot(repoPath: string): Promise<{ repositoryRoot: string; unsafeRepositoryMatch: RegExpMatchArray | null }> { + try { + const rawRoot = await this.git.getRepositoryRoot(repoPath); + + // This can happen whenever `path` has the wrong case sensitivity in case + // insensitive file systems https://github.com/microsoft/vscode/issues/33498 + return { repositoryRoot: Uri.file(rawRoot).fsPath, unsafeRepositoryMatch: null }; + } catch (err) { + // Handle unsafe repository + const unsafeRepositoryMatch = /^fatal: detected dubious ownership in repository at \'([^']+)\'[\s\S]*git config --global --add safe\.directory '?([^'\n]+)'?$/m.exec(err.stderr); + if (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) { + return { repositoryRoot: path.normalize(unsafeRepositoryMatch[1]), unsafeRepositoryMatch }; + } + + throw err; + } + } + + private shouldRepositoryBeIgnored(repositoryRoot: string): boolean { + const config = workspace.getConfiguration('git'); + const ignoredRepos = config.get('ignoredRepositories') || []; + + for (const ignoredRepo of ignoredRepos) { + if (path.isAbsolute(ignoredRepo)) { + if (pathEquals(ignoredRepo, repositoryRoot)) { + return true; + } + } else { + for (const folder of workspace.workspaceFolders || []) { + if (pathEquals(path.join(folder.uri.fsPath, ignoredRepo), repositoryRoot)) { + return true; + } + } + } + } + + return false; + } + + private open(repository: Repository): void { + this.logger.info(`Open repository: ${repository.root}`); + + const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed); + const disappearListener = onDidDisappearRepository(() => dispose()); + const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri })); + const originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri })); + + const shouldDetectSubmodules = workspace + .getConfiguration('git', Uri.file(repository.root)) + .get('detectSubmodules') as boolean; + + const submodulesLimit = workspace + .getConfiguration('git', Uri.file(repository.root)) + .get('detectSubmodulesLimit') as number; + + const checkForSubmodules = () => { + if (!shouldDetectSubmodules) { + this.logger.trace('Automatic detection of git submodules is not enabled.'); + return; + } + + if (repository.submodules.length > submodulesLimit) { + window.showWarningMessage(l10n.t('The "{0}" repository has {1} submodules which won\'t be opened automatically. You can still open each one individually by opening a file within.', path.basename(repository.root), repository.submodules.length)); + statusListener.dispose(); + } + + repository.submodules + .slice(0, submodulesLimit) + .map(r => path.join(repository.root, r.path)) + .forEach(p => { + this.logger.trace(`Opening submodule: '${p}'`); + this.eventuallyScanPossibleGitRepository(p); + }); + }; + + const updateMergeChanges = () => { + // set mergeChanges context + const mergeChanges: Uri[] = []; + for (const { repository } of this.openRepositories.values()) { + for (const state of repository.mergeGroup.resourceStates) { + mergeChanges.push(state.resourceUri); + } + } + commands.executeCommand('setContext', 'git.mergeChanges', mergeChanges); + }; + + const statusListener = repository.onDidRunGitStatus(() => { + checkForSubmodules(); + updateMergeChanges(); + }); + checkForSubmodules(); + + const updateOperationInProgressContext = () => { + let operationInProgress = false; + for (const { repository } of this.openRepositories.values()) { + if (repository.operations.shouldDisableCommands()) { + operationInProgress = true; + } + } + + commands.executeCommand('setContext', 'operationInProgress', operationInProgress); + }; + + const operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event); + const operationListener = operationEvent(() => updateOperationInProgressContext()); + updateOperationInProgressContext(); + + const dispose = () => { + disappearListener.dispose(); + changeListener.dispose(); + originalResourceChangeListener.dispose(); + statusListener.dispose(); + operationListener.dispose(); + repository.dispose(); + + this.openRepositories = this.openRepositories.filter(e => e !== openRepository); + this._onDidCloseRepository.fire(repository); + }; + + const openRepository = { repository, dispose }; + this.openRepositories.push(openRepository); + updateMergeChanges(); + this._onDidOpenRepository.fire(repository); + } + + close(repository: Repository): void { + const openRepository = this.getOpenRepository(repository); + + if (!openRepository) { + return; + } + + this.logger.info(`Close repository: ${repository.root}`); + this._closedRepositoriesManager.addRepository(openRepository.repository.root); + + openRepository.dispose(); + } + + async pickRepository(): Promise { + if (this.openRepositories.length === 0) { + throw new Error(l10n.t('There are no available repositories')); + } + + const picks = this.openRepositories.map((e, index) => new RepositoryPick(e.repository, index)); + const active = window.activeTextEditor; + const repository = active && this.getRepository(active.document.fileName); + const index = picks.findIndex(pick => pick.repository === repository); + + // Move repository pick containing the active text editor to appear first + if (index > -1) { + picks.unshift(...picks.splice(index, 1)); + } + + const placeHolder = l10n.t('Choose a repository'); + const pick = await window.showQuickPick(picks, { placeHolder }); + + return pick && pick.repository; + } + + getRepository(sourceControl: SourceControl): Repository | undefined; + getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined; + getRepository(path: string): Repository | undefined; + getRepository(resource: Uri): Repository | undefined; + getRepository(hint: any): Repository | undefined { + const liveRepository = this.getOpenRepository(hint); + return liveRepository && liveRepository.repository; + } + + private async getRepositoryExact(repoPath: string): Promise { + const repoPathCanonical = await fs.promises.realpath(repoPath, { encoding: 'utf8' }); + const openRepository = this.openRepositories.find(async r => { + const rootPathCanonical = await fs.promises.realpath(r.repository.root, { encoding: 'utf8' }); + return pathEquals(rootPathCanonical, repoPathCanonical); + }); + return openRepository?.repository; + } + + private getOpenRepository(repository: Repository): OpenRepository | undefined; + private getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined; + private getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined; + private getOpenRepository(path: string): OpenRepository | undefined; + private getOpenRepository(resource: Uri): OpenRepository | undefined; + private getOpenRepository(hint: any): OpenRepository | undefined { + if (!hint) { + return undefined; + } + + if (hint instanceof Repository) { + return this.openRepositories.filter(r => r.repository === hint)[0]; + } + + if (hint instanceof ApiRepository) { + return this.openRepositories.filter(r => r.repository === hint.repository)[0]; + } + + if (typeof hint === 'string') { + hint = Uri.file(hint); + } + + if (hint instanceof Uri) { + let resourcePath: string; + + if (hint.scheme === 'git') { + resourcePath = fromGitUri(hint).path; + } else { + resourcePath = hint.fsPath; + } + + outer: + for (const liveRepository of this.openRepositories.sort((a, b) => b.repository.root.length - a.repository.root.length)) { + if (!isDescendant(liveRepository.repository.root, resourcePath)) { + continue; + } + + for (const submodule of liveRepository.repository.submodules) { + const submoduleRoot = path.join(liveRepository.repository.root, submodule.path); + + if (isDescendant(submoduleRoot, resourcePath)) { + continue outer; + } + } + + return liveRepository; + } + + return undefined; + } + + for (const liveRepository of this.openRepositories) { + const repository = liveRepository.repository; + + if (hint === repository.sourceControl) { + return liveRepository; + } + + if (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup || hint === repository.untrackedGroup) { + return liveRepository; + } + } + + return undefined; + } + +} diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/2.tst b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/2.tst new file mode 100644 index 00000000000..d8d33a59a63 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/2.tst @@ -0,0 +1,832 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, WorkspaceFolder } from 'vscode'; +import TelemetryReporter from '@vscode/extension-telemetry'; +import { Repository, RepositoryState } from './repository'; +import { memoize, sequentialize, debounce } from './decorators'; +import { dispose, anyEvent, filterEvent, isDescendant, pathEquals, toDisposable, eventToPromise } from './util'; +import { Git } from './git'; +import * as path from 'path'; +import * as fs from 'fs'; +import { fromGitUri } from './uri'; +import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider } from './api/git'; +import { Askpass } from './askpass'; +import { IPushErrorHandlerRegistry } from './pushError'; +import { ApiRepository } from './api/api1'; +import { IRemoteSourcePublisherRegistry } from './remotePublisher'; +import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; +import { IBranchProtectionProviderRegistry } from './branchProtection'; + +class ClosedRepositoriesManager { + + private _repositories: Set; + get repositories(): string[] { + return [...this._repositories.values()]; + } + + constructor(private readonly workspaceState: Memento) { + this._repositories = new Set(workspaceState.get('closedRepositories', [])); + this.onDidChangeRepositories(); + } + + addRepository(repository: string): void { + this._repositories.add(repository); + this.onDidChangeRepositories(); + } + + deleteRepository(repository: string): boolean { + const result = this._repositories.delete(repository); + if (result) { + this.onDidChangeRepositories(); + } + + return result; + } + + isRepositoryClosed(repository: string): boolean { + return this._repositories.has(repository); + } + + private onDidChangeRepositories(): void { + this.workspaceState.update('closedRepositories', [...this._repositories.values()]); + commands.executeCommand('setContext', 'git.closedRepositoryCount', this._repositories.size); + } +} + +class ParentRepositoriesManager { + + /** + * Key - normalized path used in user interface + * Value - value indicating whether the repository should be opened + */ + private _repositories = new Set; + get repositories(): string[] { + return [...this._repositories.values()]; + } + + constructor(private readonly globalState: Memento) { + this.onDidChangeRepositories(); + } + + addRepository(repository: string): void { + this._repositories.add(repository); + this.onDidChangeRepositories(); + } + + deleteRepository(repository: string): boolean { + const result = this._repositories.delete(repository); + if (result) { + this.onDidChangeRepositories(); + } + + return result; + } + + hasRepository(repository: string): boolean { + return this._repositories.has(repository); + } + + openRepository(repository: string): void { + this.globalState.update(`parentRepository:${repository}`, true); + this.deleteRepository(repository); + } + + private onDidChangeRepositories(): void { + commands.executeCommand('setContext', 'git.parentRepositoryCount', this._repositories.size); + } +} + +class UnsafeRepositoriesManager { + + /** + * Key - normalized path used in user interface + * Value - path extracted from the output of the `git status` command + * used when calling `git config --global --add safe.directory` + */ + private _repositories = new Map(); + get repositories(): string[] { + return [...this._repositories.keys()]; + } + + constructor() { + this.onDidChangeRepositories(); + } + + addRepository(repository: string, path: string): void { + this._repositories.set(repository, path); + this.onDidChangeRepositories(); + } + + deleteRepository(repository: string): boolean { + const result = this._repositories.delete(repository); + if (result) { + this.onDidChangeRepositories(); + } + + return result; + } + + getRepositoryPath(repository: string): string | undefined { + return this._repositories.get(repository); + } + + hasRepository(repository: string): boolean { + return this._repositories.has(repository); + } + + private onDidChangeRepositories(): void { + commands.executeCommand('setContext', 'git.unsafeRepositoryCount', this._repositories.size); + } +} + +export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry { + + private _onDidOpenRepository = new EventEmitter(); + readonly onDidOpenRepository: Event = this._onDidOpenRepository.event; + + private _onDidCloseRepository = new EventEmitter(); + readonly onDidCloseRepository: Event = this._onDidCloseRepository.event; + + private _onDidChangeRepository = new EventEmitter(); + readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; + + private _onDidChangeOriginalResource = new EventEmitter(); + readonly onDidChangeOriginalResource: Event = this._onDidChangeOriginalResource.event; + + private openRepositories: OpenRepository[] = []; + get repositories(): Repository[] { return this.openRepositories.map(r => r.repository); } + + private possibleGitRepositoryPaths = new Set(); + + private _onDidChangeState = new EventEmitter(); + readonly onDidChangeState = this._onDidChangeState.event; + + private _onDidPublish = new EventEmitter(); + readonly onDidPublish = this._onDidPublish.event; + + firePublishEvent(repository: Repository, branch?: string) { + this._onDidPublish.fire({ repository: new ApiRepository(repository), branch: branch }); + } + + private _state: State = 'uninitialized'; + get state(): State { return this._state; } + + setState(state: State): void { + this._state = state; + this._onDidChangeState.fire(state); + commands.executeCommand('setContext', 'git.state', state); + } + + @memoize + get isInitialized(): Promise { + if (this._state === 'initialized') { + return Promise.resolve(); + } + + return eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized')) as Promise; + } + + private remoteSourcePublishers = new Set(); + + private _onDidAddRemoteSourcePublisher = new EventEmitter(); + readonly onDidAddRemoteSourcePublisher = this._onDidAddRemoteSourcePublisher.event; + + private _onDidRemoveRemoteSourcePublisher = new EventEmitter(); + readonly onDidRemoveRemoteSourcePublisher = this._onDidRemoveRemoteSourcePublisher.event; + + private postCommitCommandsProviders = new Set(); + + private _onDidChangePostCommitCommandsProviders = new EventEmitter(); + readonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event; + + private branchProtectionProviders = new Map>(); + + private _onDidChangeBranchProtectionProviders = new EventEmitter(); + readonly onDidChangeBranchProtectionProviders = this._onDidChangeBranchProtectionProviders.event; + + private pushErrorHandlers = new Set(); + + private _unsafeRepositoriesManager: UnsafeRepositoriesManager; + get unsafeRepositories(): string[] { + return this._unsafeRepositoriesManager.repositories; + } + + private _parentRepositoriesManager: ParentRepositoriesManager; + get parentRepositories(): string[] { + return this._parentRepositoriesManager.repositories; + } + + private _closedRepositoriesManager: ClosedRepositoriesManager; + get closedRepositories(): string[] { + return [...this._closedRepositoriesManager.repositories]; + } + + /** + * We maintain a map containing both the path and the canonical path of the + * workspace folders. We are doing this as `git.exe` expands the symbolic links + * while there are scenarios in which VS Code does not. + * + * Key - path of the workspace folder + * Value - canonical path of the workspace folder + */ + private _workspaceFolders = new Map(); + + private disposables: Disposable[] = []; + + constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) { + // Repositories managers + this._closedRepositoriesManager = new ClosedRepositoriesManager(workspaceState); + this._parentRepositoriesManager = new ParentRepositoriesManager(globalState); + this._unsafeRepositoriesManager = new UnsafeRepositoriesManager(); + + workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables); + window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables); + workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); + + const fsWatcher = workspace.createFileSystemWatcher('**'); + this.disposables.push(fsWatcher); + + const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete); + const onGitRepositoryChange = filterEvent(onWorkspaceChange, uri => /\/\.git/.test(uri.path)); + const onPossibleGitRepositoryChange = filterEvent(onGitRepositoryChange, uri => !this.getRepository(uri)); + onPossibleGitRepositoryChange(this.onPossibleGitRepositoryChange, this, this.disposables); + + this.setState('uninitialized'); + this.doInitialScan().finally(() => this.setState('initialized')); + } + + private async doInitialScan(): Promise { + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); + const parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt'); + + // Initial repository scan function + const initialScanFn = () => Promise.all([ + this.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }), + this.onDidChangeVisibleTextEditors(window.visibleTextEditors), + this.scanWorkspaceFolders() + ]); + + if (config.get('showProgress', true)) { + await window.withProgress({ location: ProgressLocation.SourceControl }, initialScanFn); + } else { + await initialScanFn(); + } + + if (this.parentRepositories.length !== 0 && + parentRepositoryConfig === 'prompt') { + // Parent repositories notification + this.showParentRepositoryNotification(); + } else if (this.unsafeRepositories.length !== 0) { + // Unsafe repositories notification + this.showUnsafeRepositoryNotification(); + } + + /* __GDPR__ + "git.repositoryInitialScan" : { + "owner": "lszomoru", + "autoRepositoryDetection": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Setting that controls the initial repository scan" }, + "repositoryCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Number of repositories opened during initial repository scan" } + } + */ + this.telemetryReporter.sendTelemetryEvent('git.repositoryInitialScan', { autoRepositoryDetection: String(autoRepositoryDetection) }, { repositoryCount: this.openRepositories.length }); + } + + /** + * Scans each workspace folder, looking for git repositories. By + * default it scans one level deep but that can be changed using + * the git.repositoryScanMaxDepth setting. + */ + private async scanWorkspaceFolders(): Promise { + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); + this.logger.trace(`[swsf] Scan workspace sub folders. autoRepositoryDetection=${autoRepositoryDetection}`); + + if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') { + return; + } + + await Promise.all((workspace.workspaceFolders || []).map(async folder => { + const root = folder.uri.fsPath; + this.logger.trace(`[swsf] Workspace folder: ${root}`); + + // Workspace folder children + const repositoryScanMaxDepth = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanMaxDepth', 1); + const repositoryScanIgnoredFolders = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanIgnoredFolders', []); + + const subfolders = new Set(await this.traverseWorkspaceFolder(root, repositoryScanMaxDepth, repositoryScanIgnoredFolders)); + + // Repository scan folders + const scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || []; + this.logger.trace(`[swsf] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`); + + for (const scanPath of scanPaths) { + if (scanPath === '.git') { + this.logger.trace('[swsf] \'.git\' not supported in \'git.scanRepositories\' setting.'); + continue; + } + + if (path.isAbsolute(scanPath)) { + const notSupportedMessage = l10n.t('Absolute paths not supported in "git.scanRepositories" setting.'); + this.logger.warn(notSupportedMessage); + console.warn(notSupportedMessage); + continue; + } + + subfolders.add(path.join(root, scanPath)); + } + + this.logger.trace(`[swsf] Workspace scan sub folders: [${[...subfolders].join(', ')}]`); + await Promise.all([...subfolders].map(f => this.openRepository(f))); + })); + } + + private async traverseWorkspaceFolder(workspaceFolder: string, maxDepth: number, repositoryScanIgnoredFolders: string[]): Promise { + const result: string[] = []; + const foldersToTravers = [{ path: workspaceFolder, depth: 0 }]; + + while (foldersToTravers.length > 0) { + const currentFolder = foldersToTravers.shift()!; + + if (currentFolder.depth < maxDepth || maxDepth === -1) { + const children = await fs.promises.readdir(currentFolder.path, { withFileTypes: true }); + const childrenFolders = children + .filter(dirent => + dirent.isDirectory() && dirent.name !== '.git' && + !repositoryScanIgnoredFolders.find(f => pathEquals(dirent.name, f))) + .map(dirent => path.join(currentFolder.path, dirent.name)); + + result.push(...childrenFolders); + foldersToTravers.push(...childrenFolders.map(folder => { + return { path: folder, depth: currentFolder.depth + 1 }; + })); + } + } + + return result; + } + + private onPossibleGitRepositoryChange(uri: Uri): void { + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); + + if (autoRepositoryDetection === false) { + return; + } + + this.eventuallyScanPossibleGitRepository(uri.fsPath.replace(/\.git.*$/, '')); + } + + private eventuallyScanPossibleGitRepository(path: string) { + this.possibleGitRepositoryPaths.add(path); + this.eventuallyScanPossibleGitRepositories(); + } + + @debounce(500) + private eventuallyScanPossibleGitRepositories(): void { + for (const path of this.possibleGitRepositoryPaths) { + this.openRepository(path); + } + + this.possibleGitRepositoryPaths.clear(); + } + + private async onDidChangeWorkspaceFolders({ added, removed }: WorkspaceFoldersChangeEvent): Promise { + const possibleRepositoryFolders = added + .filter(folder => !this.getOpenRepository(folder.uri)); + + const activeRepositoriesList = window.visibleTextEditors + .map(editor => this.getRepository(editor.document.uri)) + .filter(repository => !!repository) as Repository[]; + + const activeRepositories = new Set(activeRepositoriesList); + const openRepositoriesToDispose = removed + .map(folder => this.getOpenRepository(folder.uri)) + .filter(r => !!r) + .filter(r => !activeRepositories.has(r!.repository)) + .filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[]; + + openRepositoriesToDispose.forEach(r => r.dispose()); + this.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); + await Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath))); + } + + private onDidChangeConfiguration(): void { + const possibleRepositoryFolders = (workspace.workspaceFolders || []) + .filter(folder => workspace.getConfiguration('git', folder.uri).get('enabled') === true) + .filter(folder => !this.getOpenRepository(folder.uri)); + + const openRepositoriesToDispose = this.openRepositories + .map(repository => ({ repository, root: Uri.file(repository.repository.root) })) + .filter(({ root }) => workspace.getConfiguration('git', root).get('enabled') !== true) + .map(({ repository }) => repository); + + this.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); + possibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath)); + openRepositoriesToDispose.forEach(r => r.dispose()); + } + + private async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise { + if (!workspace.isTrusted) { + this.logger.trace('[svte] Workspace is not trusted.'); + return; + } + + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); + this.logger.trace(`[svte] Scan visible text editors. autoRepositoryDetection=${autoRepositoryDetection}`); + + if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') { + return; + } + + await Promise.all(editors.map(async editor => { + const uri = editor.document.uri; + + if (uri.scheme !== 'file') { + return; + } + + const repository = this.getRepository(uri); + + if (repository) { + this.logger.trace(`[svte] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`); + return; + } + + this.logger.trace(`[svte] Open repository for editor resource ${uri.fsPath}`); + await this.openRepository(path.dirname(uri.fsPath)); + })); + } + + @sequentialize + async openRepository(repoPath: string, openIfClosed = false): Promise { + this.logger.trace(`Opening repository: ${repoPath}`); + const existingRepository = await this.getRepositoryExact(repoPath); + if (existingRepository) { + this.logger.trace(`Repository for path ${repoPath} already exists: ${existingRepository.root})`); + return; + } + + const config = workspace.getConfiguration('git', Uri.file(repoPath)); + const enabled = config.get('enabled') === true; + + if (!enabled) { + this.logger.trace('Git is not enabled'); + return; + } + + if (!workspace.isTrusted) { + // Check if the folder is a bare repo: if it has a file named HEAD && `rev-parse --show -cdup` is empty + try { + fs.accessSync(path.join(repoPath, 'HEAD'), fs.constants.F_OK); + const result = await this.git.exec(repoPath, ['-C', repoPath, 'rev-parse', '--show-cdup']); + if (result.stderr.trim() === '' && result.stdout.trim() === '') { + this.logger.trace(`Bare repository: ${repoPath}`); + return; + } + } catch { + // If this throw, we should be good to open the repo (e.g. HEAD doesn't exist) + } + } + + try { + const { repositoryRoot, unsafeRepositoryMatch } = await this.getRepositoryRoot(repoPath); + this.logger.trace(`Repository root for path ${repoPath} is: ${repositoryRoot}`); + + const existingRepository = await this.getRepositoryExact(repositoryRoot); + if (existingRepository) { + this.logger.trace(`Repository for path ${repositoryRoot} already exists: ${existingRepository.root}`); + return; + } + + if (this.shouldRepositoryBeIgnored(repositoryRoot)) { + this.logger.trace(`Repository for path ${repositoryRoot} is ignored`); + return; + } + + // Handle git repositories that are in parent folders + const parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt'); + if (parentRepositoryConfig !== 'always' && this.globalState.get(`parentRepository:${repositoryRoot}`) !== true) { + const isRepositoryOutsideWorkspace = await this.isRepositoryOutsideWorkspace(repositoryRoot); + if (isRepositoryOutsideWorkspace) { + this.logger.trace(`Repository in parent folder: ${repositoryRoot}`); + + if (!this._parentRepositoriesManager.hasRepository(repositoryRoot)) { + // Show a notification if the parent repository is opened after the initial scan + if (this.state === 'initialized' && parentRepositoryConfig === 'prompt') { + this.showParentRepositoryNotification(); + } + + this._parentRepositoriesManager.addRepository(repositoryRoot); + } + + return; + } + } + + // Handle unsafe repositories + if (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) { + this.logger.trace(`Unsafe repository: ${repositoryRoot}`); + + // Show a notification if the unsafe repository is opened after the initial scan + if (this._state === 'initialized' && !this._unsafeRepositoriesManager.hasRepository(repositoryRoot)) { + this.showUnsafeRepositoryNotification(); + } + + this._unsafeRepositoriesManager.addRepository(repositoryRoot, unsafeRepositoryMatch[2]); + + return; + } + + // Handle repositories that were closed by the user + if (!openIfClosed && this._closedRepositoriesManager.isRepositoryClosed(repositoryRoot)) { + this.logger.trace(`Repository for path ${repositoryRoot} is closed`); + return; + } + + // Open repository + const dotGit = await this.git.getRepositoryDotGit(repositoryRoot); + const repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this, this.globalState, this.logger, this.telemetryReporter); + + this.open(repository); + this._closedRepositoriesManager.deleteRepository(repository.root); + + // Do not await this, we want SCM + // to know about the repo asap + repository.status(); + } catch (err) { + // noop + this.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${err}`); + } + } + + async openParentRepository(repoPath: string): Promise { + await this.openRepository(repoPath); + this._parentRepositoriesManager.openRepository(repoPath); + } + + private async getRepositoryRoot(repoPath: string): Promise<{ repositoryRoot: string; unsafeRepositoryMatch: RegExpMatchArray | null }> { + try { + const rawRoot = await this.git.getRepositoryRoot(repoPath); + + // This can happen whenever `path` has the wrong case sensitivity in case + // insensitive file systems https://github.com/microsoft/vscode/issues/33498 + return { repositoryRoot: Uri.file(rawRoot).fsPath, unsafeRepositoryMatch: null }; + } catch (err) { + // Handle unsafe repository + const unsafeRepositoryMatch = /^fatal: detected dubious ownership in repository at \'([^']+)\'[\s\S]*git config --global --add safe\.directory '?([^'\n]+)'?$/m.exec(err.stderr); + if (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) { + return { repositoryRoot: path.normalize(unsafeRepositoryMatch[1]), unsafeRepositoryMatch }; + } + + throw err; + } + } + + private shouldRepositoryBeIgnored(repositoryRoot: string): boolean { + const config = workspace.getConfiguration('git'); + const ignoredRepos = config.get('ignoredRepositories') || []; + + for (const ignoredRepo of ignoredRepos) { + if (path.isAbsolute(ignoredRepo)) { + if (pathEquals(ignoredRepo, repositoryRoot)) { + return true; + } + } else { + for (const folder of workspace.workspaceFolders || []) { + if (pathEquals(path.join(folder.uri.fsPath, ignoredRepo), repositoryRoot)) { + return true; + } + } + } + } + + return false; + } + + private open(repository: Repository): void { + this.logger.info(`Open repository: ${repository.root}`); + + const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed); + const disappearListener = onDidDisappearRepository(() => dispose()); + const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri })); + const originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri })); + + const shouldDetectSubmodules = workspace + .getConfiguration('git', Uri.file(repository.root)) + .get('detectSubmodules') as boolean; + + const submodulesLimit = workspace + .getConfiguration('git', Uri.file(repository.root)) + .get('detectSubmodulesLimit') as number; + + const checkForSubmodules = () => { + if (!shouldDetectSubmodules) { + this.logger.trace('Automatic detection of git submodules is not enabled.'); + return; + } + + if (repository.submodules.length > submodulesLimit) { + window.showWarningMessage(l10n.t('The "{0}" repository has {1} submodules which won\'t be opened automatically. You can still open each one individually by opening a file within.', path.basename(repository.root), repository.submodules.length)); + statusListener.dispose(); + } + + repository.submodules + .slice(0, submodulesLimit) + .map(r => path.join(repository.root, r.path)) + .forEach(p => { + this.logger.trace(`Opening submodule: '${p}'`); + this.eventuallyScanPossibleGitRepository(p); + }); + }; + + const updateMergeChanges = () => { + // set mergeChanges context + const mergeChanges: Uri[] = []; + for (const { repository } of this.openRepositories.values()) { + for (const state of repository.mergeGroup.resourceStates) { + mergeChanges.push(state.resourceUri); + } + } + commands.executeCommand('setContext', 'git.mergeChanges', mergeChanges); + }; + + const statusListener = repository.onDidRunGitStatus(() => { + checkForSubmodules(); + updateMergeChanges(); + }); + checkForSubmodules(); + + const updateOperationInProgressContext = () => { + let operationInProgress = false; + for (const { repository } of this.openRepositories.values()) { + if (repository.operations.shouldDisableCommands()) { + operationInProgress = true; + } + } + + commands.executeCommand('setContext', 'operationInProgress', operationInProgress); + }; + + const operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event); + const operationListener = operationEvent(() => updateOperationInProgressContext()); + updateOperationInProgressContext(); + + const dispose = () => { + disappearListener.dispose(); + changeListener.dispose(); + originalResourceChangeListener.dispose(); + statusListener.dispose(); + operationListener.dispose(); + repository.dispose(); + + this.openRepositories = this.openRepositories.filter(e => e !== openRepository); + this._onDidCloseRepository.fire(repository); + }; + + const openRepository = { repository, dispose }; + this.openRepositories.push(openRepository); + updateMergeChanges(); + this._onDidOpenRepository.fire(repository); + } + + close(repository: Repository): void { + const openRepository = this.getOpenRepository(repository); + + if (!openRepository) { + return; + } + + this.logger.info(`Close repository: ${repository.root}`); + this._closedRepositoriesManager.addRepository(openRepository.repository.root); + + openRepository.dispose(); + } + + async pickRepository(): Promise { + if (this.openRepositories.length === 0) { + throw new Error(l10n.t('There are no available repositories')); + } + + const picks = this.openRepositories.map((e, index) => new RepositoryPick(e.repository, index)); + const active = window.activeTextEditor; + const repository = active && this.getRepository(active.document.fileName); + const index = picks.findIndex(pick => pick.repository === repository); + + // Move repository pick containing the active text editor to appear first + if (index > -1) { + picks.unshift(...picks.splice(index, 1)); + } + + const placeHolder = l10n.t('Choose a repository'); + const pick = await window.showQuickPick(picks, { placeHolder }); + + return pick && pick.repository; + } + + getRepository(sourceControl: SourceControl): Repository | undefined; + getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined; + getRepository(path: string): Repository | undefined; + getRepository(resource: Uri): Repository | undefined; + getRepository(hint: any): Repository | undefined { + const liveRepository = this.getOpenRepository(hint); + return liveRepository && liveRepository.repository; + } + + private async getRepositoryExact(repoPath: string): Promise { + const repoPathCanonical = await fs.promises.realpath(repoPath, { encoding: 'utf8' }); + + for (const openRepository of this.openRepositories) { + const rootPathCanonical = await fs.promises.realpath(openRepository.repository.root, { encoding: 'utf8' }); + if (pathEquals(rootPathCanonical, repoPathCanonical)) { + return openRepository.repository; + } + } + + return undefined; + } + + private getOpenRepository(repository: Repository): OpenRepository | undefined; + private getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined; + private getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined; + private getOpenRepository(path: string): OpenRepository | undefined; + private getOpenRepository(resource: Uri): OpenRepository | undefined; + private getOpenRepository(hint: any): OpenRepository | undefined { + if (!hint) { + return undefined; + } + + if (hint instanceof Repository) { + return this.openRepositories.filter(r => r.repository === hint)[0]; + } + + if (hint instanceof ApiRepository) { + return this.openRepositories.filter(r => r.repository === hint.repository)[0]; + } + + if (typeof hint === 'string') { + hint = Uri.file(hint); + } + + if (hint instanceof Uri) { + let resourcePath: string; + + if (hint.scheme === 'git') { + resourcePath = fromGitUri(hint).path; + } else { + resourcePath = hint.fsPath; + } + + outer: + for (const liveRepository of this.openRepositories.sort((a, b) => b.repository.root.length - a.repository.root.length)) { + if (!isDescendant(liveRepository.repository.root, resourcePath)) { + continue; + } + + for (const submodule of liveRepository.repository.submodules) { + const submoduleRoot = path.join(liveRepository.repository.root, submodule.path); + + if (isDescendant(submoduleRoot, resourcePath)) { + continue outer; + } + } + + return liveRepository; + } + + return undefined; + } + + for (const liveRepository of this.openRepositories) { + const repository = liveRepository.repository; + + if (hint === repository.sourceControl) { + return liveRepository; + } + + if (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup || hint === repository.untrackedGroup) { + return liveRepository; + } + } + + return undefined; + } + + getRepositoryForSubmodule(submoduleUri: Uri): Repository | undefined { + for (const repository of this.repositories) { + for (const submodule of repository.submodules) { + const submodulePath = path.join(repository.root, submodule.path); + + if (submodulePath === submoduleUri.fsPath) { + return repository; + } + } + } + + return undefined; + } +} diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json new file mode 100644 index 00000000000..6bfa1f36e92 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json @@ -0,0 +1,60 @@ +{ + "original": { + "content": "/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, WorkspaceFolder } from 'vscode';\nimport TelemetryReporter from '@vscode/extension-telemetry';\nimport { Repository, RepositoryState } from './repository';\nimport { memoize, sequentialize, debounce } from './decorators';\nimport { dispose, anyEvent, filterEvent, isDescendant, pathEquals, toDisposable, eventToPromise } from './util';\nimport { Git } from './git';\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport { fromGitUri } from './uri';\nimport { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider } from './api/git';\nimport { Askpass } from './askpass';\nimport { IPushErrorHandlerRegistry } from './pushError';\nimport { ApiRepository } from './api/api1';\nimport { IRemoteSourcePublisherRegistry } from './remotePublisher';\nimport { IPostCommitCommandsProviderRegistry } from './postCommitCommands';\nimport { IBranchProtectionProviderRegistry } from './branchProtection';\n\nclass ClosedRepositoriesManager {\n\n\tprivate _repositories: Set;\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.values()];\n\t}\n\n\tconstructor(private readonly workspaceState: Memento) {\n\t\tthis._repositories = new Set(workspaceState.get('closedRepositories', []));\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string): void {\n\t\tthis._repositories.add(repository);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tisRepositoryClosed(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tthis.workspaceState.update('closedRepositories', [...this._repositories.values()]);\n\t\tcommands.executeCommand('setContext', 'git.closedRepositoryCount', this._repositories.size);\n\t}\n}\n\nclass ParentRepositoriesManager {\n\n\t/**\n\t * Key - normalized path used in user interface\n\t * Value - value indicating whether the repository should be opened\n\t */\n\tprivate _repositories = new Set;\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.values()];\n\t}\n\n\tconstructor(private readonly globalState: Memento) {\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string): void {\n\t\tthis._repositories.add(repository);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\thasRepository(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\topenRepository(repository: string): void {\n\t\tthis.globalState.update(`parentRepository:${repository}`, true);\n\t\tthis.deleteRepository(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tcommands.executeCommand('setContext', 'git.parentRepositoryCount', this._repositories.size);\n\t}\n}\n\nclass UnsafeRepositoriesManager {\n\n\t/**\n\t * Key - normalized path used in user interface\n\t * Value - path extracted from the output of the `git status` command\n\t * used when calling `git config --global --add safe.directory`\n\t */\n\tprivate _repositories = new Map();\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.keys()];\n\t}\n\n\tconstructor() {\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string, path: string): void {\n\t\tthis._repositories.set(repository, path);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tgetRepositoryPath(repository: string): string | undefined {\n\t\treturn this._repositories.get(repository);\n\t}\n\n\thasRepository(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tcommands.executeCommand('setContext', 'git.unsafeRepositoryCount', this._repositories.size);\n\t}\n}\n\nexport class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry {\n\n\tprivate _onDidOpenRepository = new EventEmitter();\n\treadonly onDidOpenRepository: Event = this._onDidOpenRepository.event;\n\n\tprivate _onDidCloseRepository = new EventEmitter();\n\treadonly onDidCloseRepository: Event = this._onDidCloseRepository.event;\n\n\tprivate _onDidChangeRepository = new EventEmitter();\n\treadonly onDidChangeRepository: Event = this._onDidChangeRepository.event;\n\n\tprivate _onDidChangeOriginalResource = new EventEmitter();\n\treadonly onDidChangeOriginalResource: Event = this._onDidChangeOriginalResource.event;\n\n\tprivate openRepositories: OpenRepository[] = [];\n\tget repositories(): Repository[] { return this.openRepositories.map(r => r.repository); }\n\n\tprivate possibleGitRepositoryPaths = new Set();\n\n\tprivate _onDidChangeState = new EventEmitter();\n\treadonly onDidChangeState = this._onDidChangeState.event;\n\n\tprivate _onDidPublish = new EventEmitter();\n\treadonly onDidPublish = this._onDidPublish.event;\n\n\tfirePublishEvent(repository: Repository, branch?: string) {\n\t\tthis._onDidPublish.fire({ repository: new ApiRepository(repository), branch: branch });\n\t}\n\n\tprivate _state: State = 'uninitialized';\n\tget state(): State { return this._state; }\n\n\tsetState(state: State): void {\n\t\tthis._state = state;\n\t\tthis._onDidChangeState.fire(state);\n\t\tcommands.executeCommand('setContext', 'git.state', state);\n\t}\n\n\t@memoize\n\tget isInitialized(): Promise {\n\t\tif (this._state === 'initialized') {\n\t\t\treturn Promise.resolve();\n\t\t}\n\n\t\treturn eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized')) as Promise;\n\t}\n\n\tprivate remoteSourcePublishers = new Set();\n\n\tprivate _onDidAddRemoteSourcePublisher = new EventEmitter();\n\treadonly onDidAddRemoteSourcePublisher = this._onDidAddRemoteSourcePublisher.event;\n\n\tprivate _onDidRemoveRemoteSourcePublisher = new EventEmitter();\n\treadonly onDidRemoveRemoteSourcePublisher = this._onDidRemoveRemoteSourcePublisher.event;\n\n\tprivate postCommitCommandsProviders = new Set();\n\n\tprivate _onDidChangePostCommitCommandsProviders = new EventEmitter();\n\treadonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event;\n\n\tprivate branchProtectionProviders = new Map>();\n\n\tprivate _onDidChangeBranchProtectionProviders = new EventEmitter();\n\treadonly onDidChangeBranchProtectionProviders = this._onDidChangeBranchProtectionProviders.event;\n\n\tprivate pushErrorHandlers = new Set();\n\n\tprivate _unsafeRepositoriesManager: UnsafeRepositoriesManager;\n\tget unsafeRepositories(): string[] {\n\t\treturn this._unsafeRepositoriesManager.repositories;\n\t}\n\n\tprivate _parentRepositoriesManager: ParentRepositoriesManager;\n\tget parentRepositories(): string[] {\n\t\treturn this._parentRepositoriesManager.repositories;\n\t}\n\n\tprivate _closedRepositoriesManager: ClosedRepositoriesManager;\n\tget closedRepositories(): string[] {\n\t\treturn [...this._closedRepositoriesManager.repositories];\n\t}\n\n\t/**\n\t * We maintain a map containing both the path and the canonical path of the\n\t * workspace folders. We are doing this as `git.exe` expands the symbolic links\n\t * while there are scenarios in which VS Code does not.\n\t *\n\t * Key - path of the workspace folder\n\t * Value - canonical path of the workspace folder\n\t */\n\tprivate _workspaceFolders = new Map();\n\n\tprivate disposables: Disposable[] = [];\n\n\tconstructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) {\n\t\t// Repositories managers\n\t\tthis._closedRepositoriesManager = new ClosedRepositoriesManager(workspaceState);\n\t\tthis._parentRepositoriesManager = new ParentRepositoriesManager(globalState);\n\t\tthis._unsafeRepositoriesManager = new UnsafeRepositoriesManager();\n\n\t\tworkspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);\n\t\twindow.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);\n\t\tworkspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables);\n\n\t\tconst fsWatcher = workspace.createFileSystemWatcher('**');\n\t\tthis.disposables.push(fsWatcher);\n\n\t\tconst onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);\n\t\tconst onGitRepositoryChange = filterEvent(onWorkspaceChange, uri => /\\/\\.git/.test(uri.path));\n\t\tconst onPossibleGitRepositoryChange = filterEvent(onGitRepositoryChange, uri => !this.getRepository(uri));\n\t\tonPossibleGitRepositoryChange(this.onPossibleGitRepositoryChange, this, this.disposables);\n\n\t\tthis.setState('uninitialized');\n\t\tthis.doInitialScan().finally(() => this.setState('initialized'));\n\t}\n\n\tprivate async doInitialScan(): Promise {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tconst parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt');\n\n\t\t// Initial repository scan function\n\t\tconst initialScanFn = () => Promise.all([\n\t\t\tthis.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }),\n\t\t\tthis.onDidChangeVisibleTextEditors(window.visibleTextEditors),\n\t\t\tthis.scanWorkspaceFolders()\n\t\t]);\n\n\t\tif (config.get('showProgress', true)) {\n\t\t\tawait window.withProgress({ location: ProgressLocation.SourceControl }, initialScanFn);\n\t\t} else {\n\t\t\tawait initialScanFn();\n\t\t}\n\n\t\tif (this.parentRepositories.length !== 0 &&\n\t\t\tparentRepositoryConfig === 'prompt') {\n\t\t\t// Parent repositories notification\n\t\t\tthis.showParentRepositoryNotification();\n\t\t} else if (this.unsafeRepositories.length !== 0) {\n\t\t\t// Unsafe repositories notification\n\t\t\tthis.showUnsafeRepositoryNotification();\n\t\t}\n\n\t\t/* __GDPR__\n\t\t\t\"git.repositoryInitialScan\" : {\n\t\t\t\t\"owner\": \"lszomoru\",\n\t\t\t\t\"autoRepositoryDetection\": { \"classification\": \"SystemMetaData\", \"purpose\": \"FeatureInsight\", \"comment\": \"Setting that controls the initial repository scan\" },\n\t\t\t\t\"repositoryCount\": { \"classification\": \"SystemMetaData\", \"purpose\": \"FeatureInsight\", \"isMeasurement\": true, \"comment\": \"Number of repositories opened during initial repository scan\" }\n\t\t\t}\n\t\t*/\n\t\tthis.telemetryReporter.sendTelemetryEvent('git.repositoryInitialScan', { autoRepositoryDetection: String(autoRepositoryDetection) }, { repositoryCount: this.openRepositories.length });\n\t}\n\n\t/**\n\t * Scans each workspace folder, looking for git repositories. By\n\t * default it scans one level deep but that can be changed using\n\t * the git.repositoryScanMaxDepth setting.\n\t */\n\tprivate async scanWorkspaceFolders(): Promise {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tthis.logger.trace(`[swsf] Scan workspace sub folders. autoRepositoryDetection=${autoRepositoryDetection}`);\n\n\t\tif (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') {\n\t\t\treturn;\n\t\t}\n\n\t\tawait Promise.all((workspace.workspaceFolders || []).map(async folder => {\n\t\t\tconst root = folder.uri.fsPath;\n\t\t\tthis.logger.trace(`[swsf] Workspace folder: ${root}`);\n\n\t\t\t// Workspace folder children\n\t\t\tconst repositoryScanMaxDepth = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanMaxDepth', 1);\n\t\t\tconst repositoryScanIgnoredFolders = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanIgnoredFolders', []);\n\n\t\t\tconst subfolders = new Set(await this.traverseWorkspaceFolder(root, repositoryScanMaxDepth, repositoryScanIgnoredFolders));\n\n\t\t\t// Repository scan folders\n\t\t\tconst scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || [];\n\t\t\tthis.logger.trace(`[swsf] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`);\n\n\t\t\tfor (const scanPath of scanPaths) {\n\t\t\t\tif (scanPath === '.git') {\n\t\t\t\t\tthis.logger.trace('[swsf] \\'.git\\' not supported in \\'git.scanRepositories\\' setting.');\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (path.isAbsolute(scanPath)) {\n\t\t\t\t\tconst notSupportedMessage = l10n.t('Absolute paths not supported in \"git.scanRepositories\" setting.');\n\t\t\t\t\tthis.logger.warn(notSupportedMessage);\n\t\t\t\t\tconsole.warn(notSupportedMessage);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tsubfolders.add(path.join(root, scanPath));\n\t\t\t}\n\n\t\t\tthis.logger.trace(`[swsf] Workspace scan sub folders: [${[...subfolders].join(', ')}]`);\n\t\t\tawait Promise.all([...subfolders].map(f => this.openRepository(f)));\n\t\t}));\n\t}\n\n\tprivate async traverseWorkspaceFolder(workspaceFolder: string, maxDepth: number, repositoryScanIgnoredFolders: string[]): Promise {\n\t\tconst result: string[] = [];\n\t\tconst foldersToTravers = [{ path: workspaceFolder, depth: 0 }];\n\n\t\twhile (foldersToTravers.length > 0) {\n\t\t\tconst currentFolder = foldersToTravers.shift()!;\n\n\t\t\tif (currentFolder.depth < maxDepth || maxDepth === -1) {\n\t\t\t\tconst children = await fs.promises.readdir(currentFolder.path, { withFileTypes: true });\n\t\t\t\tconst childrenFolders = children\n\t\t\t\t\t.filter(dirent =>\n\t\t\t\t\t\tdirent.isDirectory() && dirent.name !== '.git' &&\n\t\t\t\t\t\t!repositoryScanIgnoredFolders.find(f => pathEquals(dirent.name, f)))\n\t\t\t\t\t.map(dirent => path.join(currentFolder.path, dirent.name));\n\n\t\t\t\tresult.push(...childrenFolders);\n\t\t\t\tfoldersToTravers.push(...childrenFolders.map(folder => {\n\t\t\t\t\treturn { path: folder, depth: currentFolder.depth + 1 };\n\t\t\t\t}));\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate onPossibleGitRepositoryChange(uri: Uri): void {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\n\t\tif (autoRepositoryDetection === false) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.eventuallyScanPossibleGitRepository(uri.fsPath.replace(/\\.git.*$/, ''));\n\t}\n\n\tprivate eventuallyScanPossibleGitRepository(path: string) {\n\t\tthis.possibleGitRepositoryPaths.add(path);\n\t\tthis.eventuallyScanPossibleGitRepositories();\n\t}\n\n\t@debounce(500)\n\tprivate eventuallyScanPossibleGitRepositories(): void {\n\t\tfor (const path of this.possibleGitRepositoryPaths) {\n\t\t\tthis.openRepository(path);\n\t\t}\n\n\t\tthis.possibleGitRepositoryPaths.clear();\n\t}\n\n\tprivate async onDidChangeWorkspaceFolders({ added, removed }: WorkspaceFoldersChangeEvent): Promise {\n\t\tconst possibleRepositoryFolders = added\n\t\t\t.filter(folder => !this.getOpenRepository(folder.uri));\n\n\t\tconst activeRepositoriesList = window.visibleTextEditors\n\t\t\t.map(editor => this.getRepository(editor.document.uri))\n\t\t\t.filter(repository => !!repository) as Repository[];\n\n\t\tconst activeRepositories = new Set(activeRepositoriesList);\n\t\tconst openRepositoriesToDispose = removed\n\t\t\t.map(folder => this.getOpenRepository(folder.uri))\n\t\t\t.filter(r => !!r)\n\t\t\t.filter(r => !activeRepositories.has(r!.repository))\n\t\t\t.filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[];\n\n\t\topenRepositoriesToDispose.forEach(r => r.dispose());\n\t\tthis.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`);\n\t\tawait Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath)));\n\t}\n\n\tprivate onDidChangeConfiguration(): void {\n\t\tconst possibleRepositoryFolders = (workspace.workspaceFolders || [])\n\t\t\t.filter(folder => workspace.getConfiguration('git', folder.uri).get('enabled') === true)\n\t\t\t.filter(folder => !this.getOpenRepository(folder.uri));\n\n\t\tconst openRepositoriesToDispose = this.openRepositories\n\t\t\t.map(repository => ({ repository, root: Uri.file(repository.repository.root) }))\n\t\t\t.filter(({ root }) => workspace.getConfiguration('git', root).get('enabled') !== true)\n\t\t\t.map(({ repository }) => repository);\n\n\t\tthis.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`);\n\t\tpossibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath));\n\t\topenRepositoriesToDispose.forEach(r => r.dispose());\n\t}\n\n\tprivate async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise {\n\t\tif (!workspace.isTrusted) {\n\t\t\tthis.logger.trace('[svte] Workspace is not trusted.');\n\t\t\treturn;\n\t\t}\n\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tthis.logger.trace(`[svte] Scan visible text editors. autoRepositoryDetection=${autoRepositoryDetection}`);\n\n\t\tif (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') {\n\t\t\treturn;\n\t\t}\n\n\t\tawait Promise.all(editors.map(async editor => {\n\t\t\tconst uri = editor.document.uri;\n\n\t\t\tif (uri.scheme !== 'file') {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst repository = this.getRepository(uri);\n\n\t\t\tif (repository) {\n\t\t\t\tthis.logger.trace(`[svte] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.logger.trace(`[svte] Open repository for editor resource ${uri.fsPath}`);\n\t\t\tawait this.openRepository(path.dirname(uri.fsPath));\n\t\t}));\n\t}\n\n\t@sequentialize\n\tasync openRepository(repoPath: string, openIfClosed = false): Promise {\n\t\tthis.logger.trace(`Opening repository: ${repoPath}`);\n\t\tconst existingRepository = await this.getRepositoryExact(repoPath);\n\t\tif (existingRepository) {\n\t\t\tthis.logger.trace(`Repository for path ${repoPath} already exists: ${existingRepository.root})`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst config = workspace.getConfiguration('git', Uri.file(repoPath));\n\t\tconst enabled = config.get('enabled') === true;\n\n\t\tif (!enabled) {\n\t\t\tthis.logger.trace('Git is not enabled');\n\t\t\treturn;\n\t\t}\n\n\t\tif (!workspace.isTrusted) {\n\t\t\t// Check if the folder is a bare repo: if it has a file named HEAD && `rev-parse --show -cdup` is empty\n\t\t\ttry {\n\t\t\t\tfs.accessSync(path.join(repoPath, 'HEAD'), fs.constants.F_OK);\n\t\t\t\tconst result = await this.git.exec(repoPath, ['-C', repoPath, 'rev-parse', '--show-cdup']);\n\t\t\t\tif (result.stderr.trim() === '' && result.stdout.trim() === '') {\n\t\t\t\t\tthis.logger.trace(`Bare repository: ${repoPath}`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// If this throw, we should be good to open the repo (e.g. HEAD doesn't exist)\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tconst { repositoryRoot, unsafeRepositoryMatch } = await this.getRepositoryRoot(repoPath);\n\t\t\tthis.logger.trace(`Repository root for path ${repoPath} is: ${repositoryRoot}`);\n\n\t\t\tconst existingRepository = await this.getRepositoryExact(repositoryRoot);\n\t\t\tif (existingRepository) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} already exists: ${existingRepository.root}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.shouldRepositoryBeIgnored(repositoryRoot)) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} is ignored`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle git repositories that are in parent folders\n\t\t\tconst parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt');\n\t\t\tif (parentRepositoryConfig !== 'always' && this.globalState.get(`parentRepository:${repositoryRoot}`) !== true) {\n\t\t\t\tconst isRepositoryOutsideWorkspace = await this.isRepositoryOutsideWorkspace(repositoryRoot);\n\t\t\t\tif (isRepositoryOutsideWorkspace) {\n\t\t\t\t\tthis.logger.trace(`Repository in parent folder: ${repositoryRoot}`);\n\n\t\t\t\t\tif (!this._parentRepositoriesManager.hasRepository(repositoryRoot)) {\n\t\t\t\t\t\t// Show a notification if the parent repository is opened after the initial scan\n\t\t\t\t\t\tif (this.state === 'initialized' && parentRepositoryConfig === 'prompt') {\n\t\t\t\t\t\t\tthis.showParentRepositoryNotification();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis._parentRepositoriesManager.addRepository(repositoryRoot);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle unsafe repositories\n\t\t\tif (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) {\n\t\t\t\tthis.logger.trace(`Unsafe repository: ${repositoryRoot}`);\n\n\t\t\t\t// Show a notification if the unsafe repository is opened after the initial scan\n\t\t\t\tif (this._state === 'initialized' && !this._unsafeRepositoriesManager.hasRepository(repositoryRoot)) {\n\t\t\t\t\tthis.showUnsafeRepositoryNotification();\n\t\t\t\t}\n\n\t\t\t\tthis._unsafeRepositoriesManager.addRepository(repositoryRoot, unsafeRepositoryMatch[2]);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle repositories that were closed by the user\n\t\t\tif (!openIfClosed && this._closedRepositoriesManager.isRepositoryClosed(repositoryRoot)) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} is closed`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Open repository\n\t\t\tconst dotGit = await this.git.getRepositoryDotGit(repositoryRoot);\n\t\t\tconst repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this, this.globalState, this.logger, this.telemetryReporter);\n\n\t\t\tthis.open(repository);\n\t\t\tthis._closedRepositoriesManager.deleteRepository(repository.root);\n\n\t\t\t// Do not await this, we want SCM\n\t\t\t// to know about the repo asap\n\t\t\trepository.status();\n\t\t} catch (err) {\n\t\t\t// noop\n\t\t\tthis.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${err}`);\n\t\t}\n\t}\n\n\tasync openParentRepository(repoPath: string): Promise {\n\t\tawait this.openRepository(repoPath);\n\t\tthis._parentRepositoriesManager.openRepository(repoPath);\n\t}\n\n\tprivate async getRepositoryRoot(repoPath: string): Promise<{ repositoryRoot: string; unsafeRepositoryMatch: RegExpMatchArray | null }> {\n\t\ttry {\n\t\t\tconst rawRoot = await this.git.getRepositoryRoot(repoPath);\n\n\t\t\t// This can happen whenever `path` has the wrong case sensitivity in case\n\t\t\t// insensitive file systems https://github.com/microsoft/vscode/issues/33498\n\t\t\treturn { repositoryRoot: Uri.file(rawRoot).fsPath, unsafeRepositoryMatch: null };\n\t\t} catch (err) {\n\t\t\t// Handle unsafe repository\n\t\t\tconst unsafeRepositoryMatch = /^fatal: detected dubious ownership in repository at \\'([^']+)\\'[\\s\\S]*git config --global --add safe\\.directory '?([^'\\n]+)'?$/m.exec(err.stderr);\n\t\t\tif (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) {\n\t\t\t\treturn { repositoryRoot: path.normalize(unsafeRepositoryMatch[1]), unsafeRepositoryMatch };\n\t\t\t}\n\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tprivate shouldRepositoryBeIgnored(repositoryRoot: string): boolean {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst ignoredRepos = config.get('ignoredRepositories') || [];\n\n\t\tfor (const ignoredRepo of ignoredRepos) {\n\t\t\tif (path.isAbsolute(ignoredRepo)) {\n\t\t\t\tif (pathEquals(ignoredRepo, repositoryRoot)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (const folder of workspace.workspaceFolders || []) {\n\t\t\t\t\tif (pathEquals(path.join(folder.uri.fsPath, ignoredRepo), repositoryRoot)) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate open(repository: Repository): void {\n\t\tthis.logger.info(`Open repository: ${repository.root}`);\n\n\t\tconst onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed);\n\t\tconst disappearListener = onDidDisappearRepository(() => dispose());\n\t\tconst changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri }));\n\t\tconst originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri }));\n\n\t\tconst shouldDetectSubmodules = workspace\n\t\t\t.getConfiguration('git', Uri.file(repository.root))\n\t\t\t.get('detectSubmodules') as boolean;\n\n\t\tconst submodulesLimit = workspace\n\t\t\t.getConfiguration('git', Uri.file(repository.root))\n\t\t\t.get('detectSubmodulesLimit') as number;\n\n\t\tconst checkForSubmodules = () => {\n\t\t\tif (!shouldDetectSubmodules) {\n\t\t\t\tthis.logger.trace('Automatic detection of git submodules is not enabled.');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (repository.submodules.length > submodulesLimit) {\n\t\t\t\twindow.showWarningMessage(l10n.t('The \"{0}\" repository has {1} submodules which won\\'t be opened automatically. You can still open each one individually by opening a file within.', path.basename(repository.root), repository.submodules.length));\n\t\t\t\tstatusListener.dispose();\n\t\t\t}\n\n\t\t\trepository.submodules\n\t\t\t\t.slice(0, submodulesLimit)\n\t\t\t\t.map(r => path.join(repository.root, r.path))\n\t\t\t\t.forEach(p => {\n\t\t\t\t\tthis.logger.trace(`Opening submodule: '${p}'`);\n\t\t\t\t\tthis.eventuallyScanPossibleGitRepository(p);\n\t\t\t\t});\n\t\t};\n\n\t\tconst updateMergeChanges = () => {\n\t\t\t// set mergeChanges context\n\t\t\tconst mergeChanges: Uri[] = [];\n\t\t\tfor (const { repository } of this.openRepositories.values()) {\n\t\t\t\tfor (const state of repository.mergeGroup.resourceStates) {\n\t\t\t\t\tmergeChanges.push(state.resourceUri);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcommands.executeCommand('setContext', 'git.mergeChanges', mergeChanges);\n\t\t};\n\n\t\tconst statusListener = repository.onDidRunGitStatus(() => {\n\t\t\tcheckForSubmodules();\n\t\t\tupdateMergeChanges();\n\t\t});\n\t\tcheckForSubmodules();\n\n\t\tconst updateOperationInProgressContext = () => {\n\t\t\tlet operationInProgress = false;\n\t\t\tfor (const { repository } of this.openRepositories.values()) {\n\t\t\t\tif (repository.operations.shouldDisableCommands()) {\n\t\t\t\t\toperationInProgress = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcommands.executeCommand('setContext', 'operationInProgress', operationInProgress);\n\t\t};\n\n\t\tconst operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event);\n\t\tconst operationListener = operationEvent(() => updateOperationInProgressContext());\n\t\tupdateOperationInProgressContext();\n\n\t\tconst dispose = () => {\n\t\t\tdisappearListener.dispose();\n\t\t\tchangeListener.dispose();\n\t\t\toriginalResourceChangeListener.dispose();\n\t\t\tstatusListener.dispose();\n\t\t\toperationListener.dispose();\n\t\t\trepository.dispose();\n\n\t\t\tthis.openRepositories = this.openRepositories.filter(e => e !== openRepository);\n\t\t\tthis._onDidCloseRepository.fire(repository);\n\t\t};\n\n\t\tconst openRepository = { repository, dispose };\n\t\tthis.openRepositories.push(openRepository);\n\t\tupdateMergeChanges();\n\t\tthis._onDidOpenRepository.fire(repository);\n\t}\n\n\tclose(repository: Repository): void {\n\t\tconst openRepository = this.getOpenRepository(repository);\n\n\t\tif (!openRepository) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.logger.info(`Close repository: ${repository.root}`);\n\t\tthis._closedRepositoriesManager.addRepository(openRepository.repository.root);\n\n\t\topenRepository.dispose();\n\t}\n\n\tasync pickRepository(): Promise {\n\t\tif (this.openRepositories.length === 0) {\n\t\t\tthrow new Error(l10n.t('There are no available repositories'));\n\t\t}\n\n\t\tconst picks = this.openRepositories.map((e, index) => new RepositoryPick(e.repository, index));\n\t\tconst active = window.activeTextEditor;\n\t\tconst repository = active && this.getRepository(active.document.fileName);\n\t\tconst index = picks.findIndex(pick => pick.repository === repository);\n\n\t\t// Move repository pick containing the active text editor to appear first\n\t\tif (index > -1) {\n\t\t\tpicks.unshift(...picks.splice(index, 1));\n\t\t}\n\n\t\tconst placeHolder = l10n.t('Choose a repository');\n\t\tconst pick = await window.showQuickPick(picks, { placeHolder });\n\n\t\treturn pick && pick.repository;\n\t}\n\n\tgetRepository(sourceControl: SourceControl): Repository | undefined;\n\tgetRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined;\n\tgetRepository(path: string): Repository | undefined;\n\tgetRepository(resource: Uri): Repository | undefined;\n\tgetRepository(hint: any): Repository | undefined {\n\t\tconst liveRepository = this.getOpenRepository(hint);\n\t\treturn liveRepository && liveRepository.repository;\n\t}\n\n\tprivate async getRepositoryExact(repoPath: string): Promise {\n\t\tconst repoPathCanonical = await fs.promises.realpath(repoPath, { encoding: 'utf8' });\n\t\tconst openRepository = this.openRepositories.find(async r => {\n\t\t\tconst rootPathCanonical = await fs.promises.realpath(r.repository.root, { encoding: 'utf8' });\n\t\t\treturn pathEquals(rootPathCanonical, repoPathCanonical);\n\t\t});\n\t\treturn openRepository?.repository;\n\t}\n\n\tprivate getOpenRepository(repository: Repository): OpenRepository | undefined;\n\tprivate getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined;\n\tprivate getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined;\n\tprivate getOpenRepository(path: string): OpenRepository | undefined;\n\tprivate getOpenRepository(resource: Uri): OpenRepository | undefined;\n\tprivate getOpenRepository(hint: any): OpenRepository | undefined {\n\t\tif (!hint) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (hint instanceof Repository) {\n\t\t\treturn this.openRepositories.filter(r => r.repository === hint)[0];\n\t\t}\n\n\t\tif (hint instanceof ApiRepository) {\n\t\t\treturn this.openRepositories.filter(r => r.repository === hint.repository)[0];\n\t\t}\n\n\t\tif (typeof hint === 'string') {\n\t\t\thint = Uri.file(hint);\n\t\t}\n\n\t\tif (hint instanceof Uri) {\n\t\t\tlet resourcePath: string;\n\n\t\t\tif (hint.scheme === 'git') {\n\t\t\t\tresourcePath = fromGitUri(hint).path;\n\t\t\t} else {\n\t\t\t\tresourcePath = hint.fsPath;\n\t\t\t}\n\n\t\t\touter:\n\t\t\tfor (const liveRepository of this.openRepositories.sort((a, b) => b.repository.root.length - a.repository.root.length)) {\n\t\t\t\tif (!isDescendant(liveRepository.repository.root, resourcePath)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tfor (const submodule of liveRepository.repository.submodules) {\n\t\t\t\t\tconst submoduleRoot = path.join(liveRepository.repository.root, submodule.path);\n\n\t\t\t\t\tif (isDescendant(submoduleRoot, resourcePath)) {\n\t\t\t\t\t\tcontinue outer;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tfor (const liveRepository of this.openRepositories) {\n\t\t\tconst repository = liveRepository.repository;\n\n\t\t\tif (hint === repository.sourceControl) {\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\n\t\t\tif (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup || hint === repository.untrackedGroup) {\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n}\n", + "fileName": "./1.tst" + }, + "modified": { + "content": "/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, WorkspaceFolder } from 'vscode';\nimport TelemetryReporter from '@vscode/extension-telemetry';\nimport { Repository, RepositoryState } from './repository';\nimport { memoize, sequentialize, debounce } from './decorators';\nimport { dispose, anyEvent, filterEvent, isDescendant, pathEquals, toDisposable, eventToPromise } from './util';\nimport { Git } from './git';\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport { fromGitUri } from './uri';\nimport { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider } from './api/git';\nimport { Askpass } from './askpass';\nimport { IPushErrorHandlerRegistry } from './pushError';\nimport { ApiRepository } from './api/api1';\nimport { IRemoteSourcePublisherRegistry } from './remotePublisher';\nimport { IPostCommitCommandsProviderRegistry } from './postCommitCommands';\nimport { IBranchProtectionProviderRegistry } from './branchProtection';\n\nclass ClosedRepositoriesManager {\n\n\tprivate _repositories: Set;\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.values()];\n\t}\n\n\tconstructor(private readonly workspaceState: Memento) {\n\t\tthis._repositories = new Set(workspaceState.get('closedRepositories', []));\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string): void {\n\t\tthis._repositories.add(repository);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tisRepositoryClosed(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tthis.workspaceState.update('closedRepositories', [...this._repositories.values()]);\n\t\tcommands.executeCommand('setContext', 'git.closedRepositoryCount', this._repositories.size);\n\t}\n}\n\nclass ParentRepositoriesManager {\n\n\t/**\n\t * Key - normalized path used in user interface\n\t * Value - value indicating whether the repository should be opened\n\t */\n\tprivate _repositories = new Set;\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.values()];\n\t}\n\n\tconstructor(private readonly globalState: Memento) {\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string): void {\n\t\tthis._repositories.add(repository);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\thasRepository(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\topenRepository(repository: string): void {\n\t\tthis.globalState.update(`parentRepository:${repository}`, true);\n\t\tthis.deleteRepository(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tcommands.executeCommand('setContext', 'git.parentRepositoryCount', this._repositories.size);\n\t}\n}\n\nclass UnsafeRepositoriesManager {\n\n\t/**\n\t * Key - normalized path used in user interface\n\t * Value - path extracted from the output of the `git status` command\n\t * used when calling `git config --global --add safe.directory`\n\t */\n\tprivate _repositories = new Map();\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.keys()];\n\t}\n\n\tconstructor() {\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string, path: string): void {\n\t\tthis._repositories.set(repository, path);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tgetRepositoryPath(repository: string): string | undefined {\n\t\treturn this._repositories.get(repository);\n\t}\n\n\thasRepository(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tcommands.executeCommand('setContext', 'git.unsafeRepositoryCount', this._repositories.size);\n\t}\n}\n\nexport class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry {\n\n\tprivate _onDidOpenRepository = new EventEmitter();\n\treadonly onDidOpenRepository: Event = this._onDidOpenRepository.event;\n\n\tprivate _onDidCloseRepository = new EventEmitter();\n\treadonly onDidCloseRepository: Event = this._onDidCloseRepository.event;\n\n\tprivate _onDidChangeRepository = new EventEmitter();\n\treadonly onDidChangeRepository: Event = this._onDidChangeRepository.event;\n\n\tprivate _onDidChangeOriginalResource = new EventEmitter();\n\treadonly onDidChangeOriginalResource: Event = this._onDidChangeOriginalResource.event;\n\n\tprivate openRepositories: OpenRepository[] = [];\n\tget repositories(): Repository[] { return this.openRepositories.map(r => r.repository); }\n\n\tprivate possibleGitRepositoryPaths = new Set();\n\n\tprivate _onDidChangeState = new EventEmitter();\n\treadonly onDidChangeState = this._onDidChangeState.event;\n\n\tprivate _onDidPublish = new EventEmitter();\n\treadonly onDidPublish = this._onDidPublish.event;\n\n\tfirePublishEvent(repository: Repository, branch?: string) {\n\t\tthis._onDidPublish.fire({ repository: new ApiRepository(repository), branch: branch });\n\t}\n\n\tprivate _state: State = 'uninitialized';\n\tget state(): State { return this._state; }\n\n\tsetState(state: State): void {\n\t\tthis._state = state;\n\t\tthis._onDidChangeState.fire(state);\n\t\tcommands.executeCommand('setContext', 'git.state', state);\n\t}\n\n\t@memoize\n\tget isInitialized(): Promise {\n\t\tif (this._state === 'initialized') {\n\t\t\treturn Promise.resolve();\n\t\t}\n\n\t\treturn eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized')) as Promise;\n\t}\n\n\tprivate remoteSourcePublishers = new Set();\n\n\tprivate _onDidAddRemoteSourcePublisher = new EventEmitter();\n\treadonly onDidAddRemoteSourcePublisher = this._onDidAddRemoteSourcePublisher.event;\n\n\tprivate _onDidRemoveRemoteSourcePublisher = new EventEmitter();\n\treadonly onDidRemoveRemoteSourcePublisher = this._onDidRemoveRemoteSourcePublisher.event;\n\n\tprivate postCommitCommandsProviders = new Set();\n\n\tprivate _onDidChangePostCommitCommandsProviders = new EventEmitter();\n\treadonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event;\n\n\tprivate branchProtectionProviders = new Map>();\n\n\tprivate _onDidChangeBranchProtectionProviders = new EventEmitter();\n\treadonly onDidChangeBranchProtectionProviders = this._onDidChangeBranchProtectionProviders.event;\n\n\tprivate pushErrorHandlers = new Set();\n\n\tprivate _unsafeRepositoriesManager: UnsafeRepositoriesManager;\n\tget unsafeRepositories(): string[] {\n\t\treturn this._unsafeRepositoriesManager.repositories;\n\t}\n\n\tprivate _parentRepositoriesManager: ParentRepositoriesManager;\n\tget parentRepositories(): string[] {\n\t\treturn this._parentRepositoriesManager.repositories;\n\t}\n\n\tprivate _closedRepositoriesManager: ClosedRepositoriesManager;\n\tget closedRepositories(): string[] {\n\t\treturn [...this._closedRepositoriesManager.repositories];\n\t}\n\n\t/**\n\t * We maintain a map containing both the path and the canonical path of the\n\t * workspace folders. We are doing this as `git.exe` expands the symbolic links\n\t * while there are scenarios in which VS Code does not.\n\t *\n\t * Key - path of the workspace folder\n\t * Value - canonical path of the workspace folder\n\t */\n\tprivate _workspaceFolders = new Map();\n\n\tprivate disposables: Disposable[] = [];\n\n\tconstructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) {\n\t\t// Repositories managers\n\t\tthis._closedRepositoriesManager = new ClosedRepositoriesManager(workspaceState);\n\t\tthis._parentRepositoriesManager = new ParentRepositoriesManager(globalState);\n\t\tthis._unsafeRepositoriesManager = new UnsafeRepositoriesManager();\n\n\t\tworkspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);\n\t\twindow.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);\n\t\tworkspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables);\n\n\t\tconst fsWatcher = workspace.createFileSystemWatcher('**');\n\t\tthis.disposables.push(fsWatcher);\n\n\t\tconst onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);\n\t\tconst onGitRepositoryChange = filterEvent(onWorkspaceChange, uri => /\\/\\.git/.test(uri.path));\n\t\tconst onPossibleGitRepositoryChange = filterEvent(onGitRepositoryChange, uri => !this.getRepository(uri));\n\t\tonPossibleGitRepositoryChange(this.onPossibleGitRepositoryChange, this, this.disposables);\n\n\t\tthis.setState('uninitialized');\n\t\tthis.doInitialScan().finally(() => this.setState('initialized'));\n\t}\n\n\tprivate async doInitialScan(): Promise {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tconst parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt');\n\n\t\t// Initial repository scan function\n\t\tconst initialScanFn = () => Promise.all([\n\t\t\tthis.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }),\n\t\t\tthis.onDidChangeVisibleTextEditors(window.visibleTextEditors),\n\t\t\tthis.scanWorkspaceFolders()\n\t\t]);\n\n\t\tif (config.get('showProgress', true)) {\n\t\t\tawait window.withProgress({ location: ProgressLocation.SourceControl }, initialScanFn);\n\t\t} else {\n\t\t\tawait initialScanFn();\n\t\t}\n\n\t\tif (this.parentRepositories.length !== 0 &&\n\t\t\tparentRepositoryConfig === 'prompt') {\n\t\t\t// Parent repositories notification\n\t\t\tthis.showParentRepositoryNotification();\n\t\t} else if (this.unsafeRepositories.length !== 0) {\n\t\t\t// Unsafe repositories notification\n\t\t\tthis.showUnsafeRepositoryNotification();\n\t\t}\n\n\t\t/* __GDPR__\n\t\t\t\"git.repositoryInitialScan\" : {\n\t\t\t\t\"owner\": \"lszomoru\",\n\t\t\t\t\"autoRepositoryDetection\": { \"classification\": \"SystemMetaData\", \"purpose\": \"FeatureInsight\", \"comment\": \"Setting that controls the initial repository scan\" },\n\t\t\t\t\"repositoryCount\": { \"classification\": \"SystemMetaData\", \"purpose\": \"FeatureInsight\", \"isMeasurement\": true, \"comment\": \"Number of repositories opened during initial repository scan\" }\n\t\t\t}\n\t\t*/\n\t\tthis.telemetryReporter.sendTelemetryEvent('git.repositoryInitialScan', { autoRepositoryDetection: String(autoRepositoryDetection) }, { repositoryCount: this.openRepositories.length });\n\t}\n\n\t/**\n\t * Scans each workspace folder, looking for git repositories. By\n\t * default it scans one level deep but that can be changed using\n\t * the git.repositoryScanMaxDepth setting.\n\t */\n\tprivate async scanWorkspaceFolders(): Promise {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tthis.logger.trace(`[swsf] Scan workspace sub folders. autoRepositoryDetection=${autoRepositoryDetection}`);\n\n\t\tif (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') {\n\t\t\treturn;\n\t\t}\n\n\t\tawait Promise.all((workspace.workspaceFolders || []).map(async folder => {\n\t\t\tconst root = folder.uri.fsPath;\n\t\t\tthis.logger.trace(`[swsf] Workspace folder: ${root}`);\n\n\t\t\t// Workspace folder children\n\t\t\tconst repositoryScanMaxDepth = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanMaxDepth', 1);\n\t\t\tconst repositoryScanIgnoredFolders = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanIgnoredFolders', []);\n\n\t\t\tconst subfolders = new Set(await this.traverseWorkspaceFolder(root, repositoryScanMaxDepth, repositoryScanIgnoredFolders));\n\n\t\t\t// Repository scan folders\n\t\t\tconst scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || [];\n\t\t\tthis.logger.trace(`[swsf] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`);\n\n\t\t\tfor (const scanPath of scanPaths) {\n\t\t\t\tif (scanPath === '.git') {\n\t\t\t\t\tthis.logger.trace('[swsf] \\'.git\\' not supported in \\'git.scanRepositories\\' setting.');\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (path.isAbsolute(scanPath)) {\n\t\t\t\t\tconst notSupportedMessage = l10n.t('Absolute paths not supported in \"git.scanRepositories\" setting.');\n\t\t\t\t\tthis.logger.warn(notSupportedMessage);\n\t\t\t\t\tconsole.warn(notSupportedMessage);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tsubfolders.add(path.join(root, scanPath));\n\t\t\t}\n\n\t\t\tthis.logger.trace(`[swsf] Workspace scan sub folders: [${[...subfolders].join(', ')}]`);\n\t\t\tawait Promise.all([...subfolders].map(f => this.openRepository(f)));\n\t\t}));\n\t}\n\n\tprivate async traverseWorkspaceFolder(workspaceFolder: string, maxDepth: number, repositoryScanIgnoredFolders: string[]): Promise {\n\t\tconst result: string[] = [];\n\t\tconst foldersToTravers = [{ path: workspaceFolder, depth: 0 }];\n\n\t\twhile (foldersToTravers.length > 0) {\n\t\t\tconst currentFolder = foldersToTravers.shift()!;\n\n\t\t\tif (currentFolder.depth < maxDepth || maxDepth === -1) {\n\t\t\t\tconst children = await fs.promises.readdir(currentFolder.path, { withFileTypes: true });\n\t\t\t\tconst childrenFolders = children\n\t\t\t\t\t.filter(dirent =>\n\t\t\t\t\t\tdirent.isDirectory() && dirent.name !== '.git' &&\n\t\t\t\t\t\t!repositoryScanIgnoredFolders.find(f => pathEquals(dirent.name, f)))\n\t\t\t\t\t.map(dirent => path.join(currentFolder.path, dirent.name));\n\n\t\t\t\tresult.push(...childrenFolders);\n\t\t\t\tfoldersToTravers.push(...childrenFolders.map(folder => {\n\t\t\t\t\treturn { path: folder, depth: currentFolder.depth + 1 };\n\t\t\t\t}));\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate onPossibleGitRepositoryChange(uri: Uri): void {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\n\t\tif (autoRepositoryDetection === false) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.eventuallyScanPossibleGitRepository(uri.fsPath.replace(/\\.git.*$/, ''));\n\t}\n\n\tprivate eventuallyScanPossibleGitRepository(path: string) {\n\t\tthis.possibleGitRepositoryPaths.add(path);\n\t\tthis.eventuallyScanPossibleGitRepositories();\n\t}\n\n\t@debounce(500)\n\tprivate eventuallyScanPossibleGitRepositories(): void {\n\t\tfor (const path of this.possibleGitRepositoryPaths) {\n\t\t\tthis.openRepository(path);\n\t\t}\n\n\t\tthis.possibleGitRepositoryPaths.clear();\n\t}\n\n\tprivate async onDidChangeWorkspaceFolders({ added, removed }: WorkspaceFoldersChangeEvent): Promise {\n\t\tconst possibleRepositoryFolders = added\n\t\t\t.filter(folder => !this.getOpenRepository(folder.uri));\n\n\t\tconst activeRepositoriesList = window.visibleTextEditors\n\t\t\t.map(editor => this.getRepository(editor.document.uri))\n\t\t\t.filter(repository => !!repository) as Repository[];\n\n\t\tconst activeRepositories = new Set(activeRepositoriesList);\n\t\tconst openRepositoriesToDispose = removed\n\t\t\t.map(folder => this.getOpenRepository(folder.uri))\n\t\t\t.filter(r => !!r)\n\t\t\t.filter(r => !activeRepositories.has(r!.repository))\n\t\t\t.filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[];\n\n\t\topenRepositoriesToDispose.forEach(r => r.dispose());\n\t\tthis.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`);\n\t\tawait Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath)));\n\t}\n\n\tprivate onDidChangeConfiguration(): void {\n\t\tconst possibleRepositoryFolders = (workspace.workspaceFolders || [])\n\t\t\t.filter(folder => workspace.getConfiguration('git', folder.uri).get('enabled') === true)\n\t\t\t.filter(folder => !this.getOpenRepository(folder.uri));\n\n\t\tconst openRepositoriesToDispose = this.openRepositories\n\t\t\t.map(repository => ({ repository, root: Uri.file(repository.repository.root) }))\n\t\t\t.filter(({ root }) => workspace.getConfiguration('git', root).get('enabled') !== true)\n\t\t\t.map(({ repository }) => repository);\n\n\t\tthis.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`);\n\t\tpossibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath));\n\t\topenRepositoriesToDispose.forEach(r => r.dispose());\n\t}\n\n\tprivate async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise {\n\t\tif (!workspace.isTrusted) {\n\t\t\tthis.logger.trace('[svte] Workspace is not trusted.');\n\t\t\treturn;\n\t\t}\n\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tthis.logger.trace(`[svte] Scan visible text editors. autoRepositoryDetection=${autoRepositoryDetection}`);\n\n\t\tif (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') {\n\t\t\treturn;\n\t\t}\n\n\t\tawait Promise.all(editors.map(async editor => {\n\t\t\tconst uri = editor.document.uri;\n\n\t\t\tif (uri.scheme !== 'file') {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst repository = this.getRepository(uri);\n\n\t\t\tif (repository) {\n\t\t\t\tthis.logger.trace(`[svte] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.logger.trace(`[svte] Open repository for editor resource ${uri.fsPath}`);\n\t\t\tawait this.openRepository(path.dirname(uri.fsPath));\n\t\t}));\n\t}\n\n\t@sequentialize\n\tasync openRepository(repoPath: string, openIfClosed = false): Promise {\n\t\tthis.logger.trace(`Opening repository: ${repoPath}`);\n\t\tconst existingRepository = await this.getRepositoryExact(repoPath);\n\t\tif (existingRepository) {\n\t\t\tthis.logger.trace(`Repository for path ${repoPath} already exists: ${existingRepository.root})`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst config = workspace.getConfiguration('git', Uri.file(repoPath));\n\t\tconst enabled = config.get('enabled') === true;\n\n\t\tif (!enabled) {\n\t\t\tthis.logger.trace('Git is not enabled');\n\t\t\treturn;\n\t\t}\n\n\t\tif (!workspace.isTrusted) {\n\t\t\t// Check if the folder is a bare repo: if it has a file named HEAD && `rev-parse --show -cdup` is empty\n\t\t\ttry {\n\t\t\t\tfs.accessSync(path.join(repoPath, 'HEAD'), fs.constants.F_OK);\n\t\t\t\tconst result = await this.git.exec(repoPath, ['-C', repoPath, 'rev-parse', '--show-cdup']);\n\t\t\t\tif (result.stderr.trim() === '' && result.stdout.trim() === '') {\n\t\t\t\t\tthis.logger.trace(`Bare repository: ${repoPath}`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// If this throw, we should be good to open the repo (e.g. HEAD doesn't exist)\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tconst { repositoryRoot, unsafeRepositoryMatch } = await this.getRepositoryRoot(repoPath);\n\t\t\tthis.logger.trace(`Repository root for path ${repoPath} is: ${repositoryRoot}`);\n\n\t\t\tconst existingRepository = await this.getRepositoryExact(repositoryRoot);\n\t\t\tif (existingRepository) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} already exists: ${existingRepository.root}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.shouldRepositoryBeIgnored(repositoryRoot)) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} is ignored`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle git repositories that are in parent folders\n\t\t\tconst parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt');\n\t\t\tif (parentRepositoryConfig !== 'always' && this.globalState.get(`parentRepository:${repositoryRoot}`) !== true) {\n\t\t\t\tconst isRepositoryOutsideWorkspace = await this.isRepositoryOutsideWorkspace(repositoryRoot);\n\t\t\t\tif (isRepositoryOutsideWorkspace) {\n\t\t\t\t\tthis.logger.trace(`Repository in parent folder: ${repositoryRoot}`);\n\n\t\t\t\t\tif (!this._parentRepositoriesManager.hasRepository(repositoryRoot)) {\n\t\t\t\t\t\t// Show a notification if the parent repository is opened after the initial scan\n\t\t\t\t\t\tif (this.state === 'initialized' && parentRepositoryConfig === 'prompt') {\n\t\t\t\t\t\t\tthis.showParentRepositoryNotification();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis._parentRepositoriesManager.addRepository(repositoryRoot);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle unsafe repositories\n\t\t\tif (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) {\n\t\t\t\tthis.logger.trace(`Unsafe repository: ${repositoryRoot}`);\n\n\t\t\t\t// Show a notification if the unsafe repository is opened after the initial scan\n\t\t\t\tif (this._state === 'initialized' && !this._unsafeRepositoriesManager.hasRepository(repositoryRoot)) {\n\t\t\t\t\tthis.showUnsafeRepositoryNotification();\n\t\t\t\t}\n\n\t\t\t\tthis._unsafeRepositoriesManager.addRepository(repositoryRoot, unsafeRepositoryMatch[2]);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle repositories that were closed by the user\n\t\t\tif (!openIfClosed && this._closedRepositoriesManager.isRepositoryClosed(repositoryRoot)) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} is closed`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Open repository\n\t\t\tconst dotGit = await this.git.getRepositoryDotGit(repositoryRoot);\n\t\t\tconst repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this, this.globalState, this.logger, this.telemetryReporter);\n\n\t\t\tthis.open(repository);\n\t\t\tthis._closedRepositoriesManager.deleteRepository(repository.root);\n\n\t\t\t// Do not await this, we want SCM\n\t\t\t// to know about the repo asap\n\t\t\trepository.status();\n\t\t} catch (err) {\n\t\t\t// noop\n\t\t\tthis.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${err}`);\n\t\t}\n\t}\n\n\tasync openParentRepository(repoPath: string): Promise {\n\t\tawait this.openRepository(repoPath);\n\t\tthis._parentRepositoriesManager.openRepository(repoPath);\n\t}\n\n\tprivate async getRepositoryRoot(repoPath: string): Promise<{ repositoryRoot: string; unsafeRepositoryMatch: RegExpMatchArray | null }> {\n\t\ttry {\n\t\t\tconst rawRoot = await this.git.getRepositoryRoot(repoPath);\n\n\t\t\t// This can happen whenever `path` has the wrong case sensitivity in case\n\t\t\t// insensitive file systems https://github.com/microsoft/vscode/issues/33498\n\t\t\treturn { repositoryRoot: Uri.file(rawRoot).fsPath, unsafeRepositoryMatch: null };\n\t\t} catch (err) {\n\t\t\t// Handle unsafe repository\n\t\t\tconst unsafeRepositoryMatch = /^fatal: detected dubious ownership in repository at \\'([^']+)\\'[\\s\\S]*git config --global --add safe\\.directory '?([^'\\n]+)'?$/m.exec(err.stderr);\n\t\t\tif (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) {\n\t\t\t\treturn { repositoryRoot: path.normalize(unsafeRepositoryMatch[1]), unsafeRepositoryMatch };\n\t\t\t}\n\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tprivate shouldRepositoryBeIgnored(repositoryRoot: string): boolean {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst ignoredRepos = config.get('ignoredRepositories') || [];\n\n\t\tfor (const ignoredRepo of ignoredRepos) {\n\t\t\tif (path.isAbsolute(ignoredRepo)) {\n\t\t\t\tif (pathEquals(ignoredRepo, repositoryRoot)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (const folder of workspace.workspaceFolders || []) {\n\t\t\t\t\tif (pathEquals(path.join(folder.uri.fsPath, ignoredRepo), repositoryRoot)) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate open(repository: Repository): void {\n\t\tthis.logger.info(`Open repository: ${repository.root}`);\n\n\t\tconst onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed);\n\t\tconst disappearListener = onDidDisappearRepository(() => dispose());\n\t\tconst changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri }));\n\t\tconst originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri }));\n\n\t\tconst shouldDetectSubmodules = workspace\n\t\t\t.getConfiguration('git', Uri.file(repository.root))\n\t\t\t.get('detectSubmodules') as boolean;\n\n\t\tconst submodulesLimit = workspace\n\t\t\t.getConfiguration('git', Uri.file(repository.root))\n\t\t\t.get('detectSubmodulesLimit') as number;\n\n\t\tconst checkForSubmodules = () => {\n\t\t\tif (!shouldDetectSubmodules) {\n\t\t\t\tthis.logger.trace('Automatic detection of git submodules is not enabled.');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (repository.submodules.length > submodulesLimit) {\n\t\t\t\twindow.showWarningMessage(l10n.t('The \"{0}\" repository has {1} submodules which won\\'t be opened automatically. You can still open each one individually by opening a file within.', path.basename(repository.root), repository.submodules.length));\n\t\t\t\tstatusListener.dispose();\n\t\t\t}\n\n\t\t\trepository.submodules\n\t\t\t\t.slice(0, submodulesLimit)\n\t\t\t\t.map(r => path.join(repository.root, r.path))\n\t\t\t\t.forEach(p => {\n\t\t\t\t\tthis.logger.trace(`Opening submodule: '${p}'`);\n\t\t\t\t\tthis.eventuallyScanPossibleGitRepository(p);\n\t\t\t\t});\n\t\t};\n\n\t\tconst updateMergeChanges = () => {\n\t\t\t// set mergeChanges context\n\t\t\tconst mergeChanges: Uri[] = [];\n\t\t\tfor (const { repository } of this.openRepositories.values()) {\n\t\t\t\tfor (const state of repository.mergeGroup.resourceStates) {\n\t\t\t\t\tmergeChanges.push(state.resourceUri);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcommands.executeCommand('setContext', 'git.mergeChanges', mergeChanges);\n\t\t};\n\n\t\tconst statusListener = repository.onDidRunGitStatus(() => {\n\t\t\tcheckForSubmodules();\n\t\t\tupdateMergeChanges();\n\t\t});\n\t\tcheckForSubmodules();\n\n\t\tconst updateOperationInProgressContext = () => {\n\t\t\tlet operationInProgress = false;\n\t\t\tfor (const { repository } of this.openRepositories.values()) {\n\t\t\t\tif (repository.operations.shouldDisableCommands()) {\n\t\t\t\t\toperationInProgress = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcommands.executeCommand('setContext', 'operationInProgress', operationInProgress);\n\t\t};\n\n\t\tconst operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event);\n\t\tconst operationListener = operationEvent(() => updateOperationInProgressContext());\n\t\tupdateOperationInProgressContext();\n\n\t\tconst dispose = () => {\n\t\t\tdisappearListener.dispose();\n\t\t\tchangeListener.dispose();\n\t\t\toriginalResourceChangeListener.dispose();\n\t\t\tstatusListener.dispose();\n\t\t\toperationListener.dispose();\n\t\t\trepository.dispose();\n\n\t\t\tthis.openRepositories = this.openRepositories.filter(e => e !== openRepository);\n\t\t\tthis._onDidCloseRepository.fire(repository);\n\t\t};\n\n\t\tconst openRepository = { repository, dispose };\n\t\tthis.openRepositories.push(openRepository);\n\t\tupdateMergeChanges();\n\t\tthis._onDidOpenRepository.fire(repository);\n\t}\n\n\tclose(repository: Repository): void {\n\t\tconst openRepository = this.getOpenRepository(repository);\n\n\t\tif (!openRepository) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.logger.info(`Close repository: ${repository.root}`);\n\t\tthis._closedRepositoriesManager.addRepository(openRepository.repository.root);\n\n\t\topenRepository.dispose();\n\t}\n\n\tasync pickRepository(): Promise {\n\t\tif (this.openRepositories.length === 0) {\n\t\t\tthrow new Error(l10n.t('There are no available repositories'));\n\t\t}\n\n\t\tconst picks = this.openRepositories.map((e, index) => new RepositoryPick(e.repository, index));\n\t\tconst active = window.activeTextEditor;\n\t\tconst repository = active && this.getRepository(active.document.fileName);\n\t\tconst index = picks.findIndex(pick => pick.repository === repository);\n\n\t\t// Move repository pick containing the active text editor to appear first\n\t\tif (index > -1) {\n\t\t\tpicks.unshift(...picks.splice(index, 1));\n\t\t}\n\n\t\tconst placeHolder = l10n.t('Choose a repository');\n\t\tconst pick = await window.showQuickPick(picks, { placeHolder });\n\n\t\treturn pick && pick.repository;\n\t}\n\n\tgetRepository(sourceControl: SourceControl): Repository | undefined;\n\tgetRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined;\n\tgetRepository(path: string): Repository | undefined;\n\tgetRepository(resource: Uri): Repository | undefined;\n\tgetRepository(hint: any): Repository | undefined {\n\t\tconst liveRepository = this.getOpenRepository(hint);\n\t\treturn liveRepository && liveRepository.repository;\n\t}\n\n\tprivate async getRepositoryExact(repoPath: string): Promise {\n\t\tconst repoPathCanonical = await fs.promises.realpath(repoPath, { encoding: 'utf8' });\n\n\t\tfor (const openRepository of this.openRepositories) {\n\t\t\tconst rootPathCanonical = await fs.promises.realpath(openRepository.repository.root, { encoding: 'utf8' });\n\t\t\tif (pathEquals(rootPathCanonical, repoPathCanonical)) {\n\t\t\t\treturn openRepository.repository;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate getOpenRepository(repository: Repository): OpenRepository | undefined;\n\tprivate getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined;\n\tprivate getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined;\n\tprivate getOpenRepository(path: string): OpenRepository | undefined;\n\tprivate getOpenRepository(resource: Uri): OpenRepository | undefined;\n\tprivate getOpenRepository(hint: any): OpenRepository | undefined {\n\t\tif (!hint) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (hint instanceof Repository) {\n\t\t\treturn this.openRepositories.filter(r => r.repository === hint)[0];\n\t\t}\n\n\t\tif (hint instanceof ApiRepository) {\n\t\t\treturn this.openRepositories.filter(r => r.repository === hint.repository)[0];\n\t\t}\n\n\t\tif (typeof hint === 'string') {\n\t\t\thint = Uri.file(hint);\n\t\t}\n\n\t\tif (hint instanceof Uri) {\n\t\t\tlet resourcePath: string;\n\n\t\t\tif (hint.scheme === 'git') {\n\t\t\t\tresourcePath = fromGitUri(hint).path;\n\t\t\t} else {\n\t\t\t\tresourcePath = hint.fsPath;\n\t\t\t}\n\n\t\t\touter:\n\t\t\tfor (const liveRepository of this.openRepositories.sort((a, b) => b.repository.root.length - a.repository.root.length)) {\n\t\t\t\tif (!isDescendant(liveRepository.repository.root, resourcePath)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tfor (const submodule of liveRepository.repository.submodules) {\n\t\t\t\t\tconst submoduleRoot = path.join(liveRepository.repository.root, submodule.path);\n\n\t\t\t\t\tif (isDescendant(submoduleRoot, resourcePath)) {\n\t\t\t\t\t\tcontinue outer;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tfor (const liveRepository of this.openRepositories) {\n\t\t\tconst repository = liveRepository.repository;\n\n\t\t\tif (hint === repository.sourceControl) {\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\n\t\t\tif (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup || hint === repository.untrackedGroup) {\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tgetRepositoryForSubmodule(submoduleUri: Uri): Repository | undefined {\n\t\tfor (const repository of this.repositories) {\n\t\t\tfor (const submodule of repository.submodules) {\n\t\t\t\tconst submodulePath = path.join(repository.root, submodule.path);\n\n\t\t\t\tif (submodulePath === submoduleUri.fsPath) {\n\t\t\t\t\treturn repository;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[742,747)", + "modifiedRange": "[742,751)", + "innerChanges": [ + { + "originalRange": "[742,3 -> 742,3]", + "modifiedRange": "[742,1 -> 743,8]" + }, + { + "originalRange": "[742,24 -> 742,25]", + "modifiedRange": "[743,29 -> 743,31]" + }, + { + "originalRange": "[742,47 -> 742,63]", + "modifiedRange": "[743,53 -> 743,54]" + }, + { + "originalRange": "[743,57 -> 743,58]", + "modifiedRange": "[744,57 -> 744,71]" + }, + { + "originalRange": "[744,4 -> 744,11]", + "modifiedRange": "[745,4 -> 745,8]" + }, + { + "originalRange": "[744,59 -> 745,6]", + "modifiedRange": "[745,56 -> 745,59]" + }, + { + "originalRange": "[746,24 -> 746,25]", + "modifiedRange": "[746,26 -> 746,26]" + }, + { + "originalRange": "[747,1 -> 747,1]", + "modifiedRange": "[747,4 -> 751,1]" + } + ] + }, + { + "originalRange": "[815,815)", + "modifiedRange": "[819,832)", + "innerChanges": [ + { + "originalRange": "[815,1 -> 815,1]", + "modifiedRange": "[819,2 -> 832,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/legacy.expected.diff.json new file mode 100644 index 00000000000..e48b9d56df1 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/legacy.expected.diff.json @@ -0,0 +1,55 @@ +{ + "original": { + "content": "/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, WorkspaceFolder } from 'vscode';\nimport TelemetryReporter from '@vscode/extension-telemetry';\nimport { Repository, RepositoryState } from './repository';\nimport { memoize, sequentialize, debounce } from './decorators';\nimport { dispose, anyEvent, filterEvent, isDescendant, pathEquals, toDisposable, eventToPromise } from './util';\nimport { Git } from './git';\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport { fromGitUri } from './uri';\nimport { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider } from './api/git';\nimport { Askpass } from './askpass';\nimport { IPushErrorHandlerRegistry } from './pushError';\nimport { ApiRepository } from './api/api1';\nimport { IRemoteSourcePublisherRegistry } from './remotePublisher';\nimport { IPostCommitCommandsProviderRegistry } from './postCommitCommands';\nimport { IBranchProtectionProviderRegistry } from './branchProtection';\n\nclass ClosedRepositoriesManager {\n\n\tprivate _repositories: Set;\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.values()];\n\t}\n\n\tconstructor(private readonly workspaceState: Memento) {\n\t\tthis._repositories = new Set(workspaceState.get('closedRepositories', []));\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string): void {\n\t\tthis._repositories.add(repository);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tisRepositoryClosed(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tthis.workspaceState.update('closedRepositories', [...this._repositories.values()]);\n\t\tcommands.executeCommand('setContext', 'git.closedRepositoryCount', this._repositories.size);\n\t}\n}\n\nclass ParentRepositoriesManager {\n\n\t/**\n\t * Key - normalized path used in user interface\n\t * Value - value indicating whether the repository should be opened\n\t */\n\tprivate _repositories = new Set;\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.values()];\n\t}\n\n\tconstructor(private readonly globalState: Memento) {\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string): void {\n\t\tthis._repositories.add(repository);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\thasRepository(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\topenRepository(repository: string): void {\n\t\tthis.globalState.update(`parentRepository:${repository}`, true);\n\t\tthis.deleteRepository(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tcommands.executeCommand('setContext', 'git.parentRepositoryCount', this._repositories.size);\n\t}\n}\n\nclass UnsafeRepositoriesManager {\n\n\t/**\n\t * Key - normalized path used in user interface\n\t * Value - path extracted from the output of the `git status` command\n\t * used when calling `git config --global --add safe.directory`\n\t */\n\tprivate _repositories = new Map();\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.keys()];\n\t}\n\n\tconstructor() {\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string, path: string): void {\n\t\tthis._repositories.set(repository, path);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tgetRepositoryPath(repository: string): string | undefined {\n\t\treturn this._repositories.get(repository);\n\t}\n\n\thasRepository(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tcommands.executeCommand('setContext', 'git.unsafeRepositoryCount', this._repositories.size);\n\t}\n}\n\nexport class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry {\n\n\tprivate _onDidOpenRepository = new EventEmitter();\n\treadonly onDidOpenRepository: Event = this._onDidOpenRepository.event;\n\n\tprivate _onDidCloseRepository = new EventEmitter();\n\treadonly onDidCloseRepository: Event = this._onDidCloseRepository.event;\n\n\tprivate _onDidChangeRepository = new EventEmitter();\n\treadonly onDidChangeRepository: Event = this._onDidChangeRepository.event;\n\n\tprivate _onDidChangeOriginalResource = new EventEmitter();\n\treadonly onDidChangeOriginalResource: Event = this._onDidChangeOriginalResource.event;\n\n\tprivate openRepositories: OpenRepository[] = [];\n\tget repositories(): Repository[] { return this.openRepositories.map(r => r.repository); }\n\n\tprivate possibleGitRepositoryPaths = new Set();\n\n\tprivate _onDidChangeState = new EventEmitter();\n\treadonly onDidChangeState = this._onDidChangeState.event;\n\n\tprivate _onDidPublish = new EventEmitter();\n\treadonly onDidPublish = this._onDidPublish.event;\n\n\tfirePublishEvent(repository: Repository, branch?: string) {\n\t\tthis._onDidPublish.fire({ repository: new ApiRepository(repository), branch: branch });\n\t}\n\n\tprivate _state: State = 'uninitialized';\n\tget state(): State { return this._state; }\n\n\tsetState(state: State): void {\n\t\tthis._state = state;\n\t\tthis._onDidChangeState.fire(state);\n\t\tcommands.executeCommand('setContext', 'git.state', state);\n\t}\n\n\t@memoize\n\tget isInitialized(): Promise {\n\t\tif (this._state === 'initialized') {\n\t\t\treturn Promise.resolve();\n\t\t}\n\n\t\treturn eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized')) as Promise;\n\t}\n\n\tprivate remoteSourcePublishers = new Set();\n\n\tprivate _onDidAddRemoteSourcePublisher = new EventEmitter();\n\treadonly onDidAddRemoteSourcePublisher = this._onDidAddRemoteSourcePublisher.event;\n\n\tprivate _onDidRemoveRemoteSourcePublisher = new EventEmitter();\n\treadonly onDidRemoveRemoteSourcePublisher = this._onDidRemoveRemoteSourcePublisher.event;\n\n\tprivate postCommitCommandsProviders = new Set();\n\n\tprivate _onDidChangePostCommitCommandsProviders = new EventEmitter();\n\treadonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event;\n\n\tprivate branchProtectionProviders = new Map>();\n\n\tprivate _onDidChangeBranchProtectionProviders = new EventEmitter();\n\treadonly onDidChangeBranchProtectionProviders = this._onDidChangeBranchProtectionProviders.event;\n\n\tprivate pushErrorHandlers = new Set();\n\n\tprivate _unsafeRepositoriesManager: UnsafeRepositoriesManager;\n\tget unsafeRepositories(): string[] {\n\t\treturn this._unsafeRepositoriesManager.repositories;\n\t}\n\n\tprivate _parentRepositoriesManager: ParentRepositoriesManager;\n\tget parentRepositories(): string[] {\n\t\treturn this._parentRepositoriesManager.repositories;\n\t}\n\n\tprivate _closedRepositoriesManager: ClosedRepositoriesManager;\n\tget closedRepositories(): string[] {\n\t\treturn [...this._closedRepositoriesManager.repositories];\n\t}\n\n\t/**\n\t * We maintain a map containing both the path and the canonical path of the\n\t * workspace folders. We are doing this as `git.exe` expands the symbolic links\n\t * while there are scenarios in which VS Code does not.\n\t *\n\t * Key - path of the workspace folder\n\t * Value - canonical path of the workspace folder\n\t */\n\tprivate _workspaceFolders = new Map();\n\n\tprivate disposables: Disposable[] = [];\n\n\tconstructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) {\n\t\t// Repositories managers\n\t\tthis._closedRepositoriesManager = new ClosedRepositoriesManager(workspaceState);\n\t\tthis._parentRepositoriesManager = new ParentRepositoriesManager(globalState);\n\t\tthis._unsafeRepositoriesManager = new UnsafeRepositoriesManager();\n\n\t\tworkspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);\n\t\twindow.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);\n\t\tworkspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables);\n\n\t\tconst fsWatcher = workspace.createFileSystemWatcher('**');\n\t\tthis.disposables.push(fsWatcher);\n\n\t\tconst onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);\n\t\tconst onGitRepositoryChange = filterEvent(onWorkspaceChange, uri => /\\/\\.git/.test(uri.path));\n\t\tconst onPossibleGitRepositoryChange = filterEvent(onGitRepositoryChange, uri => !this.getRepository(uri));\n\t\tonPossibleGitRepositoryChange(this.onPossibleGitRepositoryChange, this, this.disposables);\n\n\t\tthis.setState('uninitialized');\n\t\tthis.doInitialScan().finally(() => this.setState('initialized'));\n\t}\n\n\tprivate async doInitialScan(): Promise {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tconst parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt');\n\n\t\t// Initial repository scan function\n\t\tconst initialScanFn = () => Promise.all([\n\t\t\tthis.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }),\n\t\t\tthis.onDidChangeVisibleTextEditors(window.visibleTextEditors),\n\t\t\tthis.scanWorkspaceFolders()\n\t\t]);\n\n\t\tif (config.get('showProgress', true)) {\n\t\t\tawait window.withProgress({ location: ProgressLocation.SourceControl }, initialScanFn);\n\t\t} else {\n\t\t\tawait initialScanFn();\n\t\t}\n\n\t\tif (this.parentRepositories.length !== 0 &&\n\t\t\tparentRepositoryConfig === 'prompt') {\n\t\t\t// Parent repositories notification\n\t\t\tthis.showParentRepositoryNotification();\n\t\t} else if (this.unsafeRepositories.length !== 0) {\n\t\t\t// Unsafe repositories notification\n\t\t\tthis.showUnsafeRepositoryNotification();\n\t\t}\n\n\t\t/* __GDPR__\n\t\t\t\"git.repositoryInitialScan\" : {\n\t\t\t\t\"owner\": \"lszomoru\",\n\t\t\t\t\"autoRepositoryDetection\": { \"classification\": \"SystemMetaData\", \"purpose\": \"FeatureInsight\", \"comment\": \"Setting that controls the initial repository scan\" },\n\t\t\t\t\"repositoryCount\": { \"classification\": \"SystemMetaData\", \"purpose\": \"FeatureInsight\", \"isMeasurement\": true, \"comment\": \"Number of repositories opened during initial repository scan\" }\n\t\t\t}\n\t\t*/\n\t\tthis.telemetryReporter.sendTelemetryEvent('git.repositoryInitialScan', { autoRepositoryDetection: String(autoRepositoryDetection) }, { repositoryCount: this.openRepositories.length });\n\t}\n\n\t/**\n\t * Scans each workspace folder, looking for git repositories. By\n\t * default it scans one level deep but that can be changed using\n\t * the git.repositoryScanMaxDepth setting.\n\t */\n\tprivate async scanWorkspaceFolders(): Promise {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tthis.logger.trace(`[swsf] Scan workspace sub folders. autoRepositoryDetection=${autoRepositoryDetection}`);\n\n\t\tif (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') {\n\t\t\treturn;\n\t\t}\n\n\t\tawait Promise.all((workspace.workspaceFolders || []).map(async folder => {\n\t\t\tconst root = folder.uri.fsPath;\n\t\t\tthis.logger.trace(`[swsf] Workspace folder: ${root}`);\n\n\t\t\t// Workspace folder children\n\t\t\tconst repositoryScanMaxDepth = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanMaxDepth', 1);\n\t\t\tconst repositoryScanIgnoredFolders = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanIgnoredFolders', []);\n\n\t\t\tconst subfolders = new Set(await this.traverseWorkspaceFolder(root, repositoryScanMaxDepth, repositoryScanIgnoredFolders));\n\n\t\t\t// Repository scan folders\n\t\t\tconst scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || [];\n\t\t\tthis.logger.trace(`[swsf] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`);\n\n\t\t\tfor (const scanPath of scanPaths) {\n\t\t\t\tif (scanPath === '.git') {\n\t\t\t\t\tthis.logger.trace('[swsf] \\'.git\\' not supported in \\'git.scanRepositories\\' setting.');\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (path.isAbsolute(scanPath)) {\n\t\t\t\t\tconst notSupportedMessage = l10n.t('Absolute paths not supported in \"git.scanRepositories\" setting.');\n\t\t\t\t\tthis.logger.warn(notSupportedMessage);\n\t\t\t\t\tconsole.warn(notSupportedMessage);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tsubfolders.add(path.join(root, scanPath));\n\t\t\t}\n\n\t\t\tthis.logger.trace(`[swsf] Workspace scan sub folders: [${[...subfolders].join(', ')}]`);\n\t\t\tawait Promise.all([...subfolders].map(f => this.openRepository(f)));\n\t\t}));\n\t}\n\n\tprivate async traverseWorkspaceFolder(workspaceFolder: string, maxDepth: number, repositoryScanIgnoredFolders: string[]): Promise {\n\t\tconst result: string[] = [];\n\t\tconst foldersToTravers = [{ path: workspaceFolder, depth: 0 }];\n\n\t\twhile (foldersToTravers.length > 0) {\n\t\t\tconst currentFolder = foldersToTravers.shift()!;\n\n\t\t\tif (currentFolder.depth < maxDepth || maxDepth === -1) {\n\t\t\t\tconst children = await fs.promises.readdir(currentFolder.path, { withFileTypes: true });\n\t\t\t\tconst childrenFolders = children\n\t\t\t\t\t.filter(dirent =>\n\t\t\t\t\t\tdirent.isDirectory() && dirent.name !== '.git' &&\n\t\t\t\t\t\t!repositoryScanIgnoredFolders.find(f => pathEquals(dirent.name, f)))\n\t\t\t\t\t.map(dirent => path.join(currentFolder.path, dirent.name));\n\n\t\t\t\tresult.push(...childrenFolders);\n\t\t\t\tfoldersToTravers.push(...childrenFolders.map(folder => {\n\t\t\t\t\treturn { path: folder, depth: currentFolder.depth + 1 };\n\t\t\t\t}));\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate onPossibleGitRepositoryChange(uri: Uri): void {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\n\t\tif (autoRepositoryDetection === false) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.eventuallyScanPossibleGitRepository(uri.fsPath.replace(/\\.git.*$/, ''));\n\t}\n\n\tprivate eventuallyScanPossibleGitRepository(path: string) {\n\t\tthis.possibleGitRepositoryPaths.add(path);\n\t\tthis.eventuallyScanPossibleGitRepositories();\n\t}\n\n\t@debounce(500)\n\tprivate eventuallyScanPossibleGitRepositories(): void {\n\t\tfor (const path of this.possibleGitRepositoryPaths) {\n\t\t\tthis.openRepository(path);\n\t\t}\n\n\t\tthis.possibleGitRepositoryPaths.clear();\n\t}\n\n\tprivate async onDidChangeWorkspaceFolders({ added, removed }: WorkspaceFoldersChangeEvent): Promise {\n\t\tconst possibleRepositoryFolders = added\n\t\t\t.filter(folder => !this.getOpenRepository(folder.uri));\n\n\t\tconst activeRepositoriesList = window.visibleTextEditors\n\t\t\t.map(editor => this.getRepository(editor.document.uri))\n\t\t\t.filter(repository => !!repository) as Repository[];\n\n\t\tconst activeRepositories = new Set(activeRepositoriesList);\n\t\tconst openRepositoriesToDispose = removed\n\t\t\t.map(folder => this.getOpenRepository(folder.uri))\n\t\t\t.filter(r => !!r)\n\t\t\t.filter(r => !activeRepositories.has(r!.repository))\n\t\t\t.filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[];\n\n\t\topenRepositoriesToDispose.forEach(r => r.dispose());\n\t\tthis.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`);\n\t\tawait Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath)));\n\t}\n\n\tprivate onDidChangeConfiguration(): void {\n\t\tconst possibleRepositoryFolders = (workspace.workspaceFolders || [])\n\t\t\t.filter(folder => workspace.getConfiguration('git', folder.uri).get('enabled') === true)\n\t\t\t.filter(folder => !this.getOpenRepository(folder.uri));\n\n\t\tconst openRepositoriesToDispose = this.openRepositories\n\t\t\t.map(repository => ({ repository, root: Uri.file(repository.repository.root) }))\n\t\t\t.filter(({ root }) => workspace.getConfiguration('git', root).get('enabled') !== true)\n\t\t\t.map(({ repository }) => repository);\n\n\t\tthis.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`);\n\t\tpossibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath));\n\t\topenRepositoriesToDispose.forEach(r => r.dispose());\n\t}\n\n\tprivate async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise {\n\t\tif (!workspace.isTrusted) {\n\t\t\tthis.logger.trace('[svte] Workspace is not trusted.');\n\t\t\treturn;\n\t\t}\n\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tthis.logger.trace(`[svte] Scan visible text editors. autoRepositoryDetection=${autoRepositoryDetection}`);\n\n\t\tif (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') {\n\t\t\treturn;\n\t\t}\n\n\t\tawait Promise.all(editors.map(async editor => {\n\t\t\tconst uri = editor.document.uri;\n\n\t\t\tif (uri.scheme !== 'file') {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst repository = this.getRepository(uri);\n\n\t\t\tif (repository) {\n\t\t\t\tthis.logger.trace(`[svte] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.logger.trace(`[svte] Open repository for editor resource ${uri.fsPath}`);\n\t\t\tawait this.openRepository(path.dirname(uri.fsPath));\n\t\t}));\n\t}\n\n\t@sequentialize\n\tasync openRepository(repoPath: string, openIfClosed = false): Promise {\n\t\tthis.logger.trace(`Opening repository: ${repoPath}`);\n\t\tconst existingRepository = await this.getRepositoryExact(repoPath);\n\t\tif (existingRepository) {\n\t\t\tthis.logger.trace(`Repository for path ${repoPath} already exists: ${existingRepository.root})`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst config = workspace.getConfiguration('git', Uri.file(repoPath));\n\t\tconst enabled = config.get('enabled') === true;\n\n\t\tif (!enabled) {\n\t\t\tthis.logger.trace('Git is not enabled');\n\t\t\treturn;\n\t\t}\n\n\t\tif (!workspace.isTrusted) {\n\t\t\t// Check if the folder is a bare repo: if it has a file named HEAD && `rev-parse --show -cdup` is empty\n\t\t\ttry {\n\t\t\t\tfs.accessSync(path.join(repoPath, 'HEAD'), fs.constants.F_OK);\n\t\t\t\tconst result = await this.git.exec(repoPath, ['-C', repoPath, 'rev-parse', '--show-cdup']);\n\t\t\t\tif (result.stderr.trim() === '' && result.stdout.trim() === '') {\n\t\t\t\t\tthis.logger.trace(`Bare repository: ${repoPath}`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// If this throw, we should be good to open the repo (e.g. HEAD doesn't exist)\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tconst { repositoryRoot, unsafeRepositoryMatch } = await this.getRepositoryRoot(repoPath);\n\t\t\tthis.logger.trace(`Repository root for path ${repoPath} is: ${repositoryRoot}`);\n\n\t\t\tconst existingRepository = await this.getRepositoryExact(repositoryRoot);\n\t\t\tif (existingRepository) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} already exists: ${existingRepository.root}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.shouldRepositoryBeIgnored(repositoryRoot)) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} is ignored`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle git repositories that are in parent folders\n\t\t\tconst parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt');\n\t\t\tif (parentRepositoryConfig !== 'always' && this.globalState.get(`parentRepository:${repositoryRoot}`) !== true) {\n\t\t\t\tconst isRepositoryOutsideWorkspace = await this.isRepositoryOutsideWorkspace(repositoryRoot);\n\t\t\t\tif (isRepositoryOutsideWorkspace) {\n\t\t\t\t\tthis.logger.trace(`Repository in parent folder: ${repositoryRoot}`);\n\n\t\t\t\t\tif (!this._parentRepositoriesManager.hasRepository(repositoryRoot)) {\n\t\t\t\t\t\t// Show a notification if the parent repository is opened after the initial scan\n\t\t\t\t\t\tif (this.state === 'initialized' && parentRepositoryConfig === 'prompt') {\n\t\t\t\t\t\t\tthis.showParentRepositoryNotification();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis._parentRepositoriesManager.addRepository(repositoryRoot);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle unsafe repositories\n\t\t\tif (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) {\n\t\t\t\tthis.logger.trace(`Unsafe repository: ${repositoryRoot}`);\n\n\t\t\t\t// Show a notification if the unsafe repository is opened after the initial scan\n\t\t\t\tif (this._state === 'initialized' && !this._unsafeRepositoriesManager.hasRepository(repositoryRoot)) {\n\t\t\t\t\tthis.showUnsafeRepositoryNotification();\n\t\t\t\t}\n\n\t\t\t\tthis._unsafeRepositoriesManager.addRepository(repositoryRoot, unsafeRepositoryMatch[2]);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle repositories that were closed by the user\n\t\t\tif (!openIfClosed && this._closedRepositoriesManager.isRepositoryClosed(repositoryRoot)) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} is closed`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Open repository\n\t\t\tconst dotGit = await this.git.getRepositoryDotGit(repositoryRoot);\n\t\t\tconst repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this, this.globalState, this.logger, this.telemetryReporter);\n\n\t\t\tthis.open(repository);\n\t\t\tthis._closedRepositoriesManager.deleteRepository(repository.root);\n\n\t\t\t// Do not await this, we want SCM\n\t\t\t// to know about the repo asap\n\t\t\trepository.status();\n\t\t} catch (err) {\n\t\t\t// noop\n\t\t\tthis.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${err}`);\n\t\t}\n\t}\n\n\tasync openParentRepository(repoPath: string): Promise {\n\t\tawait this.openRepository(repoPath);\n\t\tthis._parentRepositoriesManager.openRepository(repoPath);\n\t}\n\n\tprivate async getRepositoryRoot(repoPath: string): Promise<{ repositoryRoot: string; unsafeRepositoryMatch: RegExpMatchArray | null }> {\n\t\ttry {\n\t\t\tconst rawRoot = await this.git.getRepositoryRoot(repoPath);\n\n\t\t\t// This can happen whenever `path` has the wrong case sensitivity in case\n\t\t\t// insensitive file systems https://github.com/microsoft/vscode/issues/33498\n\t\t\treturn { repositoryRoot: Uri.file(rawRoot).fsPath, unsafeRepositoryMatch: null };\n\t\t} catch (err) {\n\t\t\t// Handle unsafe repository\n\t\t\tconst unsafeRepositoryMatch = /^fatal: detected dubious ownership in repository at \\'([^']+)\\'[\\s\\S]*git config --global --add safe\\.directory '?([^'\\n]+)'?$/m.exec(err.stderr);\n\t\t\tif (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) {\n\t\t\t\treturn { repositoryRoot: path.normalize(unsafeRepositoryMatch[1]), unsafeRepositoryMatch };\n\t\t\t}\n\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tprivate shouldRepositoryBeIgnored(repositoryRoot: string): boolean {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst ignoredRepos = config.get('ignoredRepositories') || [];\n\n\t\tfor (const ignoredRepo of ignoredRepos) {\n\t\t\tif (path.isAbsolute(ignoredRepo)) {\n\t\t\t\tif (pathEquals(ignoredRepo, repositoryRoot)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (const folder of workspace.workspaceFolders || []) {\n\t\t\t\t\tif (pathEquals(path.join(folder.uri.fsPath, ignoredRepo), repositoryRoot)) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate open(repository: Repository): void {\n\t\tthis.logger.info(`Open repository: ${repository.root}`);\n\n\t\tconst onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed);\n\t\tconst disappearListener = onDidDisappearRepository(() => dispose());\n\t\tconst changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri }));\n\t\tconst originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri }));\n\n\t\tconst shouldDetectSubmodules = workspace\n\t\t\t.getConfiguration('git', Uri.file(repository.root))\n\t\t\t.get('detectSubmodules') as boolean;\n\n\t\tconst submodulesLimit = workspace\n\t\t\t.getConfiguration('git', Uri.file(repository.root))\n\t\t\t.get('detectSubmodulesLimit') as number;\n\n\t\tconst checkForSubmodules = () => {\n\t\t\tif (!shouldDetectSubmodules) {\n\t\t\t\tthis.logger.trace('Automatic detection of git submodules is not enabled.');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (repository.submodules.length > submodulesLimit) {\n\t\t\t\twindow.showWarningMessage(l10n.t('The \"{0}\" repository has {1} submodules which won\\'t be opened automatically. You can still open each one individually by opening a file within.', path.basename(repository.root), repository.submodules.length));\n\t\t\t\tstatusListener.dispose();\n\t\t\t}\n\n\t\t\trepository.submodules\n\t\t\t\t.slice(0, submodulesLimit)\n\t\t\t\t.map(r => path.join(repository.root, r.path))\n\t\t\t\t.forEach(p => {\n\t\t\t\t\tthis.logger.trace(`Opening submodule: '${p}'`);\n\t\t\t\t\tthis.eventuallyScanPossibleGitRepository(p);\n\t\t\t\t});\n\t\t};\n\n\t\tconst updateMergeChanges = () => {\n\t\t\t// set mergeChanges context\n\t\t\tconst mergeChanges: Uri[] = [];\n\t\t\tfor (const { repository } of this.openRepositories.values()) {\n\t\t\t\tfor (const state of repository.mergeGroup.resourceStates) {\n\t\t\t\t\tmergeChanges.push(state.resourceUri);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcommands.executeCommand('setContext', 'git.mergeChanges', mergeChanges);\n\t\t};\n\n\t\tconst statusListener = repository.onDidRunGitStatus(() => {\n\t\t\tcheckForSubmodules();\n\t\t\tupdateMergeChanges();\n\t\t});\n\t\tcheckForSubmodules();\n\n\t\tconst updateOperationInProgressContext = () => {\n\t\t\tlet operationInProgress = false;\n\t\t\tfor (const { repository } of this.openRepositories.values()) {\n\t\t\t\tif (repository.operations.shouldDisableCommands()) {\n\t\t\t\t\toperationInProgress = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcommands.executeCommand('setContext', 'operationInProgress', operationInProgress);\n\t\t};\n\n\t\tconst operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event);\n\t\tconst operationListener = operationEvent(() => updateOperationInProgressContext());\n\t\tupdateOperationInProgressContext();\n\n\t\tconst dispose = () => {\n\t\t\tdisappearListener.dispose();\n\t\t\tchangeListener.dispose();\n\t\t\toriginalResourceChangeListener.dispose();\n\t\t\tstatusListener.dispose();\n\t\t\toperationListener.dispose();\n\t\t\trepository.dispose();\n\n\t\t\tthis.openRepositories = this.openRepositories.filter(e => e !== openRepository);\n\t\t\tthis._onDidCloseRepository.fire(repository);\n\t\t};\n\n\t\tconst openRepository = { repository, dispose };\n\t\tthis.openRepositories.push(openRepository);\n\t\tupdateMergeChanges();\n\t\tthis._onDidOpenRepository.fire(repository);\n\t}\n\n\tclose(repository: Repository): void {\n\t\tconst openRepository = this.getOpenRepository(repository);\n\n\t\tif (!openRepository) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.logger.info(`Close repository: ${repository.root}`);\n\t\tthis._closedRepositoriesManager.addRepository(openRepository.repository.root);\n\n\t\topenRepository.dispose();\n\t}\n\n\tasync pickRepository(): Promise {\n\t\tif (this.openRepositories.length === 0) {\n\t\t\tthrow new Error(l10n.t('There are no available repositories'));\n\t\t}\n\n\t\tconst picks = this.openRepositories.map((e, index) => new RepositoryPick(e.repository, index));\n\t\tconst active = window.activeTextEditor;\n\t\tconst repository = active && this.getRepository(active.document.fileName);\n\t\tconst index = picks.findIndex(pick => pick.repository === repository);\n\n\t\t// Move repository pick containing the active text editor to appear first\n\t\tif (index > -1) {\n\t\t\tpicks.unshift(...picks.splice(index, 1));\n\t\t}\n\n\t\tconst placeHolder = l10n.t('Choose a repository');\n\t\tconst pick = await window.showQuickPick(picks, { placeHolder });\n\n\t\treturn pick && pick.repository;\n\t}\n\n\tgetRepository(sourceControl: SourceControl): Repository | undefined;\n\tgetRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined;\n\tgetRepository(path: string): Repository | undefined;\n\tgetRepository(resource: Uri): Repository | undefined;\n\tgetRepository(hint: any): Repository | undefined {\n\t\tconst liveRepository = this.getOpenRepository(hint);\n\t\treturn liveRepository && liveRepository.repository;\n\t}\n\n\tprivate async getRepositoryExact(repoPath: string): Promise {\n\t\tconst repoPathCanonical = await fs.promises.realpath(repoPath, { encoding: 'utf8' });\n\t\tconst openRepository = this.openRepositories.find(async r => {\n\t\t\tconst rootPathCanonical = await fs.promises.realpath(r.repository.root, { encoding: 'utf8' });\n\t\t\treturn pathEquals(rootPathCanonical, repoPathCanonical);\n\t\t});\n\t\treturn openRepository?.repository;\n\t}\n\n\tprivate getOpenRepository(repository: Repository): OpenRepository | undefined;\n\tprivate getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined;\n\tprivate getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined;\n\tprivate getOpenRepository(path: string): OpenRepository | undefined;\n\tprivate getOpenRepository(resource: Uri): OpenRepository | undefined;\n\tprivate getOpenRepository(hint: any): OpenRepository | undefined {\n\t\tif (!hint) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (hint instanceof Repository) {\n\t\t\treturn this.openRepositories.filter(r => r.repository === hint)[0];\n\t\t}\n\n\t\tif (hint instanceof ApiRepository) {\n\t\t\treturn this.openRepositories.filter(r => r.repository === hint.repository)[0];\n\t\t}\n\n\t\tif (typeof hint === 'string') {\n\t\t\thint = Uri.file(hint);\n\t\t}\n\n\t\tif (hint instanceof Uri) {\n\t\t\tlet resourcePath: string;\n\n\t\t\tif (hint.scheme === 'git') {\n\t\t\t\tresourcePath = fromGitUri(hint).path;\n\t\t\t} else {\n\t\t\t\tresourcePath = hint.fsPath;\n\t\t\t}\n\n\t\t\touter:\n\t\t\tfor (const liveRepository of this.openRepositories.sort((a, b) => b.repository.root.length - a.repository.root.length)) {\n\t\t\t\tif (!isDescendant(liveRepository.repository.root, resourcePath)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tfor (const submodule of liveRepository.repository.submodules) {\n\t\t\t\t\tconst submoduleRoot = path.join(liveRepository.repository.root, submodule.path);\n\n\t\t\t\t\tif (isDescendant(submoduleRoot, resourcePath)) {\n\t\t\t\t\t\tcontinue outer;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tfor (const liveRepository of this.openRepositories) {\n\t\t\tconst repository = liveRepository.repository;\n\n\t\t\tif (hint === repository.sourceControl) {\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\n\t\t\tif (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup || hint === repository.untrackedGroup) {\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n}\n", + "fileName": "./1.tst" + }, + "modified": { + "content": "/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, WorkspaceFolder } from 'vscode';\nimport TelemetryReporter from '@vscode/extension-telemetry';\nimport { Repository, RepositoryState } from './repository';\nimport { memoize, sequentialize, debounce } from './decorators';\nimport { dispose, anyEvent, filterEvent, isDescendant, pathEquals, toDisposable, eventToPromise } from './util';\nimport { Git } from './git';\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport { fromGitUri } from './uri';\nimport { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider } from './api/git';\nimport { Askpass } from './askpass';\nimport { IPushErrorHandlerRegistry } from './pushError';\nimport { ApiRepository } from './api/api1';\nimport { IRemoteSourcePublisherRegistry } from './remotePublisher';\nimport { IPostCommitCommandsProviderRegistry } from './postCommitCommands';\nimport { IBranchProtectionProviderRegistry } from './branchProtection';\n\nclass ClosedRepositoriesManager {\n\n\tprivate _repositories: Set;\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.values()];\n\t}\n\n\tconstructor(private readonly workspaceState: Memento) {\n\t\tthis._repositories = new Set(workspaceState.get('closedRepositories', []));\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string): void {\n\t\tthis._repositories.add(repository);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tisRepositoryClosed(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tthis.workspaceState.update('closedRepositories', [...this._repositories.values()]);\n\t\tcommands.executeCommand('setContext', 'git.closedRepositoryCount', this._repositories.size);\n\t}\n}\n\nclass ParentRepositoriesManager {\n\n\t/**\n\t * Key - normalized path used in user interface\n\t * Value - value indicating whether the repository should be opened\n\t */\n\tprivate _repositories = new Set;\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.values()];\n\t}\n\n\tconstructor(private readonly globalState: Memento) {\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string): void {\n\t\tthis._repositories.add(repository);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\thasRepository(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\topenRepository(repository: string): void {\n\t\tthis.globalState.update(`parentRepository:${repository}`, true);\n\t\tthis.deleteRepository(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tcommands.executeCommand('setContext', 'git.parentRepositoryCount', this._repositories.size);\n\t}\n}\n\nclass UnsafeRepositoriesManager {\n\n\t/**\n\t * Key - normalized path used in user interface\n\t * Value - path extracted from the output of the `git status` command\n\t * used when calling `git config --global --add safe.directory`\n\t */\n\tprivate _repositories = new Map();\n\tget repositories(): string[] {\n\t\treturn [...this._repositories.keys()];\n\t}\n\n\tconstructor() {\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\taddRepository(repository: string, path: string): void {\n\t\tthis._repositories.set(repository, path);\n\t\tthis.onDidChangeRepositories();\n\t}\n\n\tdeleteRepository(repository: string): boolean {\n\t\tconst result = this._repositories.delete(repository);\n\t\tif (result) {\n\t\t\tthis.onDidChangeRepositories();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tgetRepositoryPath(repository: string): string | undefined {\n\t\treturn this._repositories.get(repository);\n\t}\n\n\thasRepository(repository: string): boolean {\n\t\treturn this._repositories.has(repository);\n\t}\n\n\tprivate onDidChangeRepositories(): void {\n\t\tcommands.executeCommand('setContext', 'git.unsafeRepositoryCount', this._repositories.size);\n\t}\n}\n\nexport class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry {\n\n\tprivate _onDidOpenRepository = new EventEmitter();\n\treadonly onDidOpenRepository: Event = this._onDidOpenRepository.event;\n\n\tprivate _onDidCloseRepository = new EventEmitter();\n\treadonly onDidCloseRepository: Event = this._onDidCloseRepository.event;\n\n\tprivate _onDidChangeRepository = new EventEmitter();\n\treadonly onDidChangeRepository: Event = this._onDidChangeRepository.event;\n\n\tprivate _onDidChangeOriginalResource = new EventEmitter();\n\treadonly onDidChangeOriginalResource: Event = this._onDidChangeOriginalResource.event;\n\n\tprivate openRepositories: OpenRepository[] = [];\n\tget repositories(): Repository[] { return this.openRepositories.map(r => r.repository); }\n\n\tprivate possibleGitRepositoryPaths = new Set();\n\n\tprivate _onDidChangeState = new EventEmitter();\n\treadonly onDidChangeState = this._onDidChangeState.event;\n\n\tprivate _onDidPublish = new EventEmitter();\n\treadonly onDidPublish = this._onDidPublish.event;\n\n\tfirePublishEvent(repository: Repository, branch?: string) {\n\t\tthis._onDidPublish.fire({ repository: new ApiRepository(repository), branch: branch });\n\t}\n\n\tprivate _state: State = 'uninitialized';\n\tget state(): State { return this._state; }\n\n\tsetState(state: State): void {\n\t\tthis._state = state;\n\t\tthis._onDidChangeState.fire(state);\n\t\tcommands.executeCommand('setContext', 'git.state', state);\n\t}\n\n\t@memoize\n\tget isInitialized(): Promise {\n\t\tif (this._state === 'initialized') {\n\t\t\treturn Promise.resolve();\n\t\t}\n\n\t\treturn eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized')) as Promise;\n\t}\n\n\tprivate remoteSourcePublishers = new Set();\n\n\tprivate _onDidAddRemoteSourcePublisher = new EventEmitter();\n\treadonly onDidAddRemoteSourcePublisher = this._onDidAddRemoteSourcePublisher.event;\n\n\tprivate _onDidRemoveRemoteSourcePublisher = new EventEmitter();\n\treadonly onDidRemoveRemoteSourcePublisher = this._onDidRemoveRemoteSourcePublisher.event;\n\n\tprivate postCommitCommandsProviders = new Set();\n\n\tprivate _onDidChangePostCommitCommandsProviders = new EventEmitter();\n\treadonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event;\n\n\tprivate branchProtectionProviders = new Map>();\n\n\tprivate _onDidChangeBranchProtectionProviders = new EventEmitter();\n\treadonly onDidChangeBranchProtectionProviders = this._onDidChangeBranchProtectionProviders.event;\n\n\tprivate pushErrorHandlers = new Set();\n\n\tprivate _unsafeRepositoriesManager: UnsafeRepositoriesManager;\n\tget unsafeRepositories(): string[] {\n\t\treturn this._unsafeRepositoriesManager.repositories;\n\t}\n\n\tprivate _parentRepositoriesManager: ParentRepositoriesManager;\n\tget parentRepositories(): string[] {\n\t\treturn this._parentRepositoriesManager.repositories;\n\t}\n\n\tprivate _closedRepositoriesManager: ClosedRepositoriesManager;\n\tget closedRepositories(): string[] {\n\t\treturn [...this._closedRepositoriesManager.repositories];\n\t}\n\n\t/**\n\t * We maintain a map containing both the path and the canonical path of the\n\t * workspace folders. We are doing this as `git.exe` expands the symbolic links\n\t * while there are scenarios in which VS Code does not.\n\t *\n\t * Key - path of the workspace folder\n\t * Value - canonical path of the workspace folder\n\t */\n\tprivate _workspaceFolders = new Map();\n\n\tprivate disposables: Disposable[] = [];\n\n\tconstructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) {\n\t\t// Repositories managers\n\t\tthis._closedRepositoriesManager = new ClosedRepositoriesManager(workspaceState);\n\t\tthis._parentRepositoriesManager = new ParentRepositoriesManager(globalState);\n\t\tthis._unsafeRepositoriesManager = new UnsafeRepositoriesManager();\n\n\t\tworkspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);\n\t\twindow.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);\n\t\tworkspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables);\n\n\t\tconst fsWatcher = workspace.createFileSystemWatcher('**');\n\t\tthis.disposables.push(fsWatcher);\n\n\t\tconst onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);\n\t\tconst onGitRepositoryChange = filterEvent(onWorkspaceChange, uri => /\\/\\.git/.test(uri.path));\n\t\tconst onPossibleGitRepositoryChange = filterEvent(onGitRepositoryChange, uri => !this.getRepository(uri));\n\t\tonPossibleGitRepositoryChange(this.onPossibleGitRepositoryChange, this, this.disposables);\n\n\t\tthis.setState('uninitialized');\n\t\tthis.doInitialScan().finally(() => this.setState('initialized'));\n\t}\n\n\tprivate async doInitialScan(): Promise {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tconst parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt');\n\n\t\t// Initial repository scan function\n\t\tconst initialScanFn = () => Promise.all([\n\t\t\tthis.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }),\n\t\t\tthis.onDidChangeVisibleTextEditors(window.visibleTextEditors),\n\t\t\tthis.scanWorkspaceFolders()\n\t\t]);\n\n\t\tif (config.get('showProgress', true)) {\n\t\t\tawait window.withProgress({ location: ProgressLocation.SourceControl }, initialScanFn);\n\t\t} else {\n\t\t\tawait initialScanFn();\n\t\t}\n\n\t\tif (this.parentRepositories.length !== 0 &&\n\t\t\tparentRepositoryConfig === 'prompt') {\n\t\t\t// Parent repositories notification\n\t\t\tthis.showParentRepositoryNotification();\n\t\t} else if (this.unsafeRepositories.length !== 0) {\n\t\t\t// Unsafe repositories notification\n\t\t\tthis.showUnsafeRepositoryNotification();\n\t\t}\n\n\t\t/* __GDPR__\n\t\t\t\"git.repositoryInitialScan\" : {\n\t\t\t\t\"owner\": \"lszomoru\",\n\t\t\t\t\"autoRepositoryDetection\": { \"classification\": \"SystemMetaData\", \"purpose\": \"FeatureInsight\", \"comment\": \"Setting that controls the initial repository scan\" },\n\t\t\t\t\"repositoryCount\": { \"classification\": \"SystemMetaData\", \"purpose\": \"FeatureInsight\", \"isMeasurement\": true, \"comment\": \"Number of repositories opened during initial repository scan\" }\n\t\t\t}\n\t\t*/\n\t\tthis.telemetryReporter.sendTelemetryEvent('git.repositoryInitialScan', { autoRepositoryDetection: String(autoRepositoryDetection) }, { repositoryCount: this.openRepositories.length });\n\t}\n\n\t/**\n\t * Scans each workspace folder, looking for git repositories. By\n\t * default it scans one level deep but that can be changed using\n\t * the git.repositoryScanMaxDepth setting.\n\t */\n\tprivate async scanWorkspaceFolders(): Promise {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tthis.logger.trace(`[swsf] Scan workspace sub folders. autoRepositoryDetection=${autoRepositoryDetection}`);\n\n\t\tif (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') {\n\t\t\treturn;\n\t\t}\n\n\t\tawait Promise.all((workspace.workspaceFolders || []).map(async folder => {\n\t\t\tconst root = folder.uri.fsPath;\n\t\t\tthis.logger.trace(`[swsf] Workspace folder: ${root}`);\n\n\t\t\t// Workspace folder children\n\t\t\tconst repositoryScanMaxDepth = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanMaxDepth', 1);\n\t\t\tconst repositoryScanIgnoredFolders = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanIgnoredFolders', []);\n\n\t\t\tconst subfolders = new Set(await this.traverseWorkspaceFolder(root, repositoryScanMaxDepth, repositoryScanIgnoredFolders));\n\n\t\t\t// Repository scan folders\n\t\t\tconst scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || [];\n\t\t\tthis.logger.trace(`[swsf] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`);\n\n\t\t\tfor (const scanPath of scanPaths) {\n\t\t\t\tif (scanPath === '.git') {\n\t\t\t\t\tthis.logger.trace('[swsf] \\'.git\\' not supported in \\'git.scanRepositories\\' setting.');\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (path.isAbsolute(scanPath)) {\n\t\t\t\t\tconst notSupportedMessage = l10n.t('Absolute paths not supported in \"git.scanRepositories\" setting.');\n\t\t\t\t\tthis.logger.warn(notSupportedMessage);\n\t\t\t\t\tconsole.warn(notSupportedMessage);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tsubfolders.add(path.join(root, scanPath));\n\t\t\t}\n\n\t\t\tthis.logger.trace(`[swsf] Workspace scan sub folders: [${[...subfolders].join(', ')}]`);\n\t\t\tawait Promise.all([...subfolders].map(f => this.openRepository(f)));\n\t\t}));\n\t}\n\n\tprivate async traverseWorkspaceFolder(workspaceFolder: string, maxDepth: number, repositoryScanIgnoredFolders: string[]): Promise {\n\t\tconst result: string[] = [];\n\t\tconst foldersToTravers = [{ path: workspaceFolder, depth: 0 }];\n\n\t\twhile (foldersToTravers.length > 0) {\n\t\t\tconst currentFolder = foldersToTravers.shift()!;\n\n\t\t\tif (currentFolder.depth < maxDepth || maxDepth === -1) {\n\t\t\t\tconst children = await fs.promises.readdir(currentFolder.path, { withFileTypes: true });\n\t\t\t\tconst childrenFolders = children\n\t\t\t\t\t.filter(dirent =>\n\t\t\t\t\t\tdirent.isDirectory() && dirent.name !== '.git' &&\n\t\t\t\t\t\t!repositoryScanIgnoredFolders.find(f => pathEquals(dirent.name, f)))\n\t\t\t\t\t.map(dirent => path.join(currentFolder.path, dirent.name));\n\n\t\t\t\tresult.push(...childrenFolders);\n\t\t\t\tfoldersToTravers.push(...childrenFolders.map(folder => {\n\t\t\t\t\treturn { path: folder, depth: currentFolder.depth + 1 };\n\t\t\t\t}));\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate onPossibleGitRepositoryChange(uri: Uri): void {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\n\t\tif (autoRepositoryDetection === false) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.eventuallyScanPossibleGitRepository(uri.fsPath.replace(/\\.git.*$/, ''));\n\t}\n\n\tprivate eventuallyScanPossibleGitRepository(path: string) {\n\t\tthis.possibleGitRepositoryPaths.add(path);\n\t\tthis.eventuallyScanPossibleGitRepositories();\n\t}\n\n\t@debounce(500)\n\tprivate eventuallyScanPossibleGitRepositories(): void {\n\t\tfor (const path of this.possibleGitRepositoryPaths) {\n\t\t\tthis.openRepository(path);\n\t\t}\n\n\t\tthis.possibleGitRepositoryPaths.clear();\n\t}\n\n\tprivate async onDidChangeWorkspaceFolders({ added, removed }: WorkspaceFoldersChangeEvent): Promise {\n\t\tconst possibleRepositoryFolders = added\n\t\t\t.filter(folder => !this.getOpenRepository(folder.uri));\n\n\t\tconst activeRepositoriesList = window.visibleTextEditors\n\t\t\t.map(editor => this.getRepository(editor.document.uri))\n\t\t\t.filter(repository => !!repository) as Repository[];\n\n\t\tconst activeRepositories = new Set(activeRepositoriesList);\n\t\tconst openRepositoriesToDispose = removed\n\t\t\t.map(folder => this.getOpenRepository(folder.uri))\n\t\t\t.filter(r => !!r)\n\t\t\t.filter(r => !activeRepositories.has(r!.repository))\n\t\t\t.filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[];\n\n\t\topenRepositoriesToDispose.forEach(r => r.dispose());\n\t\tthis.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`);\n\t\tawait Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath)));\n\t}\n\n\tprivate onDidChangeConfiguration(): void {\n\t\tconst possibleRepositoryFolders = (workspace.workspaceFolders || [])\n\t\t\t.filter(folder => workspace.getConfiguration('git', folder.uri).get('enabled') === true)\n\t\t\t.filter(folder => !this.getOpenRepository(folder.uri));\n\n\t\tconst openRepositoriesToDispose = this.openRepositories\n\t\t\t.map(repository => ({ repository, root: Uri.file(repository.repository.root) }))\n\t\t\t.filter(({ root }) => workspace.getConfiguration('git', root).get('enabled') !== true)\n\t\t\t.map(({ repository }) => repository);\n\n\t\tthis.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`);\n\t\tpossibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath));\n\t\topenRepositoriesToDispose.forEach(r => r.dispose());\n\t}\n\n\tprivate async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise {\n\t\tif (!workspace.isTrusted) {\n\t\t\tthis.logger.trace('[svte] Workspace is not trusted.');\n\t\t\treturn;\n\t\t}\n\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst autoRepositoryDetection = config.get('autoRepositoryDetection');\n\t\tthis.logger.trace(`[svte] Scan visible text editors. autoRepositoryDetection=${autoRepositoryDetection}`);\n\n\t\tif (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') {\n\t\t\treturn;\n\t\t}\n\n\t\tawait Promise.all(editors.map(async editor => {\n\t\t\tconst uri = editor.document.uri;\n\n\t\t\tif (uri.scheme !== 'file') {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst repository = this.getRepository(uri);\n\n\t\t\tif (repository) {\n\t\t\t\tthis.logger.trace(`[svte] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.logger.trace(`[svte] Open repository for editor resource ${uri.fsPath}`);\n\t\t\tawait this.openRepository(path.dirname(uri.fsPath));\n\t\t}));\n\t}\n\n\t@sequentialize\n\tasync openRepository(repoPath: string, openIfClosed = false): Promise {\n\t\tthis.logger.trace(`Opening repository: ${repoPath}`);\n\t\tconst existingRepository = await this.getRepositoryExact(repoPath);\n\t\tif (existingRepository) {\n\t\t\tthis.logger.trace(`Repository for path ${repoPath} already exists: ${existingRepository.root})`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst config = workspace.getConfiguration('git', Uri.file(repoPath));\n\t\tconst enabled = config.get('enabled') === true;\n\n\t\tif (!enabled) {\n\t\t\tthis.logger.trace('Git is not enabled');\n\t\t\treturn;\n\t\t}\n\n\t\tif (!workspace.isTrusted) {\n\t\t\t// Check if the folder is a bare repo: if it has a file named HEAD && `rev-parse --show -cdup` is empty\n\t\t\ttry {\n\t\t\t\tfs.accessSync(path.join(repoPath, 'HEAD'), fs.constants.F_OK);\n\t\t\t\tconst result = await this.git.exec(repoPath, ['-C', repoPath, 'rev-parse', '--show-cdup']);\n\t\t\t\tif (result.stderr.trim() === '' && result.stdout.trim() === '') {\n\t\t\t\t\tthis.logger.trace(`Bare repository: ${repoPath}`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// If this throw, we should be good to open the repo (e.g. HEAD doesn't exist)\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tconst { repositoryRoot, unsafeRepositoryMatch } = await this.getRepositoryRoot(repoPath);\n\t\t\tthis.logger.trace(`Repository root for path ${repoPath} is: ${repositoryRoot}`);\n\n\t\t\tconst existingRepository = await this.getRepositoryExact(repositoryRoot);\n\t\t\tif (existingRepository) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} already exists: ${existingRepository.root}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.shouldRepositoryBeIgnored(repositoryRoot)) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} is ignored`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle git repositories that are in parent folders\n\t\t\tconst parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt');\n\t\t\tif (parentRepositoryConfig !== 'always' && this.globalState.get(`parentRepository:${repositoryRoot}`) !== true) {\n\t\t\t\tconst isRepositoryOutsideWorkspace = await this.isRepositoryOutsideWorkspace(repositoryRoot);\n\t\t\t\tif (isRepositoryOutsideWorkspace) {\n\t\t\t\t\tthis.logger.trace(`Repository in parent folder: ${repositoryRoot}`);\n\n\t\t\t\t\tif (!this._parentRepositoriesManager.hasRepository(repositoryRoot)) {\n\t\t\t\t\t\t// Show a notification if the parent repository is opened after the initial scan\n\t\t\t\t\t\tif (this.state === 'initialized' && parentRepositoryConfig === 'prompt') {\n\t\t\t\t\t\t\tthis.showParentRepositoryNotification();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis._parentRepositoriesManager.addRepository(repositoryRoot);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle unsafe repositories\n\t\t\tif (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) {\n\t\t\t\tthis.logger.trace(`Unsafe repository: ${repositoryRoot}`);\n\n\t\t\t\t// Show a notification if the unsafe repository is opened after the initial scan\n\t\t\t\tif (this._state === 'initialized' && !this._unsafeRepositoriesManager.hasRepository(repositoryRoot)) {\n\t\t\t\t\tthis.showUnsafeRepositoryNotification();\n\t\t\t\t}\n\n\t\t\t\tthis._unsafeRepositoriesManager.addRepository(repositoryRoot, unsafeRepositoryMatch[2]);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle repositories that were closed by the user\n\t\t\tif (!openIfClosed && this._closedRepositoriesManager.isRepositoryClosed(repositoryRoot)) {\n\t\t\t\tthis.logger.trace(`Repository for path ${repositoryRoot} is closed`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Open repository\n\t\t\tconst dotGit = await this.git.getRepositoryDotGit(repositoryRoot);\n\t\t\tconst repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this, this.globalState, this.logger, this.telemetryReporter);\n\n\t\t\tthis.open(repository);\n\t\t\tthis._closedRepositoriesManager.deleteRepository(repository.root);\n\n\t\t\t// Do not await this, we want SCM\n\t\t\t// to know about the repo asap\n\t\t\trepository.status();\n\t\t} catch (err) {\n\t\t\t// noop\n\t\t\tthis.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${err}`);\n\t\t}\n\t}\n\n\tasync openParentRepository(repoPath: string): Promise {\n\t\tawait this.openRepository(repoPath);\n\t\tthis._parentRepositoriesManager.openRepository(repoPath);\n\t}\n\n\tprivate async getRepositoryRoot(repoPath: string): Promise<{ repositoryRoot: string; unsafeRepositoryMatch: RegExpMatchArray | null }> {\n\t\ttry {\n\t\t\tconst rawRoot = await this.git.getRepositoryRoot(repoPath);\n\n\t\t\t// This can happen whenever `path` has the wrong case sensitivity in case\n\t\t\t// insensitive file systems https://github.com/microsoft/vscode/issues/33498\n\t\t\treturn { repositoryRoot: Uri.file(rawRoot).fsPath, unsafeRepositoryMatch: null };\n\t\t} catch (err) {\n\t\t\t// Handle unsafe repository\n\t\t\tconst unsafeRepositoryMatch = /^fatal: detected dubious ownership in repository at \\'([^']+)\\'[\\s\\S]*git config --global --add safe\\.directory '?([^'\\n]+)'?$/m.exec(err.stderr);\n\t\t\tif (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) {\n\t\t\t\treturn { repositoryRoot: path.normalize(unsafeRepositoryMatch[1]), unsafeRepositoryMatch };\n\t\t\t}\n\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tprivate shouldRepositoryBeIgnored(repositoryRoot: string): boolean {\n\t\tconst config = workspace.getConfiguration('git');\n\t\tconst ignoredRepos = config.get('ignoredRepositories') || [];\n\n\t\tfor (const ignoredRepo of ignoredRepos) {\n\t\t\tif (path.isAbsolute(ignoredRepo)) {\n\t\t\t\tif (pathEquals(ignoredRepo, repositoryRoot)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (const folder of workspace.workspaceFolders || []) {\n\t\t\t\t\tif (pathEquals(path.join(folder.uri.fsPath, ignoredRepo), repositoryRoot)) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate open(repository: Repository): void {\n\t\tthis.logger.info(`Open repository: ${repository.root}`);\n\n\t\tconst onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed);\n\t\tconst disappearListener = onDidDisappearRepository(() => dispose());\n\t\tconst changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri }));\n\t\tconst originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri }));\n\n\t\tconst shouldDetectSubmodules = workspace\n\t\t\t.getConfiguration('git', Uri.file(repository.root))\n\t\t\t.get('detectSubmodules') as boolean;\n\n\t\tconst submodulesLimit = workspace\n\t\t\t.getConfiguration('git', Uri.file(repository.root))\n\t\t\t.get('detectSubmodulesLimit') as number;\n\n\t\tconst checkForSubmodules = () => {\n\t\t\tif (!shouldDetectSubmodules) {\n\t\t\t\tthis.logger.trace('Automatic detection of git submodules is not enabled.');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (repository.submodules.length > submodulesLimit) {\n\t\t\t\twindow.showWarningMessage(l10n.t('The \"{0}\" repository has {1} submodules which won\\'t be opened automatically. You can still open each one individually by opening a file within.', path.basename(repository.root), repository.submodules.length));\n\t\t\t\tstatusListener.dispose();\n\t\t\t}\n\n\t\t\trepository.submodules\n\t\t\t\t.slice(0, submodulesLimit)\n\t\t\t\t.map(r => path.join(repository.root, r.path))\n\t\t\t\t.forEach(p => {\n\t\t\t\t\tthis.logger.trace(`Opening submodule: '${p}'`);\n\t\t\t\t\tthis.eventuallyScanPossibleGitRepository(p);\n\t\t\t\t});\n\t\t};\n\n\t\tconst updateMergeChanges = () => {\n\t\t\t// set mergeChanges context\n\t\t\tconst mergeChanges: Uri[] = [];\n\t\t\tfor (const { repository } of this.openRepositories.values()) {\n\t\t\t\tfor (const state of repository.mergeGroup.resourceStates) {\n\t\t\t\t\tmergeChanges.push(state.resourceUri);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcommands.executeCommand('setContext', 'git.mergeChanges', mergeChanges);\n\t\t};\n\n\t\tconst statusListener = repository.onDidRunGitStatus(() => {\n\t\t\tcheckForSubmodules();\n\t\t\tupdateMergeChanges();\n\t\t});\n\t\tcheckForSubmodules();\n\n\t\tconst updateOperationInProgressContext = () => {\n\t\t\tlet operationInProgress = false;\n\t\t\tfor (const { repository } of this.openRepositories.values()) {\n\t\t\t\tif (repository.operations.shouldDisableCommands()) {\n\t\t\t\t\toperationInProgress = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcommands.executeCommand('setContext', 'operationInProgress', operationInProgress);\n\t\t};\n\n\t\tconst operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event);\n\t\tconst operationListener = operationEvent(() => updateOperationInProgressContext());\n\t\tupdateOperationInProgressContext();\n\n\t\tconst dispose = () => {\n\t\t\tdisappearListener.dispose();\n\t\t\tchangeListener.dispose();\n\t\t\toriginalResourceChangeListener.dispose();\n\t\t\tstatusListener.dispose();\n\t\t\toperationListener.dispose();\n\t\t\trepository.dispose();\n\n\t\t\tthis.openRepositories = this.openRepositories.filter(e => e !== openRepository);\n\t\t\tthis._onDidCloseRepository.fire(repository);\n\t\t};\n\n\t\tconst openRepository = { repository, dispose };\n\t\tthis.openRepositories.push(openRepository);\n\t\tupdateMergeChanges();\n\t\tthis._onDidOpenRepository.fire(repository);\n\t}\n\n\tclose(repository: Repository): void {\n\t\tconst openRepository = this.getOpenRepository(repository);\n\n\t\tif (!openRepository) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.logger.info(`Close repository: ${repository.root}`);\n\t\tthis._closedRepositoriesManager.addRepository(openRepository.repository.root);\n\n\t\topenRepository.dispose();\n\t}\n\n\tasync pickRepository(): Promise {\n\t\tif (this.openRepositories.length === 0) {\n\t\t\tthrow new Error(l10n.t('There are no available repositories'));\n\t\t}\n\n\t\tconst picks = this.openRepositories.map((e, index) => new RepositoryPick(e.repository, index));\n\t\tconst active = window.activeTextEditor;\n\t\tconst repository = active && this.getRepository(active.document.fileName);\n\t\tconst index = picks.findIndex(pick => pick.repository === repository);\n\n\t\t// Move repository pick containing the active text editor to appear first\n\t\tif (index > -1) {\n\t\t\tpicks.unshift(...picks.splice(index, 1));\n\t\t}\n\n\t\tconst placeHolder = l10n.t('Choose a repository');\n\t\tconst pick = await window.showQuickPick(picks, { placeHolder });\n\n\t\treturn pick && pick.repository;\n\t}\n\n\tgetRepository(sourceControl: SourceControl): Repository | undefined;\n\tgetRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined;\n\tgetRepository(path: string): Repository | undefined;\n\tgetRepository(resource: Uri): Repository | undefined;\n\tgetRepository(hint: any): Repository | undefined {\n\t\tconst liveRepository = this.getOpenRepository(hint);\n\t\treturn liveRepository && liveRepository.repository;\n\t}\n\n\tprivate async getRepositoryExact(repoPath: string): Promise {\n\t\tconst repoPathCanonical = await fs.promises.realpath(repoPath, { encoding: 'utf8' });\n\n\t\tfor (const openRepository of this.openRepositories) {\n\t\t\tconst rootPathCanonical = await fs.promises.realpath(openRepository.repository.root, { encoding: 'utf8' });\n\t\t\tif (pathEquals(rootPathCanonical, repoPathCanonical)) {\n\t\t\t\treturn openRepository.repository;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate getOpenRepository(repository: Repository): OpenRepository | undefined;\n\tprivate getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined;\n\tprivate getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined;\n\tprivate getOpenRepository(path: string): OpenRepository | undefined;\n\tprivate getOpenRepository(resource: Uri): OpenRepository | undefined;\n\tprivate getOpenRepository(hint: any): OpenRepository | undefined {\n\t\tif (!hint) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (hint instanceof Repository) {\n\t\t\treturn this.openRepositories.filter(r => r.repository === hint)[0];\n\t\t}\n\n\t\tif (hint instanceof ApiRepository) {\n\t\t\treturn this.openRepositories.filter(r => r.repository === hint.repository)[0];\n\t\t}\n\n\t\tif (typeof hint === 'string') {\n\t\t\thint = Uri.file(hint);\n\t\t}\n\n\t\tif (hint instanceof Uri) {\n\t\t\tlet resourcePath: string;\n\n\t\t\tif (hint.scheme === 'git') {\n\t\t\t\tresourcePath = fromGitUri(hint).path;\n\t\t\t} else {\n\t\t\t\tresourcePath = hint.fsPath;\n\t\t\t}\n\n\t\t\touter:\n\t\t\tfor (const liveRepository of this.openRepositories.sort((a, b) => b.repository.root.length - a.repository.root.length)) {\n\t\t\t\tif (!isDescendant(liveRepository.repository.root, resourcePath)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tfor (const submodule of liveRepository.repository.submodules) {\n\t\t\t\t\tconst submoduleRoot = path.join(liveRepository.repository.root, submodule.path);\n\n\t\t\t\t\tif (isDescendant(submoduleRoot, resourcePath)) {\n\t\t\t\t\t\tcontinue outer;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tfor (const liveRepository of this.openRepositories) {\n\t\t\tconst repository = liveRepository.repository;\n\n\t\t\tif (hint === repository.sourceControl) {\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\n\t\t\tif (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup || hint === repository.untrackedGroup) {\n\t\t\t\treturn liveRepository;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tgetRepositoryForSubmodule(submoduleUri: Uri): Repository | undefined {\n\t\tfor (const repository of this.repositories) {\n\t\t\tfor (const submodule of repository.submodules) {\n\t\t\t\tconst submodulePath = path.join(repository.root, submodule.path);\n\n\t\t\t\tif (submodulePath === submoduleUri.fsPath) {\n\t\t\t\t\treturn repository;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[742,747)", + "modifiedRange": "[742,751)", + "innerChanges": [ + { + "originalRange": "[742,3 -> 742,3]", + "modifiedRange": "[743,3 -> 743,8]" + }, + { + "originalRange": "[742,24 -> 742,25]", + "modifiedRange": "[743,29 -> 743,31]" + }, + { + "originalRange": "[742,47 -> 742,63]", + "modifiedRange": "[743,53 -> 743,54]" + }, + { + "originalRange": "[743,57 -> 743,58]", + "modifiedRange": "[744,57 -> 744,71]" + }, + { + "originalRange": "[744,4 -> 744,11]", + "modifiedRange": "[745,4 -> 745,8]" + }, + { + "originalRange": "[744,59 -> 745,6]", + "modifiedRange": "[745,56 -> 745,59]" + }, + { + "originalRange": "[746,24 -> 746,25]", + "modifiedRange": "[746,26 -> 746,26]" + }, + { + "originalRange": "[746,37 -> 746,37]", + "modifiedRange": "[747,4 -> 750,20]" + } + ] + }, + { + "originalRange": "[815,815)", + "modifiedRange": "[819,832)", + "innerChanges": null + } + ] +} \ No newline at end of file From b912246257d36a43bd0bf50a2658322ccaa6a091 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 11 Jul 2023 14:26:34 -0700 Subject: [PATCH 027/826] fix when clause --- .../terminal.accessibility.contribution.ts | 2 +- .../browser/terminalAccessibleBuffer.ts | 22 +++++++++---------- .../browser/terminalAccessibleWidget.ts | 1 + 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index 50d519feb79..fa198f5464f 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -92,7 +92,7 @@ registerTerminalAction({ { primary: KeyMod.Shift | KeyCode.Tab, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, terminalTabFocusContextKey, TerminalContextKeys.accessibleBufferFocus.negate()) + when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, ContextKeyExpr.or(terminalTabFocusContextKey, TerminalContextKeys.accessibleBufferFocus.negate())) } ], run: async (c) => { diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts index 2b58c359253..48cc9dec78d 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts @@ -4,23 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; -import type { Terminal } from 'xterm'; -import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; -import { IEditorViewState } from 'vs/editor/common/editorCommon'; -import { TerminalAccessibleWidget } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ITerminalLogService } from 'vs/platform/terminal/common/terminal'; +import { ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; +import { TerminalAccessibleWidget } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget'; +import type { Terminal } from 'xterm'; export const enum NavigationType { Next = 'next', diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts index d1316db9bee..6754d0a2635 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts @@ -118,6 +118,7 @@ export abstract class TerminalAccessibleWidget extends DisposableStore { this._terminalService.setActiveInstance(this._instance as ITerminalInstance); this._xtermElement.classList.add(ClassName.Hide); })); + this.layout(); } registerListeners(): void { From fe98d08c7e9ebda9a3f957195579d971e6daabcc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 11 Jul 2023 14:28:24 -0700 Subject: [PATCH 028/826] improve name --- src/vs/platform/terminal/common/terminal.ts | 2 +- src/vs/workbench/browser/parts/editor/tabFocus.ts | 4 ++-- .../browser/terminal.accessibility.contribution.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index a73f7a7f9fa..1f019a69053 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -17,7 +17,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import type * as performance from 'vs/base/common/performance'; import { ILogService } from 'vs/platform/log/common/log'; -export const terminalTabFocusContextKey = new RawContextKey('terminalTabFocusMode', false, true); +export const terminalTabFocusModeContextKey = new RawContextKey('terminalTabFocusMode', false, true); export const enum TerminalSettingPrefix { DefaultProfile = 'terminal.integrated.defaultProfile.', diff --git a/src/vs/workbench/browser/parts/editor/tabFocus.ts b/src/vs/workbench/browser/parts/editor/tabFocus.ts index 3d770eb8857..52f2e0d7199 100644 --- a/src/vs/workbench/browser/parts/editor/tabFocus.ts +++ b/src/vs/workbench/browser/parts/editor/tabFocus.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { TabFocusContext, TabFocus } from 'vs/editor/browser/config/tabFocus'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { TerminalSettingId, terminalTabFocusContextKey } from 'vs/platform/terminal/common/terminal'; +import { TerminalSettingId, terminalTabFocusModeContextKey } from 'vs/platform/terminal/common/terminal'; export const editorTabFocusContextKey = new RawContextKey('editorTabFocusMode', false, true); @@ -26,7 +26,7 @@ export class TabFocusMode extends Disposable { super(); this._editorContext = editorTabFocusContextKey.bindTo(contextKeyService); - this._terminalContext = terminalTabFocusContextKey.bindTo(contextKeyService); + this._terminalContext = terminalTabFocusModeContextKey.bindTo(contextKeyService); const editorConfig: boolean = configurationService.getValue('editor.tabFocusMode'); const terminalConfig: boolean = configurationService.getValue(TerminalSettingId.TabFocusMode) ?? editorConfig; this._editorContext.set(editorConfig); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index fa198f5464f..acff267d59c 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -11,7 +11,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { terminalTabFocusContextKey } from 'vs/platform/terminal/common/terminal'; +import { terminalTabFocusModeContextKey } from 'vs/platform/terminal/common/terminal'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -92,7 +92,7 @@ registerTerminalAction({ { primary: KeyMod.Shift | KeyCode.Tab, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, ContextKeyExpr.or(terminalTabFocusContextKey, TerminalContextKeys.accessibleBufferFocus.negate())) + when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, ContextKeyExpr.or(terminalTabFocusModeContextKey, TerminalContextKeys.accessibleBufferFocus.negate())) } ], run: async (c) => { From 018a8d2ebd39924b5085b8ec5b2d30d95068cabe Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 11 Jul 2023 14:29:14 -0700 Subject: [PATCH 029/826] revert unneeded change --- .../accessibility/browser/terminalAccessibleWidget.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts index 6754d0a2635..d1316db9bee 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts @@ -118,7 +118,6 @@ export abstract class TerminalAccessibleWidget extends DisposableStore { this._terminalService.setActiveInstance(this._instance as ITerminalInstance); this._xtermElement.classList.add(ClassName.Hide); })); - this.layout(); } registerListeners(): void { From fbd39c1ac25434c6bb12caf1c157b273bd629f15 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Mon, 10 Jul 2023 09:35:25 -0700 Subject: [PATCH 030/826] moved optimization to model --- .../view/renderers/backLayerWebView.ts | 14 ++--- .../model/notebookCellOutputTextModel.ts | 35 ++++++++++++- .../common/model/notebookCellTextModel.ts | 33 +----------- .../test/browser/notebookTextModel.test.ts | 52 +++++++++++++++++++ 4 files changed, 91 insertions(+), 43 deletions(-) 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 06a4054a668..0fdc7c3c612 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -1413,15 +1413,11 @@ export class BackLayerWebView extends Themable { } // create new output - const createOutput = () => { - const { message, renderer, transfer: transferable } = this._createOutputCreationMessage(cellInfo, content, cellTop, offset, false, false); - this._sendMessageToWebview(message, transferable); - this.insetMapping.set(content.source, { outputId: message.outputId, versionId: content.source.model.versionId, cellInfo: cellInfo, renderer, cachedCreation: message }); - this.hiddenInsetMapping.delete(content.source); - this.reversedInsetMapping.set(message.outputId, content.source); - }; - - createOutput(); + const { message, renderer, transfer: transferable } = this._createOutputCreationMessage(cellInfo, content, cellTop, offset, false, false); + this._sendMessageToWebview(message, transferable); + this.insetMapping.set(content.source, { outputId: message.outputId, versionId: content.source.model.versionId, cellInfo: cellInfo, renderer, cachedCreation: message }); + this.hiddenInsetMapping.delete(content.source); + this.reversedInsetMapping.set(message.outputId, content.source); } private createMetadata(output: ICellOutput, mimeType: string) { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts index 5d7ac8195b6..3c9e16931fd 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts @@ -5,7 +5,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ICellOutput, IOutputDto, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellOutput, IOutputDto, IOutputItemDto, compressOutputItemStreams, isTextStreamMime } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class NotebookCellOutputTextModel extends Disposable implements ICellOutput { @@ -38,17 +38,46 @@ export class NotebookCellOutputTextModel extends Disposable implements ICellOutp replaceData(rawData: IOutputDto) { this._rawOutput = rawData; + this.optimizeOutputItems(); this._versionId = this._versionId + 1; - this._onDidChangeData.fire(); } appendData(items: IOutputItemDto[]) { this._rawOutput.outputs.push(...items); + this.optimizeOutputItems(); this._versionId = this._versionId + 1; this._onDidChangeData.fire(); } + private optimizeOutputItems() { + if (this.outputs.length > 1 && this.outputs.every(item => isTextStreamMime(item.mime))) { + // Look for the mimes in the items, and keep track of their order. + // Merge the streams into one output item, per mime type. + const mimeOutputs = new Map(); + const mimeTypes: string[] = []; + this.outputs.forEach(item => { + let items: Uint8Array[]; + if (mimeOutputs.has(item.mime)) { + items = mimeOutputs.get(item.mime)!; + } else { + items = []; + mimeOutputs.set(item.mime, items); + mimeTypes.push(item.mime); + } + items.push(item.data.buffer); + }); + this.outputs.length = 0; + mimeTypes.forEach(mime => { + const compressed = compressOutputItemStreams(mimeOutputs.get(mime)!); + this.outputs.push({ + mime, + data: compressed + }); + }); + } + } + toJSON(): IOutputDto { return { // data: this._data, @@ -57,4 +86,6 @@ export class NotebookCellOutputTextModel extends Disposable implements ICellOutp outputId: this._rawOutput.outputId }; } + + } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index c5d927de580..e32021676e5 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -16,7 +16,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel'; -import { CellInternalMetadataChangedEvent, CellKind, compressOutputItemStreams, ICell, ICellDto2, ICellOutput, IOutputDto, IOutputItemDto, isTextStreamMime, NotebookCellCollapseState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellInternalMetadataChangedEvent, CellKind, ICell, ICellDto2, ICellOutput, IOutputDto, IOutputItemDto, NotebookCellCollapseState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class NotebookCellTextModel extends Disposable implements ICell { private readonly _onDidChangeOutputs = this._register(new Emitter()); @@ -298,34 +298,6 @@ export class NotebookCellTextModel extends Disposable implements ICell { } } - private _optimizeOutputItems(output: ICellOutput) { - if (output.outputs.length > 1 && output.outputs.every(item => isTextStreamMime(item.mime))) { - // Look for the mimes in the items, and keep track of their order. - // Merge the streams into one output item, per mime type. - const mimeOutputs = new Map(); - const mimeTypes: string[] = []; - output.outputs.forEach(item => { - let items: Uint8Array[]; - if (mimeOutputs.has(item.mime)) { - items = mimeOutputs.get(item.mime)!; - } else { - items = []; - mimeOutputs.set(item.mime, items); - mimeTypes.push(item.mime); - } - items.push(item.data.buffer); - }); - output.outputs.length = 0; - mimeTypes.forEach(mime => { - const compressed = compressOutputItemStreams(mimeOutputs.get(mime)!); - output.outputs.push({ - mime, - data: compressed - }); - }); - } - } - replaceOutput(outputId: string, newOutputItem: ICellOutput) { const outputIndex = this.outputs.findIndex(output => output.outputId === outputId); @@ -335,7 +307,6 @@ export class NotebookCellTextModel extends Disposable implements ICell { const output = this.outputs[outputIndex]; output.replaceData(newOutputItem); - this._optimizeOutputItems(output); this._onDidChangeOutputItems.fire(); return true; } @@ -353,8 +324,6 @@ export class NotebookCellTextModel extends Disposable implements ICell { } else { output.replaceData({ outputId: outputId, outputs: items, metadata: output.metadata }); } - - this._optimizeOutputItems(output); this._onDidChangeOutputItems.fire(); return true; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts index 4da03a9f248..362619bf1b7 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts @@ -311,6 +311,58 @@ suite('NotebookTextModel', () => { ); }); + test('appending streaming outputs', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ], + (editor) => { + const textModel = editor.textModel; + + textModel.applyEdits([ + { + index: 0, + editType: CellEditType.Output, + append: true, + outputs: [{ + outputId: 'append1', + outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('append 1') }] + }] + }], true, undefined, () => undefined, undefined, true); + const [output] = textModel.cells[0].outputs; + assert.strictEqual(output.versionId, 0, 'initial output version is 0'); + + textModel.applyEdits([ + { + editType: CellEditType.OutputItems, + append: true, + outputId: 'append1', + items: [{ + mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('append 2') + }] + }], true, undefined, () => undefined, undefined, true); + assert.strictEqual(output.versionId, 1, 'version should bump per append'); + + textModel.applyEdits([ + { + editType: CellEditType.OutputItems, + append: true, + outputId: 'append1', + items: [{ + mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('append 3') + }] + }], true, undefined, () => undefined, undefined, true); + assert.strictEqual(output.versionId, 2, 'version should bump per append'); + + assert.strictEqual(textModel.cells.length, 1); + assert.strictEqual(textModel.cells[0].outputs.length, 1, 'has 1 output'); + assert.strictEqual(output.outputId, 'append1'); + assert.strictEqual(output.outputs.length, 1, 'outputs are compressed'); + assert.strictEqual(output.outputs[0].data.toString(), 'append 1append 2append 3'); + } + ); + }); + test('metadata', async function () { await withTestNotebook( [ From 79c495444e2e689594da40642730a85ee46dfa05 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Mon, 10 Jul 2023 10:48:20 -0700 Subject: [PATCH 031/826] track output buffer lengths per version --- .../model/notebookCellOutputTextModel.ts | 27 ++++ .../contrib/notebook/common/notebookCommon.ts | 1 + .../test/browser/notebookTextModel.test.ts | 133 ++++++++++++++++-- 3 files changed, 152 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts index 3c9e16931fd..7b989ef0076 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ICellOutput, IOutputDto, IOutputItemDto, compressOutputItemStreams, isTextStreamMime } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -37,6 +38,7 @@ export class NotebookCellOutputTextModel extends Disposable implements ICellOutp } replaceData(rawData: IOutputDto) { + this.versionedBufferLengths = {}; this._rawOutput = rawData; this.optimizeOutputItems(); this._versionId = this._versionId + 1; @@ -44,12 +46,37 @@ export class NotebookCellOutputTextModel extends Disposable implements ICellOutp } appendData(items: IOutputItemDto[]) { + this.trackBufferLengths(); this._rawOutput.outputs.push(...items); this.optimizeOutputItems(); this._versionId = this._versionId + 1; this._onDidChangeData.fire(); } + private trackBufferLengths() { + this.outputs.forEach(output => { + if (isTextStreamMime(output.mime)) { + if (!this.versionedBufferLengths[output.mime]) { + this.versionedBufferLengths[output.mime] = {}; + } + this.versionedBufferLengths[output.mime][this.versionId] = output.data.byteLength; + } + }); + } + + // mime: versionId: buffer length + private versionedBufferLengths: Record> = {}; + + appendedSinceVersion(versionId: number, mime: string): VSBuffer | undefined { + const bufferLength = this.versionedBufferLengths[mime]?.[versionId]; + const output = this.outputs.find(output => output.mime === mime); + if (bufferLength && output) { + return output.data.slice(bufferLength); + } + + return undefined; + } + private optimizeOutputItems() { if (this.outputs.length > 1 && this.outputs.every(item => isTextStreamMime(item.mime))) { // Look for the mimes in the items, and keep track of their order. diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 558f6c04765..c2c0e5a9f08 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -217,6 +217,7 @@ export interface ICellOutput { onDidChangeData: Event; replaceData(items: IOutputDto): void; appendData(items: IOutputItemDto[]): void; + appendedSinceVersion(versionId: number, mime: string): VSBuffer | undefined; } export interface CellInternalMetadataChangedEvent { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts index 362619bf1b7..a0759f0043d 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts @@ -311,6 +311,9 @@ suite('NotebookTextModel', () => { ); }); + const stdOutMime = 'application/vnd.code.notebook.stdout'; + const stdErrMime = 'application/vnd.code.notebook.stderr'; + test('appending streaming outputs', async function () { await withTestNotebook( [ @@ -326,20 +329,21 @@ suite('NotebookTextModel', () => { append: true, outputs: [{ outputId: 'append1', - outputs: [{ mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('append 1') }] + outputs: [{ mime: stdOutMime, data: valueBytesFromString('append 1') }] }] }], true, undefined, () => undefined, undefined, true); const [output] = textModel.cells[0].outputs; - assert.strictEqual(output.versionId, 0, 'initial output version is 0'); + assert.strictEqual(output.versionId, 0, 'initial output version should be 0'); textModel.applyEdits([ { editType: CellEditType.OutputItems, append: true, outputId: 'append1', - items: [{ - mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('append 2') - }] + items: [ + { mime: stdOutMime, data: valueBytesFromString('append 2') }, + { mime: stdOutMime, data: valueBytesFromString('append 3') } + ] }], true, undefined, () => undefined, undefined, true); assert.strictEqual(output.versionId, 1, 'version should bump per append'); @@ -348,9 +352,10 @@ suite('NotebookTextModel', () => { editType: CellEditType.OutputItems, append: true, outputId: 'append1', - items: [{ - mime: 'application/vnd.code.notebook.stdout', data: valueBytesFromString('append 3') - }] + items: [ + { mime: stdOutMime, data: valueBytesFromString('append 4') }, + { mime: stdOutMime, data: valueBytesFromString('append 5') } + ] }], true, undefined, () => undefined, undefined, true); assert.strictEqual(output.versionId, 2, 'version should bump per append'); @@ -358,7 +363,117 @@ suite('NotebookTextModel', () => { assert.strictEqual(textModel.cells[0].outputs.length, 1, 'has 1 output'); assert.strictEqual(output.outputId, 'append1'); assert.strictEqual(output.outputs.length, 1, 'outputs are compressed'); - assert.strictEqual(output.outputs[0].data.toString(), 'append 1append 2append 3'); + assert.strictEqual(output.outputs[0].data.toString(), 'append 1append 2append 3append 4append 5'); + assert.strictEqual(output.appendedSinceVersion(0, stdOutMime)?.toString(), 'append 2append 3append 4append 5'); + assert.strictEqual(output.appendedSinceVersion(1, stdOutMime)?.toString(), 'append 4append 5'); + assert.strictEqual(output.appendedSinceVersion(2, stdOutMime), undefined); + assert.strictEqual(output.appendedSinceVersion(2, stdErrMime), undefined); + } + ); + }); + + test('replacing streaming outputs', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ], + (editor) => { + const textModel = editor.textModel; + + textModel.applyEdits([ + { + index: 0, + editType: CellEditType.Output, + append: true, + outputs: [{ + outputId: 'append1', + outputs: [{ mime: stdOutMime, data: valueBytesFromString('append 1') }] + }] + }], true, undefined, () => undefined, undefined, true); + const [output] = textModel.cells[0].outputs; + assert.strictEqual(output.versionId, 0, 'initial output version should be 0'); + + textModel.applyEdits([ + { + editType: CellEditType.OutputItems, + append: true, + outputId: 'append1', + items: [{ + mime: stdOutMime, data: valueBytesFromString('append 2') + }] + }], true, undefined, () => undefined, undefined, true); + assert.strictEqual(output.versionId, 1, 'version should bump per append'); + + textModel.applyEdits([ + { + editType: CellEditType.OutputItems, + append: false, + outputId: 'append1', + items: [{ + mime: stdOutMime, data: valueBytesFromString('replace 3') + }] + }], true, undefined, () => undefined, undefined, true); + assert.strictEqual(output.versionId, 2, 'version should bump per replace'); + + textModel.applyEdits([ + { + editType: CellEditType.OutputItems, + append: true, + outputId: 'append1', + items: [{ + mime: stdOutMime, data: valueBytesFromString('append 4') + }] + }], true, undefined, () => undefined, undefined, true); + assert.strictEqual(output.versionId, 3, 'version should bump per append'); + + assert.strictEqual(output.outputs[0].data.toString(), 'replace 3append 4'); + assert.strictEqual(output.appendedSinceVersion(0, stdOutMime), undefined, + 'replacing output should clear out previous versioned output buffers'); + assert.strictEqual(output.appendedSinceVersion(1, stdOutMime), undefined, + 'replacing output should clear out previous versioned output buffers'); + assert.strictEqual(output.appendedSinceVersion(2, stdOutMime)?.toString(), 'append 4'); + } + ); + }); + + test('appending multiple different mime streaming outputs', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ], + (editor) => { + const textModel = editor.textModel; + + textModel.applyEdits([ + { + index: 0, + editType: CellEditType.Output, + append: true, + outputs: [{ + outputId: 'append1', + outputs: [ + { mime: stdOutMime, data: valueBytesFromString('stdout 1') }, + { mime: stdErrMime, data: valueBytesFromString('stderr 1') } + ] + }] + }], true, undefined, () => undefined, undefined, true); + const [output] = textModel.cells[0].outputs; + assert.strictEqual(output.versionId, 0, 'initial output version should be 0'); + + textModel.applyEdits([ + { + editType: CellEditType.OutputItems, + append: true, + outputId: 'append1', + items: [ + { mime: stdOutMime, data: valueBytesFromString('stdout 2') }, + { mime: stdErrMime, data: valueBytesFromString('stderr 2') } + ] + }], true, undefined, () => undefined, undefined, true); + assert.strictEqual(output.versionId, 1, 'version should bump per replace'); + + assert.strictEqual(output.appendedSinceVersion(0, stdErrMime)?.toString(), 'stderr 2'); + assert.strictEqual(output.appendedSinceVersion(0, stdOutMime)?.toString(), 'stdout 2'); } ); }); From 0edc352ebc037c0bd179aa2ef48decbcfd472fdb Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Mon, 10 Jul 2023 11:16:53 -0700 Subject: [PATCH 032/826] test for ouput compression --- .../model/notebookCellOutputTextModel.ts | 8 +++- .../contrib/notebook/common/notebookCommon.ts | 14 ++++-- .../test/browser/notebookTextModel.test.ts | 44 ++++++++++++++++++- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts index 7b989ef0076..74ed0aaf158 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts @@ -96,11 +96,15 @@ export class NotebookCellOutputTextModel extends Disposable implements ICellOutp }); this.outputs.length = 0; mimeTypes.forEach(mime => { - const compressed = compressOutputItemStreams(mimeOutputs.get(mime)!); + const compressionResult = compressOutputItemStreams(mimeOutputs.get(mime)!); this.outputs.push({ mime, - data: compressed + data: compressionResult.data }); + if (compressionResult.didCompression) { + // we can't rely on knowing buffer lengths if we've erased previous lines + this.versionedBufferLengths = {}; + } }); } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index c2c0e5a9f08..d031650968a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -996,6 +996,7 @@ const textDecoder = new TextDecoder(); * Given a stream of individual stdout outputs, this function will return the compressed lines, escaping some of the common terminal escape codes. * E.g. some terminal escape codes would result in the previous line getting cleared, such if we had 3 lines and * last line contained such a code, then the result string would be just the first two lines. + * @returns a single VSBuffer with the concatenated and compressed data, and whether any compression was done. */ export function compressOutputItemStreams(outputs: Uint8Array[]) { const buffers: Uint8Array[] = []; @@ -1008,13 +1009,17 @@ export function compressOutputItemStreams(outputs: Uint8Array[]) { startAppending = true; } } - compressStreamBuffer(buffers); - return formatStreamText(VSBuffer.concat(buffers.map(buffer => VSBuffer.wrap(buffer)))); + + const didCompression = compressStreamBuffer(buffers); + const data = formatStreamText(VSBuffer.concat(buffers.map(buffer => VSBuffer.wrap(buffer)))); + return { data, didCompression }; } -const MOVE_CURSOR_1_LINE_COMMAND = `${String.fromCharCode(27)}[A`; + +export const MOVE_CURSOR_1_LINE_COMMAND = `${String.fromCharCode(27)}[A`; const MOVE_CURSOR_1_LINE_COMMAND_BYTES = MOVE_CURSOR_1_LINE_COMMAND.split('').map(c => c.charCodeAt(0)); const LINE_FEED = 10; function compressStreamBuffer(streams: Uint8Array[]) { + let didCompress = false; streams.forEach((stream, index) => { if (index === 0 || stream.length < MOVE_CURSOR_1_LINE_COMMAND.length) { return; @@ -1029,10 +1034,13 @@ function compressStreamBuffer(streams: Uint8Array[]) { if (lastIndexOfLineFeed === -1) { return; } + + didCompress = true; streams[index - 1] = previousStream.subarray(0, lastIndexOfLineFeed); streams[index] = stream.subarray(MOVE_CURSOR_1_LINE_COMMAND.length); } }); + return didCompress; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts index a0759f0043d..c2cce9c8e78 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts @@ -11,7 +11,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellEditType, CellKind, ICellEditOperation, NotebookTextModelChangedEvent, NotebookTextModelWillAddRemoveEvent, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, ICellEditOperation, MOVE_CURSOR_1_LINE_COMMAND, NotebookTextModelChangedEvent, NotebookTextModelWillAddRemoveEvent, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { setupInstantiationService, TestCell, valueBytesFromString, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; suite('NotebookTextModel', () => { @@ -436,6 +436,48 @@ suite('NotebookTextModel', () => { ); }); + test('appending streaming outputs with compression', async function () { + + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ], + (editor) => { + const textModel = editor.textModel; + + textModel.applyEdits([ + { + index: 0, + editType: CellEditType.Output, + append: true, + outputs: [{ + outputId: 'append1', + outputs: [ + { mime: stdOutMime, data: valueBytesFromString('append 1') }, + { mime: stdOutMime, data: valueBytesFromString('\nappend 1') }] + }] + }], true, undefined, () => undefined, undefined, true); + const [output] = textModel.cells[0].outputs; + assert.strictEqual(output.versionId, 0, 'initial output version should be 0'); + + textModel.applyEdits([ + { + editType: CellEditType.OutputItems, + append: true, + outputId: 'append1', + items: [{ + mime: stdOutMime, data: valueBytesFromString(MOVE_CURSOR_1_LINE_COMMAND + '\nappend 2') + }] + }], true, undefined, () => undefined, undefined, true); + assert.strictEqual(output.versionId, 1, 'version should bump per append'); + + assert.strictEqual(output.outputs[0].data.toString(), 'append 1\nappend 2'); + assert.strictEqual(output.appendedSinceVersion(0, stdOutMime), undefined, + 'compressing outputs should clear out previous versioned output buffers'); + } + ); + }); + test('appending multiple different mime streaming outputs', async function () { await withTestNotebook( [ From c5511d5eec85db570ca81384aed9d4556a1c74e6 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Mon, 10 Jul 2023 14:22:07 -0700 Subject: [PATCH 033/826] pipe appended data info to webview --- extensions/notebook-renderers/src/index.ts | 13 ++++++++++++- .../api/common/extHostNotebookDocument.ts | 2 +- .../browser/view/renderers/backLayerWebView.ts | 3 +++ .../browser/view/renderers/webviewMessages.ts | 9 ++++++++- .../browser/view/renderers/webviewPreloads.ts | 16 +++++++++++++--- 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index df674353633..0450aa54f10 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -265,12 +265,20 @@ function scrollingEnabled(output: OutputItem, options: RenderOptions) { metadata.scrollable : options.outputScrolling; } +interface OutputWithAppend extends OutputItem { + appendedText?(): string | undefined; +} + // div.cell_container // div.output_container // div.output.output-stream <-- outputElement parameter // div.scrollable? tabindex="0" <-- contentParent // div output-item-id="{guid}" <-- content from outputItem parameter -function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: boolean, ctx: IRichRenderContext): IDisposable { +function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, error: boolean, ctx: IRichRenderContext): IDisposable { + const appendedText = outputInfo.appendedText?.(); + if (appendedText) { + console.log(`appending output version ${appendedText}`); + } const disposableStore = createDisposableStore(); const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); @@ -301,6 +309,9 @@ function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: const existingContent = outputElement.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; let contentParent = existingContent?.parentElement; if (existingContent && contentParent) { + if (appendedText){ + existingContent + } existingContent.replaceWith(newContent); while (newContent.nextSibling) { // clear out any stale content if we had previously combined streaming outputs into this one diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index 6f48147c9e4..e8970f70dee 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -132,7 +132,7 @@ export class ExtHostCell { const compressed = notebookCommon.compressOutputItemStreams(mimeOutputs.get(mime)!); output.items.push({ mime, - data: compressed.buffer + data: compressed.data.buffer }); }); } 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 0fdc7c3c612..4e3b2573ec3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -1506,6 +1506,8 @@ export class BackLayerWebView extends Themable { if (content.type === RenderOutputType.Extension) { const output = content.source.model; const firstBuffer = output.outputs.find(op => op.mime === content.mimeType)!; + const appenededData = output.appendedSinceVersion(outputCache.versionId, content.mimeType); + const appended = appenededData ? { valueBytes: appenededData.buffer, previousVersion: outputCache.versionId } : undefined; const valueBytes = copyBufferIfNeeded(firstBuffer.data.buffer, transfer); updatedContent = { @@ -1515,6 +1517,7 @@ export class BackLayerWebView extends Themable { output: { mime: content.mimeType, valueBytes, + appended: appended }, allOutputs: output.outputs.map(output => ({ mime: output.mime })) }; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index 15fc1f19790..1927f9497dc 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -188,11 +188,18 @@ export interface IOutputRequestDto { export interface OutputItemEntry { readonly mime: string; readonly valueBytes: Uint8Array; + readonly appended?: { valueBytes: Uint8Array; previousVersion: number }; } export type ICreationContent = | { readonly type: RenderOutputType.Html; readonly htmlContent: string } - | { readonly type: RenderOutputType.Extension; readonly outputId: string; readonly metadata: unknown; readonly output: OutputItemEntry; readonly allOutputs: ReadonlyArray<{ readonly mime: string }> }; + | { + readonly type: RenderOutputType.Extension; + readonly outputId: string; + readonly metadata: unknown; + readonly output: OutputItemEntry; + readonly allOutputs: ReadonlyArray<{ readonly mime: string }>; + }; export interface ICreationRequestMessage { readonly type: 'html'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 25dab44de71..190980671a0 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -804,6 +804,7 @@ async function webviewPreloads(ctx: PreloadContext) { interface ExtendedOutputItem extends rendererApi.OutputItem { readonly _allOutputItems: ReadonlyArray; + appendedText?(): string | undefined; } let hasWarnedAboutAllOutputItemsProposal = false; @@ -813,7 +814,8 @@ async function webviewPreloads(ctx: PreloadContext) { mime: string, metadata: unknown, valueBytes: Uint8Array, - allOutputItemData: ReadonlyArray<{ readonly mime: string }> + allOutputItemData: ReadonlyArray<{ readonly mime: string }>, + appended?: { valueBytes: Uint8Array; previousVersion: number } ): ExtendedOutputItem { function create( @@ -821,12 +823,20 @@ async function webviewPreloads(ctx: PreloadContext) { mime: string, metadata: unknown, valueBytes: Uint8Array, + appended?: { valueBytes: Uint8Array; previousVersion: number } ): ExtendedOutputItem { return Object.freeze({ id, mime, metadata, + appendedText(): string | undefined { + if (appended) { + return textDecoder.decode(appended.valueBytes); + } + return undefined; + }, + data(): Uint8Array { return valueBytes; }, @@ -874,7 +884,7 @@ async function webviewPreloads(ctx: PreloadContext) { }); })); - const item = create(id, mime, metadata, valueBytes); + const item = create(id, mime, metadata, valueBytes, appended); allOutputItemCache.set(mime, Promise.resolve(item)); return item; } @@ -2542,7 +2552,7 @@ async function webviewPreloads(ctx: PreloadContext) { const errors = preloadErrors.filter((e): e is Error => e instanceof Error); showRenderError(`Error loading preloads`, this.element, errors); } else { - const item = createOutputItem(this.outputId, content.output.mime, content.metadata, content.output.valueBytes, content.allOutputs); + const item = createOutputItem(this.outputId, content.output.mime, content.metadata, content.output.valueBytes, content.allOutputs, content.output.appended); const controller = new AbortController(); this.renderTaskAbort = controller; From 84d97b6802fb5814552160fc6aeefb5b6442e415 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 12 Jul 2023 11:39:15 +0200 Subject: [PATCH 034/826] Fixes #185028 (#187608) * Fixes #185028 * Consider accessibility.verbosity.diffEditor * Mentions audio cues in diff editor help. * Fixes API change. --- .../browser/widget/diffEditor.contribution.ts | 12 ++-- .../diffEditorWidget2/diffEditorEditors.ts | 9 ++- .../codeEditor/browser/diffEditorHelper.ts | 62 ++++++++++++++++--- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor.contribution.ts index 665d845f164..456dc90b293 100644 --- a/src/vs/editor/browser/widget/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor.contribution.ts @@ -11,10 +11,12 @@ import { localize } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -class DiffReviewNext extends EditorAction { +export class DiffReviewNext extends EditorAction { + public static id = 'editor.action.diffReview.next'; + constructor() { super({ - id: 'editor.action.diffReview.next', + id: DiffReviewNext.id, label: localize('editor.action.diffReview.next', "Go to Next Difference"), alias: 'Go to Next Difference', precondition: ContextKeyExpr.has('isInDiffEditor'), @@ -32,10 +34,12 @@ class DiffReviewNext extends EditorAction { } } -class DiffReviewPrev extends EditorAction { +export class DiffReviewPrev extends EditorAction { + public static id = 'editor.action.diffReview.prev'; + constructor() { super({ - id: 'editor.action.diffReview.prev', + id: DiffReviewPrev.id, label: localize('editor.action.diffReview.prev', "Go to Previous Difference"), alias: 'Go to Previous Difference', precondition: ContextKeyExpr.has('isInDiffEditor'), diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors.ts index b65da185662..a77d3c1727b 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors.ts @@ -16,6 +16,7 @@ import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DiffEditorOptions } from './diffEditorOptions'; import { IObservable, IReader } from 'vs/base/common/observable'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export class DiffEditorEditors extends Disposable { public readonly modified: CodeEditorWidget; @@ -31,7 +32,8 @@ export class DiffEditorEditors extends Disposable { codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, private readonly _createInnerEditor: (instantiationService: IInstantiationService, container: HTMLElement, options: Readonly, editorWidgetOptions: ICodeEditorWidgetOptions) => CodeEditorWidget, private readonly _modifiedReadOnlyOverride: IObservable, - @IInstantiationService private readonly _instantiationService: IInstantiationService + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { super(); @@ -150,7 +152,10 @@ export class DiffEditorEditors extends Disposable { } private _updateAriaLabel(ariaLabel: string | undefined): string | undefined { - const ariaNavigationTip = localize('diff-aria-navigation-tip', ' use Shift + F7 to navigate changes'); + if (!ariaLabel) { + ariaLabel = ''; + } + const ariaNavigationTip = localize('diff-aria-navigation-tip', ' use {0} to open the accessibility help.', this._keybindingService.lookupKeybinding('editor.action.accessibilityHelp')?.getAriaLabel()); if (this._options.accessibilityVerbose.get()) { return ariaLabel + ariaNavigationTip; } else if (ariaLabel) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index a12598dbdfe..2fe58e8b68a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -3,17 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { registerDiffEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { IDiffEditorContribution } from 'vs/editor/common/editorCommon'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; -import { IDiffComputationResult } from 'vs/editor/common/diff/smartLinesDiffComputer'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { DiffReviewNext, DiffReviewPrev } from 'vs/editor/browser/widget/diffEditor.contribution'; +import { DiffEditorWidget2 } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2'; import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { IDiffComputationResult } from 'vs/editor/common/diff/smartLinesDiffComputer'; +import { IDiffEditorContribution } from 'vs/editor/common/editorCommon'; +import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; +import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const enum WidgetState { Hidden, @@ -35,6 +43,44 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont @INotificationService private readonly _notificationService: INotificationService, ) { super(); + + this._register(AccessibilityHelpAction.addImplementation(105, 'diff-editor', async accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + const editorService = accessor.get(IEditorService); + const codeEditorService = accessor.get(ICodeEditorService); + const keybindingService = accessor.get(IKeybindingService); + + const next = keybindingService.lookupKeybinding(DiffReviewNext.id)?.getAriaLabel(); + const previous = keybindingService.lookupKeybinding(DiffReviewPrev.id)?.getAriaLabel(); + + if (!(editorService.activeTextEditorControl instanceof DiffEditorWidget2)) { + return; + } + + const codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); + if (!codeEditor) { + return; + } + + const keys = ['audioCues.diffLineDeleted', 'audioCues.diffLineInserted', 'audioCues.diffLineModified']; + + accessibleViewService.show({ + id: 'diffEditor', + provideContent: () => [ + nls.localize('msg1', "You are in a diff editor."), + nls.localize('msg2', "Press {0} or {1} to view the next or previous diff in the diff review mode that is optimized for screen readers.", next, previous), + nls.localize('msg3', "To control which audio cues should be played, the following settings can be configured: {0}.", keys.join(', ')), + ].join('\n'), + onClose: () => { + codeEditor.focus(); + }, + options: { type: AccessibleViewType.HelpMenu, ariaLabel: nls.localize('chat-help-label', "Diff editor accessibility help") } + }); + }, ContextKeyExpr.and( + ContextKeyEqualsExpr.create('diffEditorVersion', 2), + ContextKeyEqualsExpr.create('isInDiffEditor', true), + ))); + this._helperWidget = null; this._helperWidgetListener = null; this._state = WidgetState.Hidden; From d568855b2a4ef75716cdaa9a52da5315f5100a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 12 Jul 2023 11:39:27 +0200 Subject: [PATCH 035/826] build: prevent re-upload of builds (#187680) would've prevented what #187575 fixed --- build/azure-pipelines/common/createAsset.js | 27 ++++++++++----------- build/azure-pipelines/common/createAsset.ts | 27 +++++++++++---------- build/azure-pipelines/common/retry.js | 4 +-- build/azure-pipelines/common/retry.ts | 4 +-- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/build/azure-pipelines/common/createAsset.js b/build/azure-pipelines/common/createAsset.js index c46745f351e..0ac5646ebac 100644 --- a/build/azure-pipelines/common/createAsset.js +++ b/build/azure-pipelines/common/createAsset.js @@ -167,10 +167,11 @@ async function main() { }; const uploadPromises = []; if (await blobClient.exists()) { - console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); + uploadPromises.push(Promise.reject(new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`))); } else { - uploadPromises.push((0, retry_1.retry)(async () => { + uploadPromises.push((0, retry_1.retry)(async (attempt) => { + console.log(`Uploading blobs to Azure storage (attempt ${attempt})...`); await blobClient.uploadFile(filePath, blobOptions); console.log('Blob successfully uploaded to Azure storage.'); })); @@ -182,25 +183,23 @@ async function main() { const mooncakeContainerClient = mooncakeBlobServiceClient.getContainerClient(quality); const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName); if (await mooncakeBlobClient.exists()) { - console.log(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`); + uploadPromises.push(Promise.reject(new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`))); } else { - uploadPromises.push((0, retry_1.retry)(async () => { + uploadPromises.push((0, retry_1.retry)(async (attempt) => { + console.log(`Uploading blobs to Mooncake Azure storage (attempt ${attempt})...`); await mooncakeBlobClient.uploadFile(filePath, blobOptions); console.log('Blob successfully uploaded to Mooncake Azure storage.'); })); } - if (uploadPromises.length) { - console.log('Uploading blobs to Azure storage and Mooncake Azure storage...'); + } + const promiseResults = await Promise.allSettled(uploadPromises); + for (const result of promiseResults) { + if (result.status === 'rejected') { + throw result.reason; } } - else { - if (uploadPromises.length) { - console.log('Uploading blobs to Azure storage...'); - } - } - await Promise.all(uploadPromises); - console.log(uploadPromises.length ? 'All blobs successfully uploaded.' : 'No blobs to upload.'); + console.log('All blobs successfully uploaded.'); const assetUrl = `${process.env['AZURE_CDN_URL']}/${quality}/${blobName}`; const blobPath = new URL(assetUrl).pathname; const mooncakeUrl = `${process.env['MOONCAKE_CDN_URL']}${blobPath}`; @@ -230,4 +229,4 @@ main().then(() => { console.error(err); process.exit(1); }); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlQXNzZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGVBc3NldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHlCQUF5QjtBQUV6QixpQ0FBaUM7QUFDakMsc0RBQXdJO0FBQ3hJLDZCQUE2QjtBQUM3QiwwQ0FBNkM7QUFDN0MsOENBQXlEO0FBQ3pELG1DQUFnQztBQWFoQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtJQUM5QixPQUFPLENBQUMsS0FBSyxDQUFDLDJEQUEyRCxDQUFDLENBQUM7SUFDM0UsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2pCO0FBRUQsd0ZBQXdGO0FBQ3hGLFNBQVMsV0FBVyxDQUFDLE9BQWUsRUFBRSxFQUFVLEVBQUUsSUFBWSxFQUFFLElBQVk7SUFDM0UsUUFBUSxFQUFFLEVBQUU7UUFDWCxLQUFLLE9BQU87WUFDWCxRQUFRLE9BQU8sRUFBRTtnQkFDaEIsS0FBSyxRQUFRLENBQUMsQ0FBQztvQkFDZCxNQUFNLEtBQUssR0FBRyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFNBQVMsSUFBSSxFQUFFLENBQUM7b0JBQzFELFFBQVEsSUFBSSxFQUFFO3dCQUNiLEtBQUssU0FBUzs0QkFDYixPQUFPLEdBQUcsS0FBSyxVQUFVLENBQUM7d0JBQzNCLEtBQUssT0FBTzs0QkFDWCxPQUFPLEtBQUssQ0FBQzt3QkFDZCxLQUFLLFlBQVk7NEJBQ2hCLE9BQU8sR0FBRyxLQUFLLE9BQU8sQ0FBQzt3QkFDeEI7NEJBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbkU7aUJBQ0Q7Z0JBQ0QsS0FBSyxRQUFRO29CQUNaLElBQUksSUFBSSxLQUFLLE9BQU8sRUFBRTt3QkFDckIsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbEU7b0JBQ0QsT0FBTyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixJQUFJLEVBQUUsQ0FBQztnQkFDbEUsS0FBSyxLQUFLO29CQUNULElBQUksSUFBSSxLQUFLLE9BQU8sRUFBRTt3QkFDckIsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbEU7b0JBQ0QsT0FBTyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLElBQUksTUFBTSxDQUFDO2dCQUMxRSxLQUFLLEtBQUs7b0JBQ1QsT0FBTyxhQUFhLElBQUksRUFBRSxDQUFDO2dCQUM1QjtvQkFDQyxNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixPQUFPLElBQUksRUFBRSxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO2FBQ25FO1FBQ0YsS0FBSyxRQUFRO1lBQ1osUUFBUSxPQUFPLEVBQUU7Z0JBQ2hCLEtBQUssUUFBUTtvQkFDWixPQUFPLGlCQUFpQixJQUFJLEVBQUUsQ0FBQztnQkFDaEMsS0FBSyxLQUFLO29CQUNULE9BQU8saUJBQWlCLElBQUksTUFBTSxDQUFDO2dCQUNwQyxLQUFLLEtBQUs7b0JBQ1QsT0FBTyxjQUFjLElBQUksRUFBRSxDQUFDO2dCQUM3QjtvQkFDQyxNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixPQUFPLElBQUksRUFBRSxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO2FBQ25FO1FBQ0YsS0FBSyxPQUFPO1lBQ1gsUUFBUSxJQUFJLEVBQUU7Z0JBQ2IsS0FBSyxNQUFNO29CQUNWLE9BQU8sY0FBYyxJQUFJLEVBQUUsQ0FBQztnQkFDN0IsS0FBSyxrQkFBa0I7b0JBQ3RCLFFBQVEsT0FBTyxFQUFFO3dCQUNoQixLQUFLLFFBQVE7NEJBQ1osT0FBTyxTQUFTLElBQUksRUFBRSxDQUFDO3dCQUN4QixLQUFLLFFBQVE7NEJBQ1osT0FBTyxnQkFBZ0IsSUFBSSxFQUFFLENBQUM7d0JBQy9CLEtBQUssS0FBSzs0QkFDVCxPQUFPLElBQUksS0FBSyxZQUFZLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxnQkFBZ0IsSUFBSSxNQUFNLENBQUM7d0JBQzlFOzRCQUNDLE1BQU0sSUFBSSxLQUFLLENBQUMsaUJBQWlCLE9BQU8sSUFBSSxFQUFFLElBQUksSUFBSSxJQUFJLElBQUksRUFBRSxDQUFDLENBQUM7cUJBQ25FO2dCQUNGLEtBQUssYUFBYTtvQkFDakIsT0FBTyxhQUFhLElBQUksRUFBRSxDQUFDO2dCQUM1QixLQUFLLGFBQWE7b0JBQ2pCLE9BQU8sYUFBYSxJQUFJLEVBQUUsQ0FBQztnQkFDNUIsS0FBSyxLQUFLO29CQUNULE9BQU8sYUFBYSxJQUFJLEVBQUUsQ0FBQztnQkFDNUI7b0JBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQzthQUNuRTtRQUNGLEtBQUssUUFBUTtZQUNaLFFBQVEsT0FBTyxFQUFFO2dCQUNoQixLQUFLLFFBQVE7b0JBQ1osSUFBSSxJQUFJLEtBQUssS0FBSyxFQUFFO3dCQUNuQixPQUFPLFFBQVEsQ0FBQztxQkFDaEI7b0JBQ0QsT0FBTyxVQUFVLElBQUksRUFBRSxDQUFDO2dCQUN6QixLQUFLLFFBQVE7b0JBQ1osSUFBSSxJQUFJLEtBQUssS0FBSyxFQUFFO3dCQUNuQixPQUFPLGVBQWUsQ0FBQztxQkFDdkI7b0JBQ0QsT0FBTyxpQkFBaUIsSUFBSSxFQUFFLENBQUM7Z0JBQ2hDLEtBQUssS0FBSztvQkFDVCxJQUFJLElBQUksS0FBSyxLQUFLLEVBQUU7d0JBQ25CLE9BQU8sbUJBQW1CLENBQUM7cUJBQzNCO29CQUNELE9BQU8saUJBQWlCLElBQUksTUFBTSxDQUFDO2dCQUNwQyxLQUFLLEtBQUs7b0JBQ1QsT0FBTyxjQUFjLElBQUksRUFBRSxDQUFDO2dCQUM3QjtvQkFDQyxNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixPQUFPLElBQUksRUFBRSxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO2FBQ25FO1FBQ0Y7WUFDQyxNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixPQUFPLElBQUksRUFBRSxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO0tBQ25FO0FBQ0YsQ0FBQztBQUVELDhFQUE4RTtBQUM5RSxTQUFTLFdBQVcsQ0FBQyxJQUFZO0lBQ2hDLFFBQVEsSUFBSSxFQUFFO1FBQ2IsS0FBSyxZQUFZO1lBQ2hCLE9BQU8sT0FBTyxDQUFDO1FBQ2hCLEtBQUssYUFBYSxDQUFDO1FBQ25CLEtBQUssYUFBYTtZQUNqQixPQUFPLFNBQVMsQ0FBQztRQUNsQjtZQUNDLE9BQU8sSUFBSSxDQUFDO0tBQ2I7QUFDRixDQUFDO0FBRUQsU0FBUyxVQUFVLENBQUMsUUFBZ0IsRUFBRSxNQUFnQjtJQUNyRCxPQUFPLElBQUksT0FBTyxDQUFTLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO1FBQ25DLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUM7UUFFM0MsTUFBTTthQUNKLEVBQUUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7YUFDdEMsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7YUFDZCxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM5QyxDQUFDLENBQUMsQ0FBQztBQUNKLENBQUM7QUFFRCxTQUFTLE1BQU0sQ0FBQyxJQUFZO0lBQzNCLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7SUFFakMsSUFBSSxPQUFPLE1BQU0sS0FBSyxXQUFXLEVBQUU7UUFDbEMsTUFBTSxJQUFJLEtBQUssQ0FBQyxlQUFlLEdBQUcsSUFBSSxDQUFDLENBQUM7S0FDeEM7SUFFRCxPQUFPLE1BQU0sQ0FBQztBQUNmLENBQUM7QUFFRCxLQUFLLFVBQVUsSUFBSTtJQUNsQixNQUFNLENBQUMsRUFBRSxBQUFELEVBQUcsT0FBTyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsZUFBZSxFQUFFLFFBQVEsRUFBRSxRQUFRLENBQUMsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDO0lBQ2xGLHdDQUF3QztJQUN4QyxNQUFNLFFBQVEsR0FBRyxXQUFXLENBQUMsT0FBTyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsZUFBZSxDQUFDLENBQUM7SUFDakUsTUFBTSxJQUFJLEdBQUcsV0FBVyxDQUFDLGVBQWUsQ0FBQyxDQUFDO0lBQzFDLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ3pDLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO0lBRTdDLE9BQU8sQ0FBQyxHQUFHLENBQUMsbUJBQW1CLENBQUMsQ0FBQztJQUVqQyxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksT0FBTyxDQUFXLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM3RyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDO0lBRXZCLE9BQU8sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBRTNCLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUM3QyxNQUFNLENBQUMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxHQUFHLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFDLEVBQUUsVUFBVSxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFN0csT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDL0IsT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFFbkMsTUFBTSxRQUFRLEdBQUcsTUFBTSxHQUFHLEdBQUcsR0FBRyxRQUFRLENBQUM7SUFFekMsTUFBTSxzQkFBc0IsR0FBMkIsRUFBRSxZQUFZLEVBQUUsRUFBRSxlQUFlLEVBQUUscUNBQXNCLENBQUMsV0FBVyxFQUFFLFFBQVEsRUFBRSxDQUFDLEVBQUUsY0FBYyxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxFQUFFLEVBQUUsQ0FBQztJQUU5SyxNQUFNLFVBQVUsR0FBRyxJQUFJLGlDQUFzQixDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUUsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFFLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBRSxDQUFDLENBQUM7SUFDckosTUFBTSxpQkFBaUIsR0FBRyxJQUFJLGdDQUFpQixDQUFDLHNDQUFzQyxFQUFFLFVBQVUsRUFBRSxzQkFBc0IsQ0FBQyxDQUFDO0lBQzVILE1BQU0sZUFBZSxHQUFHLGlCQUFpQixDQUFDLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3RFLE1BQU0sVUFBVSxHQUFHLGVBQWUsQ0FBQyxrQkFBa0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUVoRSxNQUFNLFdBQVcsR0FBbUM7UUFDbkQsZUFBZSxFQUFFO1lBQ2hCLGVBQWUsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQztZQUN0QyxzQkFBc0IsRUFBRSx5QkFBeUIsUUFBUSxHQUFHO1lBQzVELGdCQUFnQixFQUFFLDBCQUEwQjtTQUM1QztLQUNELENBQUM7SUFFRixNQUFNLGNBQWMsR0FBb0IsRUFBRSxDQUFDO0lBQzNDLElBQUksTUFBTSxVQUFVLENBQUMsTUFBTSxFQUFFLEVBQUU7UUFDOUIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLE9BQU8sS0FBSyxRQUFRLHdDQUF3QyxDQUFDLENBQUM7S0FDbEY7U0FBTTtRQUNOLGNBQWMsQ0FBQyxJQUFJLENBQUMsSUFBQSxhQUFLLEVBQUMsS0FBSyxJQUFJLEVBQUU7WUFDcEMsTUFBTSxVQUFVLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRSxXQUFXLENBQUMsQ0FBQztZQUNuRCxPQUFPLENBQUMsR0FBRyxDQUFDLDhDQUE4QyxDQUFDLENBQUM7UUFDN0QsQ0FBQyxDQUFDLENBQUMsQ0FBQztLQUNKO0lBRUQsTUFBTSxzQkFBc0IsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsNEJBQTRCLENBQUMsSUFBSSxNQUFNLENBQUMsQ0FBQztJQUVqRyxJQUFJLHNCQUFzQixFQUFFO1FBQzNCLE1BQU0sa0JBQWtCLEdBQUcsSUFBSSxpQ0FBc0IsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLDBCQUEwQixDQUFFLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQywwQkFBMEIsQ0FBRSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsOEJBQThCLENBQUUsQ0FBQyxDQUFDO1FBQ3hMLE1BQU0seUJBQXlCLEdBQUcsSUFBSSxnQ0FBaUIsQ0FBQywyQ0FBMkMsRUFBRSxrQkFBa0IsRUFBRSxzQkFBc0IsQ0FBQyxDQUFDO1FBQ2pKLE1BQU0sdUJBQXVCLEdBQUcseUJBQXlCLENBQUMsa0JBQWtCLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDdEYsTUFBTSxrQkFBa0IsR0FBRyx1QkFBdUIsQ0FBQyxrQkFBa0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUVoRixJQUFJLE1BQU0sa0JBQWtCLENBQUMsTUFBTSxFQUFFLEVBQUU7WUFDdEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsT0FBTyxLQUFLLFFBQVEsd0NBQXdDLENBQUMsQ0FBQztTQUMzRjthQUFNO1lBQ04sY0FBYyxDQUFDLElBQUksQ0FBQyxJQUFBLGFBQUssRUFBQyxLQUFLLElBQUksRUFBRTtnQkFDcEMsTUFBTSxrQkFBa0IsQ0FBQyxVQUFVLENBQUMsUUFBUSxFQUFFLFdBQVcsQ0FBQyxDQUFDO2dCQUMzRCxPQUFPLENBQUMsR0FBRyxDQUFDLHVEQUF1RCxDQUFDLENBQUM7WUFDdEUsQ0FBQyxDQUFDLENBQUMsQ0FBQztTQUNKO1FBRUQsSUFBSSxjQUFjLENBQUMsTUFBTSxFQUFFO1lBQzFCLE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0VBQWdFLENBQUMsQ0FBQztTQUM5RTtLQUNEO1NBQU07UUFDTixJQUFJLGNBQWMsQ0FBQyxNQUFNLEVBQUU7WUFDMUIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQ0FBcUMsQ0FBQyxDQUFDO1NBQ25EO0tBQ0Q7SUFFRCxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLENBQUM7SUFFbEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFDLENBQUMscUJBQXFCLENBQUMsQ0FBQztJQUVoRyxNQUFNLFFBQVEsR0FBRyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsZUFBZSxDQUFDLElBQUksT0FBTyxJQUFJLFFBQVEsRUFBRSxDQUFDO0lBQzFFLE1BQU0sUUFBUSxHQUFHLElBQUksR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDLFFBQVEsQ0FBQztJQUM1QyxNQUFNLFdBQVcsR0FBRyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsa0JBQWtCLENBQUMsR0FBRyxRQUFRLEVBQUUsQ0FBQztJQUVwRSxNQUFNLEtBQUssR0FBVTtRQUNwQixRQUFRO1FBQ1IsSUFBSTtRQUNKLEdBQUcsRUFBRSxRQUFRO1FBQ2IsSUFBSSxFQUFFLFFBQVE7UUFDZCxXQUFXO1FBQ1gsVUFBVTtRQUNWLElBQUk7S0FDSixDQUFDO0lBRUYsbUVBQW1FO0lBQ25FLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRTtRQUMzQixLQUFLLENBQUMsa0JBQWtCLEdBQUcsSUFBSSxDQUFDO0tBQ2hDO0lBRUQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7SUFFekQsTUFBTSxNQUFNLEdBQUcsSUFBSSxxQkFBWSxDQUFDLEVBQUUsUUFBUSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkJBQTJCLENBQUUsRUFBRSxjQUFjLEVBQUUsVUFBVSxFQUFFLENBQUMsQ0FBQztJQUNySCxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxPQUFPLENBQUM7SUFDckUsTUFBTSxJQUFBLGFBQUssRUFBQyxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsZUFBZSxDQUFDLGFBQWEsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUU3RixPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0FBQzFCLENBQUM7QUFFRCxJQUFJLEVBQUUsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFO0lBQ2hCLE9BQU8sQ0FBQyxHQUFHLENBQUMsNEJBQTRCLENBQUMsQ0FBQztJQUMxQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2pCLENBQUMsRUFBRSxHQUFHLENBQUMsRUFBRTtJQUNSLE9BQU8sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDbkIsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNqQixDQUFDLENBQUMsQ0FBQyJ9 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlQXNzZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGVBc3NldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHlCQUF5QjtBQUV6QixpQ0FBaUM7QUFDakMsc0RBQXdJO0FBQ3hJLDZCQUE2QjtBQUM3QiwwQ0FBNkM7QUFDN0MsOENBQXlEO0FBQ3pELG1DQUFnQztBQWFoQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtJQUM5QixPQUFPLENBQUMsS0FBSyxDQUFDLDJEQUEyRCxDQUFDLENBQUM7SUFDM0UsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2pCO0FBRUQsd0ZBQXdGO0FBQ3hGLFNBQVMsV0FBVyxDQUFDLE9BQWUsRUFBRSxFQUFVLEVBQUUsSUFBWSxFQUFFLElBQVk7SUFDM0UsUUFBUSxFQUFFLEVBQUU7UUFDWCxLQUFLLE9BQU87WUFDWCxRQUFRLE9BQU8sRUFBRTtnQkFDaEIsS0FBSyxRQUFRLENBQUMsQ0FBQztvQkFDZCxNQUFNLEtBQUssR0FBRyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFNBQVMsSUFBSSxFQUFFLENBQUM7b0JBQzFELFFBQVEsSUFBSSxFQUFFO3dCQUNiLEtBQUssU0FBUzs0QkFDYixPQUFPLEdBQUcsS0FBSyxVQUFVLENBQUM7d0JBQzNCLEtBQUssT0FBTzs0QkFDWCxPQUFPLEtBQUssQ0FBQzt3QkFDZCxLQUFLLFlBQVk7NEJBQ2hCLE9BQU8sR0FBRyxLQUFLLE9BQU8sQ0FBQzt3QkFDeEI7NEJBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbkU7aUJBQ0Q7Z0JBQ0QsS0FBSyxRQUFRO29CQUNaLElBQUksSUFBSSxLQUFLLE9BQU8sRUFBRTt3QkFDckIsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbEU7b0JBQ0QsT0FBTyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixJQUFJLEVBQUUsQ0FBQztnQkFDbEUsS0FBSyxLQUFLO29CQUNULElBQUksSUFBSSxLQUFLLE9BQU8sRUFBRTt3QkFDckIsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbEU7b0JBQ0QsT0FBTyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLElBQUksTUFBTSxDQUFDO2dCQUMxRSxLQUFLLEtBQUs7b0JBQ1QsT0FBTyxhQUFhLElBQUksRUFBRSxDQUFDO2dCQUM1QjtvQkFDQyxNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixPQUFPLElBQUksRUFBRSxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO2FBQ25FO1FBQ0YsS0FBSyxRQUFRO1lBQ1osUUFBUSxPQUFPLEVBQUU7Z0JBQ2hCLEtBQUssUUFBUTtvQkFDWixPQUFPLGlCQUFpQixJQUFJLEVBQUUsQ0FBQztnQkFDaEMsS0FBSyxLQUFLO29CQUNULE9BQU8saUJBQWlCLElBQUksTUFBTSxDQUFDO2dCQUNwQyxLQUFLLEtBQUs7b0JBQ1QsT0FBTyxjQUFjLElBQUksRUFBRSxDQUFDO2dCQUM3QjtvQkFDQyxNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixPQUFPLElBQUksRUFBRSxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO2FBQ25FO1FBQ0YsS0FBSyxPQUFPO1lBQ1gsUUFBUSxJQUFJLEVBQUU7Z0JBQ2IsS0FBSyxNQUFNO29CQUNWLE9BQU8sY0FBYyxJQUFJLEVBQUUsQ0FBQztnQkFDN0IsS0FBSyxrQkFBa0I7b0JBQ3RCLFFBQVEsT0FBTyxFQUFFO3dCQUNoQixLQUFLLFFBQVE7NEJBQ1osT0FBTyxTQUFTLElBQUksRUFBRSxDQUFDO3dCQUN4QixLQUFLLFFBQVE7NEJBQ1osT0FBTyxnQkFBZ0IsSUFBSSxFQUFFLENBQUM7d0JBQy9CLEtBQUssS0FBSzs0QkFDVCxPQUFPLElBQUksS0FBSyxZQUFZLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxnQkFBZ0IsSUFBSSxNQUFNLENBQUM7d0JBQzlFOzRCQUNDLE1BQU0sSUFBSSxLQUFLLENBQUMsaUJBQWlCLE9BQU8sSUFBSSxFQUFFLElBQUksSUFBSSxJQUFJLElBQUksRUFBRSxDQUFDLENBQUM7cUJBQ25FO2dCQUNGLEtBQUssYUFBYTtvQkFDakIsT0FBTyxhQUFhLElBQUksRUFBRSxDQUFDO2dCQUM1QixLQUFLLGFBQWE7b0JBQ2pCLE9BQU8sYUFBYSxJQUFJLEVBQUUsQ0FBQztnQkFDNUIsS0FBSyxLQUFLO29CQUNULE9BQU8sYUFBYSxJQUFJLEVBQUUsQ0FBQztnQkFDNUI7b0JBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQzthQUNuRTtRQUNGLEtBQUssUUFBUTtZQUNaLFFBQVEsT0FBTyxFQUFFO2dCQUNoQixLQUFLLFFBQVE7b0JBQ1osSUFBSSxJQUFJLEtBQUssS0FBSyxFQUFFO3dCQUNuQixPQUFPLFFBQVEsQ0FBQztxQkFDaEI7b0JBQ0QsT0FBTyxVQUFVLElBQUksRUFBRSxDQUFDO2dCQUN6QixLQUFLLFFBQVE7b0JBQ1osSUFBSSxJQUFJLEtBQUssS0FBSyxFQUFFO3dCQUNuQixPQUFPLGVBQWUsQ0FBQztxQkFDdkI7b0JBQ0QsT0FBTyxpQkFBaUIsSUFBSSxFQUFFLENBQUM7Z0JBQ2hDLEtBQUssS0FBSztvQkFDVCxJQUFJLElBQUksS0FBSyxLQUFLLEVBQUU7d0JBQ25CLE9BQU8sbUJBQW1CLENBQUM7cUJBQzNCO29CQUNELE9BQU8saUJBQWlCLElBQUksTUFBTSxDQUFDO2dCQUNwQyxLQUFLLEtBQUs7b0JBQ1QsT0FBTyxjQUFjLElBQUksRUFBRSxDQUFDO2dCQUM3QjtvQkFDQyxNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixPQUFPLElBQUksRUFBRSxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO2FBQ25FO1FBQ0Y7WUFDQyxNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixPQUFPLElBQUksRUFBRSxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO0tBQ25FO0FBQ0YsQ0FBQztBQUVELDhFQUE4RTtBQUM5RSxTQUFTLFdBQVcsQ0FBQyxJQUFZO0lBQ2hDLFFBQVEsSUFBSSxFQUFFO1FBQ2IsS0FBSyxZQUFZO1lBQ2hCLE9BQU8sT0FBTyxDQUFDO1FBQ2hCLEtBQUssYUFBYSxDQUFDO1FBQ25CLEtBQUssYUFBYTtZQUNqQixPQUFPLFNBQVMsQ0FBQztRQUNsQjtZQUNDLE9BQU8sSUFBSSxDQUFDO0tBQ2I7QUFDRixDQUFDO0FBRUQsU0FBUyxVQUFVLENBQUMsUUFBZ0IsRUFBRSxNQUFnQjtJQUNyRCxPQUFPLElBQUksT0FBTyxDQUFTLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO1FBQ25DLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUM7UUFFM0MsTUFBTTthQUNKLEVBQUUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7YUFDdEMsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7YUFDZCxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM5QyxDQUFDLENBQUMsQ0FBQztBQUNKLENBQUM7QUFFRCxTQUFTLE1BQU0sQ0FBQyxJQUFZO0lBQzNCLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7SUFFakMsSUFBSSxPQUFPLE1BQU0sS0FBSyxXQUFXLEVBQUU7UUFDbEMsTUFBTSxJQUFJLEtBQUssQ0FBQyxlQUFlLEdBQUcsSUFBSSxDQUFDLENBQUM7S0FDeEM7SUFFRCxPQUFPLE1BQU0sQ0FBQztBQUNmLENBQUM7QUFFRCxLQUFLLFVBQVUsSUFBSTtJQUNsQixNQUFNLENBQUMsRUFBRSxBQUFELEVBQUcsT0FBTyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsZUFBZSxFQUFFLFFBQVEsRUFBRSxRQUFRLENBQUMsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDO0lBQ2xGLHdDQUF3QztJQUN4QyxNQUFNLFFBQVEsR0FBRyxXQUFXLENBQUMsT0FBTyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsZUFBZSxDQUFDLENBQUM7SUFDakUsTUFBTSxJQUFJLEdBQUcsV0FBVyxDQUFDLGVBQWUsQ0FBQyxDQUFDO0lBQzFDLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ3pDLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO0lBRTdDLE9BQU8sQ0FBQyxHQUFHLENBQUMsbUJBQW1CLENBQUMsQ0FBQztJQUVqQyxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksT0FBTyxDQUFXLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM3RyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDO0lBRXZCLE9BQU8sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBRTNCLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUM3QyxNQUFNLENBQUMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxHQUFHLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFDLEVBQUUsVUFBVSxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFN0csT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDL0IsT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFFbkMsTUFBTSxRQUFRLEdBQUcsTUFBTSxHQUFHLEdBQUcsR0FBRyxRQUFRLENBQUM7SUFFekMsTUFBTSxzQkFBc0IsR0FBMkIsRUFBRSxZQUFZLEVBQUUsRUFBRSxlQUFlLEVBQUUscUNBQXNCLENBQUMsV0FBVyxFQUFFLFFBQVEsRUFBRSxDQUFDLEVBQUUsY0FBYyxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxFQUFFLEVBQUUsQ0FBQztJQUU5SyxNQUFNLFVBQVUsR0FBRyxJQUFJLGlDQUFzQixDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUUsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFFLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBRSxDQUFDLENBQUM7SUFDckosTUFBTSxpQkFBaUIsR0FBRyxJQUFJLGdDQUFpQixDQUFDLHNDQUFzQyxFQUFFLFVBQVUsRUFBRSxzQkFBc0IsQ0FBQyxDQUFDO0lBQzVILE1BQU0sZUFBZSxHQUFHLGlCQUFpQixDQUFDLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3RFLE1BQU0sVUFBVSxHQUFHLGVBQWUsQ0FBQyxrQkFBa0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUVoRSxNQUFNLFdBQVcsR0FBbUM7UUFDbkQsZUFBZSxFQUFFO1lBQ2hCLGVBQWUsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQztZQUN0QyxzQkFBc0IsRUFBRSx5QkFBeUIsUUFBUSxHQUFHO1lBQzVELGdCQUFnQixFQUFFLDBCQUEwQjtTQUM1QztLQUNELENBQUM7SUFFRixNQUFNLGNBQWMsR0FBb0IsRUFBRSxDQUFDO0lBRTNDLElBQUksTUFBTSxVQUFVLENBQUMsTUFBTSxFQUFFLEVBQUU7UUFDOUIsY0FBYyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksS0FBSyxDQUFDLFFBQVEsT0FBTyxLQUFLLFFBQVEsd0NBQXdDLENBQUMsQ0FBQyxDQUFDLENBQUM7S0FDckg7U0FBTTtRQUNOLGNBQWMsQ0FBQyxJQUFJLENBQUMsSUFBQSxhQUFLLEVBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRSxFQUFFO1lBQzNDLE9BQU8sQ0FBQyxHQUFHLENBQUMsNkNBQTZDLE9BQU8sTUFBTSxDQUFDLENBQUM7WUFDeEUsTUFBTSxVQUFVLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRSxXQUFXLENBQUMsQ0FBQztZQUNuRCxPQUFPLENBQUMsR0FBRyxDQUFDLDhDQUE4QyxDQUFDLENBQUM7UUFDN0QsQ0FBQyxDQUFDLENBQUMsQ0FBQztLQUNKO0lBRUQsTUFBTSxzQkFBc0IsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsNEJBQTRCLENBQUMsSUFBSSxNQUFNLENBQUMsQ0FBQztJQUVqRyxJQUFJLHNCQUFzQixFQUFFO1FBQzNCLE1BQU0sa0JBQWtCLEdBQUcsSUFBSSxpQ0FBc0IsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLDBCQUEwQixDQUFFLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQywwQkFBMEIsQ0FBRSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsOEJBQThCLENBQUUsQ0FBQyxDQUFDO1FBQ3hMLE1BQU0seUJBQXlCLEdBQUcsSUFBSSxnQ0FBaUIsQ0FBQywyQ0FBMkMsRUFBRSxrQkFBa0IsRUFBRSxzQkFBc0IsQ0FBQyxDQUFDO1FBQ2pKLE1BQU0sdUJBQXVCLEdBQUcseUJBQXlCLENBQUMsa0JBQWtCLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDdEYsTUFBTSxrQkFBa0IsR0FBRyx1QkFBdUIsQ0FBQyxrQkFBa0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUVoRixJQUFJLE1BQU0sa0JBQWtCLENBQUMsTUFBTSxFQUFFLEVBQUU7WUFDdEMsY0FBYyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksS0FBSyxDQUFDLGlCQUFpQixPQUFPLEtBQUssUUFBUSx3Q0FBd0MsQ0FBQyxDQUFDLENBQUMsQ0FBQztTQUM5SDthQUFNO1lBQ04sY0FBYyxDQUFDLElBQUksQ0FBQyxJQUFBLGFBQUssRUFBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7Z0JBQzNDLE9BQU8sQ0FBQyxHQUFHLENBQUMsc0RBQXNELE9BQU8sTUFBTSxDQUFDLENBQUM7Z0JBQ2pGLE1BQU0sa0JBQWtCLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRSxXQUFXLENBQUMsQ0FBQztnQkFDM0QsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1REFBdUQsQ0FBQyxDQUFDO1lBQ3RFLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDSjtLQUNEO0lBRUQsTUFBTSxjQUFjLEdBQUcsTUFBTSxPQUFPLENBQUMsVUFBVSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0lBRWhFLEtBQUssTUFBTSxNQUFNLElBQUksY0FBYyxFQUFFO1FBQ3BDLElBQUksTUFBTSxDQUFDLE1BQU0sS0FBSyxVQUFVLEVBQUU7WUFDakMsTUFBTSxNQUFNLENBQUMsTUFBTSxDQUFDO1NBQ3BCO0tBQ0Q7SUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLGtDQUFrQyxDQUFDLENBQUM7SUFFaEQsTUFBTSxRQUFRLEdBQUcsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLGVBQWUsQ0FBQyxJQUFJLE9BQU8sSUFBSSxRQUFRLEVBQUUsQ0FBQztJQUMxRSxNQUFNLFFBQVEsR0FBRyxJQUFJLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQyxRQUFRLENBQUM7SUFDNUMsTUFBTSxXQUFXLEdBQUcsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixDQUFDLEdBQUcsUUFBUSxFQUFFLENBQUM7SUFFcEUsTUFBTSxLQUFLLEdBQVU7UUFDcEIsUUFBUTtRQUNSLElBQUk7UUFDSixHQUFHLEVBQUUsUUFBUTtRQUNiLElBQUksRUFBRSxRQUFRO1FBQ2QsV0FBVztRQUNYLFVBQVU7UUFDVixJQUFJO0tBQ0osQ0FBQztJQUVGLG1FQUFtRTtJQUNuRSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUU7UUFDM0IsS0FBSyxDQUFDLGtCQUFrQixHQUFHLElBQUksQ0FBQztLQUNoQztJQUVELE9BQU8sQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBRXpELE1BQU0sTUFBTSxHQUFHLElBQUkscUJBQVksQ0FBQyxFQUFFLFFBQVEsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLDJCQUEyQixDQUFFLEVBQUUsY0FBYyxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUM7SUFDckgsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsT0FBTyxDQUFDO0lBQ3JFLE1BQU0sSUFBQSxhQUFLLEVBQUMsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLGVBQWUsQ0FBQyxhQUFhLENBQUMsQ0FBQyxPQUFPLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFN0YsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUMxQixDQUFDO0FBRUQsSUFBSSxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRTtJQUNoQixPQUFPLENBQUMsR0FBRyxDQUFDLDRCQUE0QixDQUFDLENBQUM7SUFDMUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNqQixDQUFDLEVBQUUsR0FBRyxDQUFDLEVBQUU7SUFDUixPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ25CLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDakIsQ0FBQyxDQUFDLENBQUMifQ== \ No newline at end of file diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts index 2f3f27c1d5b..32d9c847c12 100644 --- a/build/azure-pipelines/common/createAsset.ts +++ b/build/azure-pipelines/common/createAsset.ts @@ -196,10 +196,12 @@ async function main(): Promise { }; const uploadPromises: Promise[] = []; + if (await blobClient.exists()) { - console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); + uploadPromises.push(Promise.reject(new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`))); } else { - uploadPromises.push(retry(async () => { + uploadPromises.push(retry(async (attempt) => { + console.log(`Uploading blobs to Azure storage (attempt ${attempt})...`); await blobClient.uploadFile(filePath, blobOptions); console.log('Blob successfully uploaded to Azure storage.'); })); @@ -214,26 +216,25 @@ async function main(): Promise { const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName); if (await mooncakeBlobClient.exists()) { - console.log(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`); + uploadPromises.push(Promise.reject(new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`))); } else { - uploadPromises.push(retry(async () => { + uploadPromises.push(retry(async (attempt) => { + console.log(`Uploading blobs to Mooncake Azure storage (attempt ${attempt})...`); await mooncakeBlobClient.uploadFile(filePath, blobOptions); console.log('Blob successfully uploaded to Mooncake Azure storage.'); })); } + } - if (uploadPromises.length) { - console.log('Uploading blobs to Azure storage and Mooncake Azure storage...'); - } - } else { - if (uploadPromises.length) { - console.log('Uploading blobs to Azure storage...'); + const promiseResults = await Promise.allSettled(uploadPromises); + + for (const result of promiseResults) { + if (result.status === 'rejected') { + throw result.reason; } } - await Promise.all(uploadPromises); - - console.log(uploadPromises.length ? 'All blobs successfully uploaded.' : 'No blobs to upload.'); + console.log('All blobs successfully uploaded.'); const assetUrl = `${process.env['AZURE_CDN_URL']}/${quality}/${blobName}`; const blobPath = new URL(assetUrl).pathname; diff --git a/build/azure-pipelines/common/retry.js b/build/azure-pipelines/common/retry.js index 5330d540ced..657f1a5b08d 100644 --- a/build/azure-pipelines/common/retry.js +++ b/build/azure-pipelines/common/retry.js @@ -9,7 +9,7 @@ async function retry(fn) { let lastError; for (let run = 1; run <= 10; run++) { try { - return await fn(); + return await fn(run); } catch (err) { if (!/ECONNRESET|CredentialUnavailableError|Audience validation failed/i.test(err.message)) { @@ -26,4 +26,4 @@ async function retry(fn) { throw lastError; } exports.retry = retry; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0cnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJyZXRyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUV6RixLQUFLLFVBQVUsS0FBSyxDQUFJLEVBQW9CO0lBQ2xELElBQUksU0FBNEIsQ0FBQztJQUVqQyxLQUFLLElBQUksR0FBRyxHQUFHLENBQUMsRUFBRSxHQUFHLElBQUksRUFBRSxFQUFFLEdBQUcsRUFBRSxFQUFFO1FBQ25DLElBQUk7WUFDSCxPQUFPLE1BQU0sRUFBRSxFQUFFLENBQUM7U0FDbEI7UUFBQyxPQUFPLEdBQUcsRUFBRTtZQUNiLElBQUksQ0FBQyxtRUFBbUUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxFQUFFO2dCQUMzRixNQUFNLEdBQUcsQ0FBQzthQUNWO1lBRUQsU0FBUyxHQUFHLEdBQUcsQ0FBQztZQUNoQixNQUFNLE1BQU0sR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUUsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO1lBQ2pFLE9BQU8sQ0FBQyxHQUFHLENBQUMsK0JBQStCLE1BQU0sT0FBTyxDQUFDLENBQUM7WUFFMUQsMENBQTBDO1lBQzFDLE1BQU0sSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7U0FDOUM7S0FDRDtJQUVELE9BQU8sQ0FBQyxHQUFHLENBQUMsNkJBQTZCLENBQUMsQ0FBQztJQUMzQyxNQUFNLFNBQVMsQ0FBQztBQUNqQixDQUFDO0FBdEJELHNCQXNCQyJ9 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0cnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJyZXRyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUV6RixLQUFLLFVBQVUsS0FBSyxDQUFJLEVBQW1DO0lBQ2pFLElBQUksU0FBNEIsQ0FBQztJQUVqQyxLQUFLLElBQUksR0FBRyxHQUFHLENBQUMsRUFBRSxHQUFHLElBQUksRUFBRSxFQUFFLEdBQUcsRUFBRSxFQUFFO1FBQ25DLElBQUk7WUFDSCxPQUFPLE1BQU0sRUFBRSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1NBQ3JCO1FBQUMsT0FBTyxHQUFHLEVBQUU7WUFDYixJQUFJLENBQUMsbUVBQW1FLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsRUFBRTtnQkFDM0YsTUFBTSxHQUFHLENBQUM7YUFDVjtZQUVELFNBQVMsR0FBRyxHQUFHLENBQUM7WUFDaEIsTUFBTSxNQUFNLEdBQUcsQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsR0FBRyxDQUFDLEdBQUcsQ0FBQyxFQUFFLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztZQUNqRSxPQUFPLENBQUMsR0FBRyxDQUFDLCtCQUErQixNQUFNLE9BQU8sQ0FBQyxDQUFDO1lBRTFELDBDQUEwQztZQUMxQyxNQUFNLElBQUksT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO1NBQzlDO0tBQ0Q7SUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLDZCQUE2QixDQUFDLENBQUM7SUFDM0MsTUFBTSxTQUFTLENBQUM7QUFDakIsQ0FBQztBQXRCRCxzQkFzQkMifQ== \ No newline at end of file diff --git a/build/azure-pipelines/common/retry.ts b/build/azure-pipelines/common/retry.ts index ed232245dec..9b28b4fd956 100644 --- a/build/azure-pipelines/common/retry.ts +++ b/build/azure-pipelines/common/retry.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export async function retry(fn: () => Promise): Promise { +export async function retry(fn: (attempt: number) => Promise): Promise { let lastError: Error | undefined; for (let run = 1; run <= 10; run++) { try { - return await fn(); + return await fn(run); } catch (err) { if (!/ECONNRESET|CredentialUnavailableError|Audience validation failed/i.test(err.message)) { throw err; From e9f17a5d77692de5f5050397f150ed6eb5fd7b54 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 12 Jul 2023 12:13:46 +0200 Subject: [PATCH 036/826] Fixes #187063 --- src/vs/editor/browser/editorBrowser.ts | 6 ++++++ src/vs/editor/browser/widget/codeEditorWidget.ts | 4 ++++ src/vs/editor/common/model/tokenizationTextModelPart.ts | 2 +- src/vs/monaco.d.ts | 5 +++++ .../contrib/files/browser/editors/textFileEditor.ts | 4 ++++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 2cd8aabfa0d..97752ae0bd5 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -1086,6 +1086,12 @@ export interface ICodeEditor extends editorCommon.IEditor { hasModel(): this is IActiveCodeEditor; setBanner(bannerDomNode: HTMLElement | null, height: number): void; + + /** + * Is called when the model has been set, view state was restored and options are updated. + * This is the best place to render UI. + */ + handleInitialized?(): void; } /** diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index e90726fe00c..0903e174700 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1021,6 +1021,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } } + public handleInitialized(): void { + this._getViewModel()?.visibleLinesStabilized(); + } + public onVisible(): void { this._modelData?.view.refreshFocusState(); } diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts index e077e75afe1..c203f53ad76 100644 --- a/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -642,7 +642,7 @@ class AttachedViewHandler extends Disposable { } private update(): void { - if (equals(this._computedLineRanges, this._lineRanges)) { + if (equals(this._computedLineRanges, this._lineRanges, (a, b) => a.equals(b))) { return; } this._computedLineRanges = this._lineRanges; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 9e2a863d592..60d3bd03976 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6065,6 +6065,11 @@ declare namespace monaco.editor { */ applyFontInfo(target: HTMLElement): void; setBanner(bannerDomNode: HTMLElement | null, height: number): void; + /** + * Is called when the model has been set, view state was restored and options are updated. + * This is the best place to render UI. + */ + handleInitialized?(): void; } /** diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 438accb72ad..44dc610e936 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -148,6 +148,10 @@ export class TextFileEditor extends AbstractTextCodeEditor // a resolved model might have more specific information about being // readonly or not that the input did not have. control.updateOptions(this.getReadonlyConfiguration(textFileModel.isReadonly())); + + if (control.handleInitialized) { + control.handleInitialized(); + } } catch (error) { await this.handleSetInputError(error, input, options); } From 16098b1fe1936f7f8c31d2ae655f6f88dc6fbf42 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 12 Jul 2023 12:31:31 +0200 Subject: [PATCH 037/826] Updates comment --- src/vs/editor/browser/editorBrowser.ts | 2 +- src/vs/monaco.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 97752ae0bd5..4d6c57410be 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -1089,7 +1089,7 @@ export interface ICodeEditor extends editorCommon.IEditor { /** * Is called when the model has been set, view state was restored and options are updated. - * This is the best place to render UI. + * This is the best place to compute data for the viewport (such as tokens). */ handleInitialized?(): void; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 60d3bd03976..770c1268f3a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6067,7 +6067,7 @@ declare namespace monaco.editor { setBanner(bannerDomNode: HTMLElement | null, height: number): void; /** * Is called when the model has been set, view state was restored and options are updated. - * This is the best place to render UI. + * This is the best place to compute data for the viewport (such as tokens). */ handleInitialized?(): void; } From ef9afc3b2bc6c238250449b2c947d5a2d2389593 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 12 Jul 2023 14:29:53 +0200 Subject: [PATCH 038/826] joh/inline progress (#187705) * allow to report progress when computing inline chat response, wip still needs better handling in the UI strategies * support for async, sequential progress * for now let extension know if progress/live is supported, for live modes apply edits as they happen --- src/vs/editor/common/languages.ts | 9 ++- src/vs/platform/progress/common/progress.ts | 20 ++++- .../api/browser/mainThreadInlineChat.ts | 23 ++++-- .../workbench/api/common/extHost.protocol.ts | 1 + .../workbench/api/common/extHostInlineChat.ts | 33 +++++++- .../browser/inlineChatController.ts | 76 ++++++++++++++----- .../inlineChat/browser/inlineChatSession.ts | 17 +++-- .../browser/inlineChatStrategies.ts | 19 ++--- .../inlineChat/browser/inlineChatWidget.ts | 6 +- .../contrib/inlineChat/common/inlineChat.ts | 10 ++- .../vscode.proposed.interactive.d.ts | 2 + 11 files changed, 170 insertions(+), 46 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 364998995e3..d589e8aa275 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -13,7 +13,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; +import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -1222,6 +1222,13 @@ export interface TextEdit { eol?: model.EndOfLineSequence; } +/** @internal */ +export abstract class TextEdit { + static asEditOperation(edit: TextEdit): ISingleEditOperation { + return EditOperation.replace(Range.lift(edit.range), edit.text); + } +} + /** * Interface used to format a model */ diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index ec7b7db6d71..ea3e1fd76a7 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -111,15 +111,31 @@ export class Progress implements IProgress { static readonly None = Object.freeze>({ report() { } }); + report: (item: T) => void; + private _value?: T; get value(): T | undefined { return this._value; } - constructor(private callback: (data: T) => void) { } + private _lastTask?: Promise; - report(item: T) { + constructor(private callback: (data: T) => unknown, opts?: { async?: boolean }) { + this.report = opts?.async + ? this._reportAsync.bind(this) + : this._reportSync.bind(this); + } + + private _reportSync(item: T) { this._value = item; this.callback(this._value); } + + private _reportAsync(item: T) { + Promise.resolve(this._lastTask).finally(() => { + this._value = item; + const r = this.callback(this._value); + this._lastTask = Promise.resolve(r).finally(() => this._lastTask = undefined); + }); + } } /** diff --git a/src/vs/workbench/api/browser/mainThreadInlineChat.ts b/src/vs/workbench/api/browser/mainThreadInlineChat.ts index cf46e2db504..ef98b339844 100644 --- a/src/vs/workbench/api/browser/mainThreadInlineChat.ts +++ b/src/vs/workbench/api/browser/mainThreadInlineChat.ts @@ -9,6 +9,8 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { reviveWorkspaceEditDto } from 'vs/workbench/api/browser/mainThreadBulkEdits'; import { ExtHostContext, ExtHostInlineChatShape, MainContext, MainThreadInlineChatShape as MainThreadInlineChatShape } from 'vs/workbench/api/common/extHost.protocol'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; +import { TextEdit } from 'vs/editor/common/languages'; +import { IProgress } from 'vs/platform/progress/common/progress'; @extHostNamedCustomer(MainContext.MainThreadInlineChat) export class MainThreadInlineChat implements MainThreadInlineChatShape { @@ -16,6 +18,8 @@ export class MainThreadInlineChat implements MainThreadInlineChatShape { private readonly _registrations = new DisposableMap(); private readonly _proxy: ExtHostInlineChatShape; + private readonly _progresses = new Map>(); + constructor( extHostContext: IExtHostContext, @IInlineChatService private readonly _inlineChatService: IInlineChatService, @@ -43,12 +47,17 @@ export class MainThreadInlineChat implements MainThreadInlineChatShape { } }; }, - provideResponse: async (item, request, token) => { - const result = await this._proxy.$provideResponse(handle, item, request, token); - if (result?.type === 'bulkEdit') { - (result).edits = reviveWorkspaceEditDto(result.edits, this._uriIdentService); + provideResponse: async (item, request, progress, token) => { + this._progresses.set(request.requestId, progress); + try { + const result = await this._proxy.$provideResponse(handle, item, request, token); + if (result?.type === 'bulkEdit') { + (result).edits = reviveWorkspaceEditDto(result.edits, this._uriIdentService); + } + return result; + } finally { + this._progresses.delete(request.requestId); } - return result; }, handleInlineChatResponseFeedback: !supportsFeedback ? undefined : async (session, response, kind) => { this._proxy.$handleFeedback(handle, session.id, response.id, kind); @@ -58,6 +67,10 @@ export class MainThreadInlineChat implements MainThreadInlineChatShape { this._registrations.set(handle, unreg); } + async $handleProgressChunk(requestId: string, chunk: { message?: string | undefined; edits?: TextEdit[] | undefined }): Promise { + this._progresses.get(requestId)?.report(chunk); + } + async $unregisterInteractiveEditorProvider(handle: number): Promise { this._registrations.deleteAndDispose(handle); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 413980ad9b3..3471b4cecaf 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1121,6 +1121,7 @@ export interface MainThreadInteractiveShape extends IDisposable { export interface MainThreadInlineChatShape extends IDisposable { $registerInteractiveEditorProvider(handle: number, debugName: string, supportsFeedback: boolean): Promise; + $handleProgressChunk(requestId: string, chunk: { message?: string; edits?: languages.TextEdit[] }): Promise; $unregisterInteractiveEditorProvider(handle: number): Promise; } diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index 1a5e5e9eba4..3e706f12286 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -139,13 +139,42 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { return; } - const res = await entry.provider.provideInteractiveEditorResponse({ + const apiRequest: vscode.InteractiveEditorRequest = { session: sessionData.session, prompt: request.prompt, selection: typeConvert.Selection.to(request.selection), wholeRange: typeConvert.Range.to(request.wholeRange), attempt: request.attempt, - }, token); + live: request.live, + }; + + + let done = false; + const progress: vscode.Progress<{ message?: string; edits?: vscode.TextEdit[] }> = { + report: value => { + if (!request.live) { + throw new Error('Progress reporting is only supported for live sessions'); + } + if (done || token.isCancellationRequested) { + return; + } + if (!value.message && !value.edits) { + return; + } + this._proxy.$handleProgressChunk(request.requestId, { + message: value.message, + edits: value.edits?.map(typeConvert.TextEdit.from) + }); + } + }; + + const task = typeof entry.provider.provideInteractiveEditorResponse2 === 'function' + ? entry.provider.provideInteractiveEditorResponse2(apiRequest, progress, token) + : entry.provider.provideInteractiveEditorResponse(apiRequest, token); + + Promise.resolve(task).finally(() => done = true); + + const res = await task; if (res) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index d24cbbe0ab8..0902ba579f8 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -13,7 +13,6 @@ import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'v import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; @@ -31,11 +30,14 @@ import { ILogService } from 'vs/platform/log/common/log'; import { EditResponse, EmptyResponse, ErrorResponse, ExpansionState, IInlineChatSessionService, MarkdownResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { EditModeStrategy, LivePreviewStrategy, LiveStrategy, PreviewStrategy } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, EditMode, InlineChatResponseFeedbackKind, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, InlineChatResponseType, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, InlineChateResponseTypes, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_USER_DID_EDIT } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, EditMode, InlineChatResponseFeedbackKind, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, InlineChatResponseType, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, InlineChateResponseTypes, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_USER_DID_EDIT, IInlineChatProgressItem } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Lazy } from 'vs/base/common/lazy'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { generateUuid } from 'vs/base/common/uuid'; +import { TextEdit } from 'vs/editor/common/languages'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -465,13 +467,31 @@ export class InlineChatController implements IEditorContribution { const sw = StopWatch.create(); const request: IInlineChatRequest = { + requestId: generateUuid(), prompt: this._activeSession.lastInput.value, attempt: this._activeSession.lastInput.attempt, selection: this._editor.getSelection(), wholeRange: this._activeSession.wholeRange.value, + live: this._activeSession.editMode !== EditMode.Preview // TODO@jrieken let extension know what document is used for previewing }; this._chatAccessibilityService.acceptRequest(); - const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, requestCts.token); + + const progressEdits: TextEdit[][] = []; + const progress = new Progress(async data => { + this._log('received chunk', data, request); + if (!request.live) { + throw new Error('Progress in NOT supported in non-live mode'); + } + if (data.message) { + this._zone.value.widget.updateToolbar(false); + this._zone.value.widget.updateInfo(data.message); + } + if (data.edits) { + progressEdits.push(data.edits); + await this._makeChanges(progressEdits); + } + }, { async: true }); + const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, requestCts.token); this._log('request started', this._activeSession.provider.debugName, this._activeSession.session, request); let response: EditResponse | MarkdownResponse | ErrorResponse | EmptyResponse; @@ -482,10 +502,14 @@ export class InlineChatController implements IEditorContribution { this._ctxHasActiveRequest.set(true); reply = await raceCancellationError(Promise.resolve(task), requestCts.token); - if (reply?.type === 'message') { + if (reply?.type === InlineChatResponseType.Message) { response = new MarkdownResponse(this._activeSession.textModelN.uri, reply); } else if (reply) { - response = new EditResponse(this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), reply); + const editResponse = new EditResponse(this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), reply, progressEdits); + if (editResponse.allLocalEdits.length > progressEdits.length) { + await this._makeChanges(editResponse.allLocalEdits); + } + response = editResponse; } else { response = new EmptyResponse(); } @@ -531,27 +555,41 @@ export class InlineChatController implements IEditorContribution { if (!canContinue) { return State.ACCEPT; } - const moreMinimalEdits = (await this._editorWorkerService.computeHumanReadableDiff(this._activeSession.textModelN.uri, response.localEdits)); - const editOperations = (moreMinimalEdits ?? response.localEdits).map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); - this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, response.localEdits, moreMinimalEdits); + } + return State.SHOW_RESPONSE; + } + private async _makeChanges(allEdits: TextEdit[][]) { + assertType(this._activeSession); + assertType(this._strategy); + + if (allEdits.length === 0) { + return; + } + + // diff-changes from model0 -> modelN+1 + for (const edits of allEdits) { const textModelNplus1 = this._modelService.createModel(createTextBufferFactoryFromSnapshot(this._activeSession.textModelN.createSnapshot()), null, undefined, true); - textModelNplus1.applyEdits(editOperations); + textModelNplus1.applyEdits(edits.map(TextEdit.asEditOperation)); const diff = await this._editorWorkerService.computeDiff(this._activeSession.textModel0.uri, textModelNplus1.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); this._activeSession.lastTextModelChanges = diff?.changes ?? []; textModelNplus1.dispose(); - - try { - this._ignoreModelContentChanged = true; - this._activeSession.wholeRange.trackEdits(editOperations); - await this._strategy.makeChanges(editOperations); - this._ctxDidEdit.set(this._activeSession.hasChangedText); - } finally { - this._ignoreModelContentChanged = false; - } } - return State.SHOW_RESPONSE; + // make changes from modelN -> modelN+1 + const lastEdits = allEdits[allEdits.length - 1]; + const moreMinimalEdits = await this._editorWorkerService.computeHumanReadableDiff(this._activeSession.textModelN.uri, lastEdits); + const editOperations = (moreMinimalEdits ?? lastEdits).map(TextEdit.asEditOperation); + this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, lastEdits, moreMinimalEdits); + + try { + this._ignoreModelContentChanged = true; + this._activeSession.wholeRange.trackEdits(editOperations); + await this._strategy.makeChanges(editOperations); + this._ctxDidEdit.set(this._activeSession.hasChangedText); + } finally { + this._ignoreModelContentChanged = false; + } } private async [State.SHOW_RESPONSE](): Promise { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 83aa7b064ff..1e524f37f9d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -296,7 +296,7 @@ export class MarkdownResponse { export class EditResponse { - readonly localEdits: TextEdit[] = []; + readonly allLocalEdits: TextEdit[][] = []; readonly singleCreateFileEdit: { uri: URI; edits: Promise[] } | undefined; readonly workspaceEdits: ResourceEdit[] | undefined; readonly workspaceEditsIncludeLocalEdits: boolean = false; @@ -304,11 +304,15 @@ export class EditResponse { constructor( localUri: URI, readonly modelAltVersionId: number, - readonly raw: IInlineChatBulkEditResponse | IInlineChatEditResponse + readonly raw: IInlineChatBulkEditResponse | IInlineChatEditResponse, + progressEdits: TextEdit[][], ) { + + this.allLocalEdits.push(...progressEdits); + if (raw.type === 'editorEdit') { // - this.localEdits = raw.edits; + this.allLocalEdits.push(raw.edits); this.singleCreateFileEdit = undefined; this.workspaceEdits = undefined; @@ -318,6 +322,7 @@ export class EditResponse { this.workspaceEdits = edits; let isComplexEdit = false; + const localEdits: TextEdit[] = []; for (const edit of edits) { if (edit instanceof ResourceFileEdit) { @@ -336,7 +341,7 @@ export class EditResponse { } else if (edit instanceof ResourceTextEdit) { // if (isEqual(edit.resource, localUri)) { - this.localEdits.push(edit.textEdit); + localEdits.push(edit.textEdit); this.workspaceEditsIncludeLocalEdits = true; } else if (isEqual(this.singleCreateFileEdit?.uri, edit.resource)) { @@ -346,7 +351,9 @@ export class EditResponse { } } } - + if (localEdits.length > 0) { + this.allLocalEdits.push(localEdits); + } if (isComplexEdit) { this.singleCreateFileEdit = undefined; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 36e64ebf09a..f4f54307d65 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -9,11 +9,11 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; -import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; +import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; +import { TextEdit } from 'vs/editor/common/languages'; import { ICursorStateComputer, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { localize } from 'vs/nls'; @@ -102,8 +102,9 @@ export class PreviewStrategy extends EditModeStrategy { if (modelN.equalsTextBuffer(this._session.textModel0.getTextBuffer())) { modelN.pushStackElement(); - const edits = editResponse.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); - modelN.pushEditOperations(null, edits, () => null); + for (const edits of editResponse.allLocalEdits) { + modelN.pushEditOperations(null, edits.map(TextEdit.asEditOperation), () => null); + } modelN.pushStackElement(); } } @@ -122,9 +123,9 @@ export class PreviewStrategy extends EditModeStrategy { } override async renderChanges(response: EditResponse): Promise { - if (response.localEdits.length > 0) { - const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); - this._widget.showEditsPreview(this._session.textModel0, edits, this._session.lastTextModelChanges); + if (response.allLocalEdits.length > 0) { + const allEditOperation = response.allLocalEdits.map(edits => edits.map(TextEdit.asEditOperation)); + this._widget.showEditsPreview(this._session.textModel0, allEditOperation, this._session.lastTextModelChanges); } else { this._widget.hideEditsPreview(); } @@ -297,7 +298,7 @@ export class LiveStrategy extends EditModeStrategy { LiveStrategy._undoModelUntil(modelN, targetAltVersion); } - override async makeChanges(edits: ISingleEditOperation[], ignoreInlineDiff?: boolean): Promise { + override async makeChanges(edits: ISingleEditOperation[]): Promise { const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => { let last: Position | null = null; for (const edit of undoEdits) { @@ -311,7 +312,7 @@ export class LiveStrategy extends EditModeStrategy { if (++this._editCount === 1) { this._editor.pushUndoStop(); } - this._editor.executeEdits('inline-chat-live', edits, ignoreInlineDiff ? undefined : cursorStateComputerAndInlineDiffCollection); + this._editor.executeEdits('inline-chat-live', edits, cursorStateComputerAndInlineDiffCollection); } override async undoChanges(response: EditResponse): Promise { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index adb0b77635e..57077181753 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -535,7 +535,7 @@ export class InlineChatWidget { // --- preview - showEditsPreview(textModelv0: ITextModel, edits: ISingleEditOperation[], changes: readonly LineRangeMapping[]) { + showEditsPreview(textModelv0: ITextModel, allEdits: ISingleEditOperation[][], changes: readonly LineRangeMapping[]) { if (changes.length === 0) { this.hideEditsPreview(); return; @@ -545,7 +545,9 @@ export class InlineChatWidget { const languageSelection: ILanguageSelection = { languageId: textModelv0.getLanguageId(), onDidChange: Event.None }; const modified = this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModelv0.createSnapshot()), languageSelection, undefined, true); - modified.applyEdits(edits, false); + for (const edits of allEdits) { + modified.applyEdits(edits, false); + } this._previewDiffEditor.value.setModel({ original: textModelv0, modified }); // joined ranges diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index e8bbb5cb940..5784ffcf838 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -15,6 +15,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProgress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; @@ -39,6 +40,8 @@ export interface IInlineChatRequest { selection: ISelection; wholeRange: IRange; attempt: number; + requestId: string; + live: boolean; } export type IInlineChatResponse = IInlineChatEditResponse | IInlineChatBulkEditResponse | IInlineChatMessageResponse; @@ -79,6 +82,11 @@ export interface IInlineChatMessageResponse { wholeRange?: IRange; } +export interface IInlineChatProgressItem { + edits?: TextEdit[]; + message?: string; +} + export const enum InlineChatResponseFeedbackKind { Unhelpful = 0, Helpful = 1, @@ -91,7 +99,7 @@ export interface IInlineChatSessionProvider { prepareInlineChatSession(model: ITextModel, range: ISelection, token: CancellationToken): ProviderResult; - provideResponse(item: IInlineChatSession, request: IInlineChatRequest, token: CancellationToken): ProviderResult; + provideResponse(item: IInlineChatSession, request: IInlineChatRequest, progress: IProgress, token: CancellationToken): ProviderResult; handleInlineChatResponseFeedback?(session: IInlineChatSession, response: IInlineChatResponse, kind: InlineChatResponseFeedbackKind): void; } diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 79d6db91573..e8fefb4ba74 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -28,6 +28,7 @@ declare module 'vscode' { selection: Selection; wholeRange: Range; attempt: number; + live: boolean; } // todo@API make classes @@ -61,6 +62,7 @@ declare module 'vscode' { prepareInteractiveEditorSession(context: TextDocumentContext, token: CancellationToken): ProviderResult; provideInteractiveEditorResponse(request: InteractiveEditorRequest, token: CancellationToken): ProviderResult; + provideInteractiveEditorResponse2?(request: InteractiveEditorRequest, progress: Progress<{ message: string; edits: TextEdit[] }>, token: CancellationToken): ProviderResult; // eslint-disable-next-line local/vscode-dts-provider-naming releaseInteractiveEditorSession?(session: S): any; From 6bad769697ec3e4ad65287d9345624328377fdd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 12 Jul 2023 14:36:51 +0200 Subject: [PATCH 039/826] make sure publishing is the last thing in each pipeline (#187708) this fixes a rare issue in which the second Generate SBOM task will fail, so a second attempt at running the pipeline will fail when publishing the first Publish SBOM task, since it would already have been published --- build/azure-pipelines/darwin/product-build-darwin.yml | 8 ++++---- build/azure-pipelines/linux/product-build-linux.yml | 8 ++++---- build/azure-pipelines/win32/product-build-win32.yml | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 054a3caec37..3d8e6dc67b8 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -212,16 +212,16 @@ steps: BuildDropPath: $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) PackageName: Visual Studio Code - - publish: $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)/_manifest - displayName: Publish SBOM (client) - artifact: $(ARTIFACT_PREFIX)sbom_client_darwin_$(VSCODE_ARCH)_sbom - - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: Generate SBOM (server) inputs: BuildDropPath: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) PackageName: Visual Studio Code Server + - publish: $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)/_manifest + displayName: Publish SBOM (client) + artifact: $(ARTIFACT_PREFIX)sbom_client_darwin_$(VSCODE_ARCH)_sbom + - publish: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (server) artifact: $(ARTIFACT_PREFIX)sbom_server_darwin_$(VSCODE_ARCH)_sbom diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 87f4cd800da..99bbd4ca6bd 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -290,16 +290,16 @@ steps: BuildDropPath: $(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) PackageName: Visual Studio Code - - publish: $(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/_manifest - displayName: Publish SBOM (client) - artifact: $(ARTIFACT_PREFIX)sbom_vscode_client_linux_$(VSCODE_ARCH) - - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: Generate SBOM (server) inputs: BuildDropPath: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) PackageName: Visual Studio Code Server + - publish: $(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/_manifest + displayName: Publish SBOM (client) + artifact: $(ARTIFACT_PREFIX)sbom_vscode_client_linux_$(VSCODE_ARCH) + - publish: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (server) artifact: $(ARTIFACT_PREFIX)sbom_vscode_server_linux_$(VSCODE_ARCH) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 9cadfab45a0..6356978d40b 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -312,10 +312,6 @@ steps: BuildDropPath: $(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH) PackageName: Visual Studio Code - - publish: $(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)/_manifest - displayName: Publish SBOM (client) - artifact: $(ARTIFACT_PREFIX)sbom_client_win32_$(VSCODE_ARCH) - - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: Generate SBOM (server) inputs: @@ -323,6 +319,10 @@ steps: PackageName: Visual Studio Code Server condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) + - publish: $(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)/_manifest + displayName: Publish SBOM (client) + artifact: $(ARTIFACT_PREFIX)sbom_client_win32_$(VSCODE_ARCH) + - publish: $(agent.builddirectory)/vscode-server-win32-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (server) artifact: $(ARTIFACT_PREFIX)sbom_server_win32_$(VSCODE_ARCH) From 1864dfdc91701ec15c68bd565893cc1f689ec1fd Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 12 Jul 2023 15:01:15 +0200 Subject: [PATCH 040/826] Fixes #186675 (#187710) * Fixes #186675 * Don't show screen reader hover when the text is read automatically. --- .../browser/hoverParticipant.ts | 2 +- .../browser/inlineCompletionsController.ts | 4 ---- .../browser/inlineCompletionsModel.ts | 20 ++++++------------- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts index efa99199a6a..1d74b99cb86 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts @@ -103,7 +103,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan comment: 'This event tracks whenever an inline completion hover is shown.'; }>('inlineCompletionHover.shown'); - if (this.accessibilityService.isScreenReaderOptimized()) { + if (this.accessibilityService.isScreenReaderOptimized() && !this._editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { this.renderScreenReaderText(context, part, disposableStore); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index 910f4b678eb..e8699277b34 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -179,10 +179,6 @@ export class InlineCompletionsController extends Disposable { if (state.completion.semanticId !== lastInlineCompletionId) { lastInlineCompletionId = state.completion.semanticId; - if (model.isNavigatingCurrentInlineCompletion) { - return; - } - this.audioCueService.playAudioCue(AudioCue.inlineSuggestion).then(() => { if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 01946f09986..05d6de2fed1 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -44,9 +44,6 @@ export class InlineCompletionsModel extends Disposable { private _isAcceptingPartially = false; public get isAcceptingPartially() { return this._isAcceptingPartially; } - private _isNavigatingCurrentInlineCompletion = false; - public get isNavigatingCurrentInlineCompletion() { return this._isNavigatingCurrentInlineCompletion; } - constructor( public readonly textModel: ITextModel, public readonly selectedSuggestItem: IObservable, @@ -251,17 +248,12 @@ export class InlineCompletionsModel extends Disposable { private async _deltaSelectedInlineCompletionIndex(delta: 1 | -1): Promise { await this.triggerExplicitly(); - this._isNavigatingCurrentInlineCompletion = true; - try { - const completions = this._filteredInlineCompletionItems.get() || []; - if (completions.length > 0) { - const newIdx = (this.selectedInlineCompletionIndex.get() + delta + completions.length) % completions.length; - this._selectedInlineCompletionId.set(completions[newIdx].semanticId, undefined); - } else { - this._selectedInlineCompletionId.set(undefined, undefined); - } - } finally { - this._isNavigatingCurrentInlineCompletion = false; + const completions = this._filteredInlineCompletionItems.get() || []; + if (completions.length > 0) { + const newIdx = (this.selectedInlineCompletionIndex.get() + delta + completions.length) % completions.length; + this._selectedInlineCompletionId.set(completions[newIdx].semanticId, undefined); + } else { + this._selectedInlineCompletionId.set(undefined, undefined); } } From 2fce05e8b33083d2067727e15490655a6998b53a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 12 Jul 2023 15:08:25 +0200 Subject: [PATCH 041/826] initial code --- .../inlineChat/browser/inlineChatActions.ts | 6 ++--- .../browser/inlineChatStrategies.ts | 22 ++++++++++++++----- .../inlineChat/browser/inlineChatWidget.ts | 19 +++++++++++++--- .../contrib/inlineChat/common/inlineChat.ts | 12 ++++++++++ 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index b5cf33298e6..6772dcbaef7 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -10,7 +10,7 @@ import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_SHOWING_DIFF, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, InlineChatResponseType, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChateResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_SHOWING_DIFF, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, InlineChatResponseType, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChateResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, MENU_INLINE_CHAT_WIDGET_TOGGLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize } from 'vs/nls'; import { IAction2Options, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -452,10 +452,10 @@ export class ToggleInlineDiff extends AbstractInlineChatAction { id: 'inlineChat.toggleDiff', title: localize('toggleDiff', 'Toggle Diff'), icon: Codicon.diff, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_DID_EDIT), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE), toggled: { condition: CTX_INLINE_CHAT_SHOWING_DIFF, title: localize('toggleDiff2', "Show Inline Diff") }, menu: { - id: MENU_INLINE_CHAT_WIDGET_DISCARD, + id: MENU_INLINE_CHAT_WIDGET_TOGGLE, when: CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview), group: '1_config', order: 9 diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 36e64ebf09a..31351a13c3d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; @@ -17,9 +17,10 @@ import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { ICursorStateComputer, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { InlineChatFileCreatePreviewWidget, InlineChatLivePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget'; import { EditResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; @@ -222,11 +223,12 @@ class InlineDiffDecorations { export class LiveStrategy extends EditModeStrategy { - private static _inlineDiffStorageKey: string = 'interactiveEditor.storage.inlineDiff'; protected _diffEnabled: boolean = false; private readonly _inlineDiffDecorations: InlineDiffDecorations; private readonly _ctxShowingDiff: IContextKey; + private readonly _store: DisposableStore = new DisposableStore(); + private _lastResponse?: EditResponse; private _editCount: number = 0; @@ -235,29 +237,36 @@ export class LiveStrategy extends EditModeStrategy { protected readonly _editor: ICodeEditor, protected readonly _widget: InlineChatWidget, @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configService: IConfigurationService, @IStorageService protected _storageService: IStorageService, @IBulkEditService protected readonly _bulkEditService: IBulkEditService, @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, @IInstantiationService private readonly _instaService: IInstantiationService, ) { super(); - this._diffEnabled = _storageService.getBoolean(LiveStrategy._inlineDiffStorageKey, StorageScope.PROFILE, true); + this._diffEnabled = configService.getValue('inlineChat.showDiff'); this._inlineDiffDecorations = new InlineDiffDecorations(this._editor, this._diffEnabled); this._ctxShowingDiff = CTX_INLINE_CHAT_SHOWING_DIFF.bindTo(contextKeyService); this._ctxShowingDiff.set(this._diffEnabled); this._inlineDiffDecorations.visible = this._diffEnabled; + + this._store.add(configService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('inlineChat.showDiff')) { + this.toggleDiff(); + } + })); } override dispose(): void { this._inlineDiffDecorations.clear(); this._ctxShowingDiff.reset(); + this._store.dispose(); } toggleDiff(): void { this._diffEnabled = !this._diffEnabled; this._ctxShowingDiff.set(this._diffEnabled); - this._storageService.store(LiveStrategy._inlineDiffStorageKey, this._diffEnabled, StorageScope.PROFILE, StorageTarget.USER); this._doToggleDiff(); } @@ -384,12 +393,13 @@ export class LivePreviewStrategy extends LiveStrategy { editor: ICodeEditor, widget: InlineChatWidget, @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configService: IConfigurationService, @IStorageService storageService: IStorageService, @IBulkEditService bulkEditService: IBulkEditService, @IEditorWorkerService editorWorkerService: IEditorWorkerService, @IInstantiationService instaService: IInstantiationService, ) { - super(session, editor, widget, contextKeyService, storageService, bulkEditService, editorWorkerService, instaService); + super(session, editor, widget, contextKeyService, configService, storageService, bulkEditService, editorWorkerService, instaService); this._diffZone = new Lazy(() => instaService.createInstance(InlineChatLivePreviewWidget, editor, session)); this._previewZone = new Lazy(() => instaService.createInstance(InlineChatFileCreatePreviewWidget, editor)); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index adb0b77635e..3218470917d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -12,9 +12,9 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, MENU_INLINE_CHAT_WIDGET_TOGGLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; +import { EventType, Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -49,6 +49,7 @@ import { ExpansionState } from 'vs/workbench/contrib/inlineChat/browser/inlineCh import { IdleValue } from 'vs/base/common/async'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IMenuWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -189,7 +190,8 @@ export class InlineChatWidget { @IKeybindingService private readonly _keybindingService: IKeybindingService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, ) { // input editor logic @@ -328,6 +330,17 @@ export class InlineChatWidget { const markdownMessageToolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.messageActions, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, workbenchToolbarOptions); this._store.add(markdownMessageToolbar.onDidChangeMenuItems(() => this._onDidChangeHeight.fire())); this._store.add(markdownMessageToolbar); + + this._store.add(addDisposableListener(this._elements.root, EventType.CONTEXT_MENU, async (event: MouseEvent) => { + this.onContextMenu(event); + })); + } + + private onContextMenu(event: MouseEvent) { + this._contextMenuService.showContextMenu({ + menuId: MENU_INLINE_CHAT_WIDGET_TOGGLE, + getAnchor: () => event, + }); } private _updateAriaLabel(): void { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index e8bbb5cb940..2c7d8c4a708 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -140,6 +140,7 @@ export const MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE = MenuId.for('inlineChatWi export const MENU_INLINE_CHAT_WIDGET_STATUS = MenuId.for('inlineChatWidget.status'); export const MENU_INLINE_CHAT_WIDGET_FEEDBACK = MenuId.for('inlineChatWidget.feedback'); export const MENU_INLINE_CHAT_WIDGET_DISCARD = MenuId.for('inlineChatWidget.undo'); +export const MENU_INLINE_CHAT_WIDGET_TOGGLE = MenuId.for('inlineChatWidget.toggle'); // --- colors @@ -188,3 +189,14 @@ Registry.as(Extensions.Configuration).registerConfigurat } } }); + +Registry.as(Extensions.Configuration).registerConfiguration({ + id: 'editor', + properties: { + 'inlineChat.showDiff': { + description: localize('showDiff', "Enable/disable showing the diff when edits are generated."), + default: true, + type: 'boolean' + } + } +}); From d405515685c70de946cdcdcfac4fc90a99965b3a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 12 Jul 2023 15:27:45 +0200 Subject: [PATCH 042/826] instead of using the toggle diff method, we update directly the value in the settings --- .../inlineChat/browser/inlineChatActions.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 6772dcbaef7..62a20bff378 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -30,6 +30,8 @@ import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/brow import { Disposable } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { Position } from 'vs/editor/common/core/position'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); @@ -453,18 +455,22 @@ export class ToggleInlineDiff extends AbstractInlineChatAction { title: localize('toggleDiff', 'Toggle Diff'), icon: Codicon.diff, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE), - toggled: { condition: CTX_INLINE_CHAT_SHOWING_DIFF, title: localize('toggleDiff2', "Show Inline Diff") }, + category: Categories.View, + toggled: { + condition: CTX_INLINE_CHAT_SHOWING_DIFF, + title: localize('toggleDiff2', "Show Inline Diff") + }, menu: { id: MENU_INLINE_CHAT_WIDGET_TOGGLE, - when: CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview), - group: '1_config', - order: 9 + when: CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview) } }); } - override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController): void { - ctrl.toggleDiff(); + override runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController): void { + const configurationService = accessor.get(IConfigurationService); + const newValue = !configurationService.getValue('inlineChat.showDiff'); + configurationService.updateValue('inlineChat.showDiff', newValue); } } From 55cabc414bef2d0fa0611c2a43bccbd9a7790746 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 12 Jul 2023 15:31:08 +0200 Subject: [PATCH 043/826] no longer need some of the toggle diff methods --- .../inlineChat/browser/inlineChatController.ts | 4 ---- .../inlineChat/browser/inlineChatStrategies.ts | 16 +++------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index d24cbbe0ab8..a8b00560768 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -713,10 +713,6 @@ export class InlineChatController implements IEditorContribution { } } - toggleDiff(): void { - this._strategy?.toggleDiff(); - } - focus(): void { this._zone.value.widget.focus(); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 31351a13c3d..c005e7d08b3 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -43,8 +43,6 @@ export abstract class EditModeStrategy { abstract renderChanges(response: EditResponse): Promise; - abstract toggleDiff(): void; - abstract hasFocus(): boolean; abstract getWidgetPosition(): Position | undefined; @@ -137,10 +135,6 @@ export class PreviewStrategy extends EditModeStrategy { } } - toggleDiff(): void { - // nothing to do - } - getWidgetPosition(): Position | undefined { return; } @@ -253,7 +247,9 @@ export class LiveStrategy extends EditModeStrategy { this._store.add(configService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('inlineChat.showDiff')) { - this.toggleDiff(); + this._diffEnabled = !this._diffEnabled; + this._ctxShowingDiff.set(this._diffEnabled); + this._doToggleDiff(); } })); } @@ -264,12 +260,6 @@ export class LiveStrategy extends EditModeStrategy { this._store.dispose(); } - toggleDiff(): void { - this._diffEnabled = !this._diffEnabled; - this._ctxShowingDiff.set(this._diffEnabled); - this._doToggleDiff(); - } - protected _doToggleDiff(): void { this._inlineDiffDecorations.visible = this._diffEnabled; } From 5862acd27af7d5ee138403c463410a04f900e1f3 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 12 Jul 2023 15:51:29 +0200 Subject: [PATCH 044/826] looks like now the toggling works as expected --- .../inlineChat/browser/inlineChatActions.ts | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 62a20bff378..150b7980f3a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -12,7 +12,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_SHOWING_DIFF, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, InlineChatResponseType, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChateResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, MENU_INLINE_CHAT_WIDGET_TOGGLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize } from 'vs/nls'; -import { IAction2Options, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { IAction2Options, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -452,18 +452,21 @@ export class ToggleInlineDiff extends AbstractInlineChatAction { constructor() { super({ id: 'inlineChat.toggleDiff', - title: localize('toggleDiff', 'Toggle Diff'), - icon: Codicon.diff, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE), + title: { + value: localize('toggleDiff', 'Toggle Diff'), + mnemonicTitle: localize({ key: 'miToggleDiff', comment: ['&& denotes a mnemonic'] }, "&&Toggle Diff"), + original: 'Toggle Diff', + }, category: Categories.View, toggled: { - condition: CTX_INLINE_CHAT_SHOWING_DIFF, - title: localize('toggleDiff2', "Show Inline Diff") + condition: ContextKeyExpr.equals('config.inlineChat.showDiff', true), + title: localize('toggleDiff2', "Toggle Diff"), + mnemonicTitle: localize({ key: 'miToggleDiff2', comment: ['&& denotes a mnemonic'] }, "&&Toggle Diff") }, - menu: { - id: MENU_INLINE_CHAT_WIDGET_TOGGLE, - when: CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview) - } + menu: [ + { id: MenuId.CommandPalette }, + { id: MENU_INLINE_CHAT_WIDGET_TOGGLE } + ] }); } @@ -471,6 +474,7 @@ export class ToggleInlineDiff extends AbstractInlineChatAction { const configurationService = accessor.get(IConfigurationService); const newValue = !configurationService.getValue('inlineChat.showDiff'); configurationService.updateValue('inlineChat.showDiff', newValue); + console.log('newValue : ', newValue); } } From 2a65810af61cef88f0e12295e07709802b391c58 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 12 Jul 2023 16:09:07 +0200 Subject: [PATCH 045/826] cleaning up the code --- .../contrib/inlineChat/browser/inlineChatActions.ts | 3 +-- .../contrib/inlineChat/browser/inlineChatStrategies.ts | 7 +------ src/vs/workbench/contrib/inlineChat/common/inlineChat.ts | 1 - 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 150b7980f3a..4db24fc6c50 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -10,7 +10,7 @@ import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_SHOWING_DIFF, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, InlineChatResponseType, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChateResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, MENU_INLINE_CHAT_WIDGET_TOGGLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, InlineChatResponseType, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChateResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, MENU_INLINE_CHAT_WIDGET_TOGGLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize } from 'vs/nls'; import { IAction2Options, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -474,7 +474,6 @@ export class ToggleInlineDiff extends AbstractInlineChatAction { const configurationService = accessor.get(IConfigurationService); const newValue = !configurationService.getValue('inlineChat.showDiff'); configurationService.updateValue('inlineChat.showDiff', newValue); - console.log('newValue : ', newValue); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index c005e7d08b3..341e4d49725 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -24,7 +24,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { InlineChatFileCreatePreviewWidget, InlineChatLivePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget'; import { EditResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { CTX_INLINE_CHAT_SHOWING_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_DOCUMENT_CHANGED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; export abstract class EditModeStrategy { @@ -220,7 +220,6 @@ export class LiveStrategy extends EditModeStrategy { protected _diffEnabled: boolean = false; private readonly _inlineDiffDecorations: InlineDiffDecorations; - private readonly _ctxShowingDiff: IContextKey; private readonly _store: DisposableStore = new DisposableStore(); private _lastResponse?: EditResponse; @@ -241,14 +240,11 @@ export class LiveStrategy extends EditModeStrategy { this._diffEnabled = configService.getValue('inlineChat.showDiff'); this._inlineDiffDecorations = new InlineDiffDecorations(this._editor, this._diffEnabled); - this._ctxShowingDiff = CTX_INLINE_CHAT_SHOWING_DIFF.bindTo(contextKeyService); - this._ctxShowingDiff.set(this._diffEnabled); this._inlineDiffDecorations.visible = this._diffEnabled; this._store.add(configService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('inlineChat.showDiff')) { this._diffEnabled = !this._diffEnabled; - this._ctxShowingDiff.set(this._diffEnabled); this._doToggleDiff(); } })); @@ -256,7 +252,6 @@ export class LiveStrategy extends EditModeStrategy { override dispose(): void { this._inlineDiffDecorations.clear(); - this._ctxShowingDiff.reset(); this._store.dispose(); } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 2c7d8c4a708..b9340aea966 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -118,7 +118,6 @@ export const CTX_INLINE_CHAT_MESSAGE_CROP_STATE = new RawContextKey<'cropped' | export const CTX_INLINE_CHAT_OUTER_CURSOR_POSITION = new RawContextKey<'above' | 'below' | ''>('inlineChatOuterCursorPosition', '', localize('inlineChatOuterCursorPosition', "Whether the cursor of the outer editor is above or below the interactive editor input")); export const CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('inlineChatHasActiveRequest', false, localize('inlineChatHasActiveRequest', "Whether interactive editor has an active request")); export const CTX_INLINE_CHAT_HAS_STASHED_SESSION = new RawContextKey('inlineChatHasStashedSession', false, localize('inlineChatHasStashedSession', "Whether interactive editor has kept a session for quick restore")); -export const CTX_INLINE_CHAT_SHOWING_DIFF = new RawContextKey('inlineChatDiff', false, localize('inlineChatDiff', "Whether interactive editor show diffs for changes")); export const CTX_INLINE_CHAT_LAST_RESPONSE_TYPE = new RawContextKey('inlineChatLastResponseType', undefined, localize('inlineChatResponseType', "What type was the last response of the current interactive editor session")); export const CTX_INLINE_CHAT_RESPONSE_TYPES = new RawContextKey('inlineChatResponseTypes', undefined, localize('inlineChatResponseTypes', "What type was the responses have been receieved")); export const CTX_INLINE_CHAT_DID_EDIT = new RawContextKey('inlineChatDidEdit', undefined, localize('inlineChatDidEdit', "Whether interactive editor did change any code")); From ad395686d24694709c16942a2ae5f36b90ad384a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 12 Jul 2023 16:18:38 +0200 Subject: [PATCH 046/826] Git - add support for type changed (#187714) * Initial implementation * Add icons --- .../icons/dark/status-type-changed.svg | 6 ++++++ .../icons/light/status-type-changed.svg | 6 ++++++ extensions/git/src/api/api1.ts | 1 + extensions/git/src/api/git.d.ts | 1 + extensions/git/src/repository.ts | 18 ++++++++++++++++-- extensions/github/src/typings/git.d.ts | 1 + 6 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 extensions/git/resources/icons/dark/status-type-changed.svg create mode 100644 extensions/git/resources/icons/light/status-type-changed.svg diff --git a/extensions/git/resources/icons/dark/status-type-changed.svg b/extensions/git/resources/icons/dark/status-type-changed.svg new file mode 100644 index 00000000000..ae504ae1881 --- /dev/null +++ b/extensions/git/resources/icons/dark/status-type-changed.svg @@ -0,0 +1,6 @@ + + + + T + + diff --git a/extensions/git/resources/icons/light/status-type-changed.svg b/extensions/git/resources/icons/light/status-type-changed.svg new file mode 100644 index 00000000000..ae504ae1881 --- /dev/null +++ b/extensions/git/resources/icons/light/status-type-changed.svg @@ -0,0 +1,6 @@ + + + + T + + diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 375d07c4198..afa4ad1bcaf 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -363,6 +363,7 @@ function getStatus(status: Status): string { case Status.IGNORED: return 'IGNORED'; case Status.INTENT_TO_ADD: return 'INTENT_TO_ADD'; case Status.INTENT_TO_RENAME: return 'INTENT_TO_RENAME'; + case Status.TYPE_CHANGED: return 'TYPE_CHANGED'; case Status.ADDED_BY_US: return 'ADDED_BY_US'; case Status.ADDED_BY_THEM: return 'ADDED_BY_THEM'; case Status.DELETED_BY_US: return 'DELETED_BY_US'; diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 30b8c271103..ae1d57d3098 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -79,6 +79,7 @@ export const enum Status { IGNORED, INTENT_TO_ADD, INTENT_TO_RENAME, + TYPE_CHANGED, ADDED_BY_US, ADDED_BY_THEM, diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 1593e7309be..77739e11ce9 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -59,6 +59,7 @@ export class Resource implements SourceControlResourceState { case Status.IGNORED: return l10n.t('Ignored'); case Status.INTENT_TO_ADD: return l10n.t('Intent to Add'); case Status.INTENT_TO_RENAME: return l10n.t('Intent to Rename'); + case Status.TYPE_CHANGED: return l10n.t('Type Changed'); case Status.BOTH_DELETED: return l10n.t('Conflict: Both Deleted'); case Status.ADDED_BY_US: return l10n.t('Conflict: Added By Us'); case Status.DELETED_BY_THEM: return l10n.t('Conflict: Deleted By Them'); @@ -112,6 +113,7 @@ export class Resource implements SourceControlResourceState { Untracked: getIconUri('status-untracked', 'light'), Ignored: getIconUri('status-ignored', 'light'), Conflict: getIconUri('status-conflict', 'light'), + TypeChanged: getIconUri('status-type-changed', 'light') }, dark: { Modified: getIconUri('status-modified', 'dark'), @@ -121,7 +123,8 @@ export class Resource implements SourceControlResourceState { Copied: getIconUri('status-copied', 'dark'), Untracked: getIconUri('status-untracked', 'dark'), Ignored: getIconUri('status-ignored', 'dark'), - Conflict: getIconUri('status-conflict', 'dark') + Conflict: getIconUri('status-conflict', 'dark'), + TypeChanged: getIconUri('status-type-changed', 'dark') } }; @@ -138,6 +141,7 @@ export class Resource implements SourceControlResourceState { case Status.IGNORED: return Resource.Icons[theme].Ignored; case Status.INTENT_TO_ADD: return Resource.Icons[theme].Added; case Status.INTENT_TO_RENAME: return Resource.Icons[theme].Renamed; + case Status.TYPE_CHANGED: return Resource.Icons[theme].TypeChanged; case Status.BOTH_DELETED: return Resource.Icons[theme].Conflict; case Status.ADDED_BY_US: return Resource.Icons[theme].Conflict; case Status.DELETED_BY_THEM: return Resource.Icons[theme].Conflict; @@ -197,6 +201,8 @@ export class Resource implements SourceControlResourceState { case Status.INDEX_RENAMED: case Status.INTENT_TO_RENAME: return 'R'; + case Status.TYPE_CHANGED: + return 'T'; case Status.UNTRACKED: return 'U'; case Status.IGNORED: @@ -223,6 +229,7 @@ export class Resource implements SourceControlResourceState { case Status.INDEX_MODIFIED: return new ThemeColor('gitDecoration.stageModifiedResourceForeground'); case Status.MODIFIED: + case Status.TYPE_CHANGED: return new ThemeColor('gitDecoration.modifiedResourceForeground'); case Status.INDEX_DELETED: return new ThemeColor('gitDecoration.stageDeletedResourceForeground'); @@ -257,6 +264,7 @@ export class Resource implements SourceControlResourceState { case Status.INDEX_MODIFIED: case Status.MODIFIED: case Status.INDEX_COPIED: + case Status.TYPE_CHANGED: return 2; case Status.IGNORED: return 3; @@ -525,6 +533,7 @@ class ResourceCommandResolver { case Status.INDEX_RENAMED: case Status.INDEX_ADDED: case Status.INTENT_TO_RENAME: + case Status.TYPE_CHANGED: return toGitUri(resource.original, 'HEAD'); case Status.MODIFIED: @@ -560,7 +569,8 @@ class ResourceCommandResolver { case Status.UNTRACKED: case Status.IGNORED: case Status.INTENT_TO_ADD: - case Status.INTENT_TO_RENAME: { + case Status.INTENT_TO_RENAME: + case Status.TYPE_CHANGED: { const uriString = resource.resourceUri.toString(); const [indexStatus] = this.repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); @@ -609,6 +619,9 @@ class ResourceCommandResolver { case Status.INTENT_TO_RENAME: return l10n.t('{0} (Intent to add)', basename); + case Status.TYPE_CHANGED: + return l10n.t('{0} (Type changed)', basename); + default: return ''; } @@ -2188,6 +2201,7 @@ export class Repository implements Disposable { case 'D': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break; case 'A': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break; case 'R': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_RENAME, useIcons, renameUri)); break; + case 'T': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.TYPE_CHANGED, useIcons, renameUri)); break; } return undefined; diff --git a/extensions/github/src/typings/git.d.ts b/extensions/github/src/typings/git.d.ts index 4b4acd66879..7ac67937a47 100644 --- a/extensions/github/src/typings/git.d.ts +++ b/extensions/github/src/typings/git.d.ts @@ -79,6 +79,7 @@ export const enum Status { IGNORED, INTENT_TO_ADD, INTENT_TO_RENAME, + TYPE_CHANGED, ADDED_BY_US, ADDED_BY_THEM, From c4516310ba3003bbd47140d6a8beb0dbf4c04bd3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 12 Jul 2023 08:29:32 -0700 Subject: [PATCH 047/826] fix #187594 --- .../contrib/accessibility/browser/accessibilityContribution.ts | 2 +- .../contrib/inlineChat/browser/inlineChatController.ts | 2 +- .../accessibility/browser/terminalAccessibleBuffer.ts | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContribution.ts index 4e891c1b285..455c3d3050f 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContribution.ts @@ -44,7 +44,7 @@ const configuration: IConfigurationNode = { ...baseProperty }, [AccessibilityVerbositySettingId.InlineChat]: { - description: localize('verbosity.interactiveEditor.description', 'Provide information about how to access the inline editor chat accessibility help menu when the input is focused'), + description: localize('verbosity.interactiveEditor.description', 'Provide information about how to access the inline editor chat accessibility help menu and alert with hints which describe how to use the feature when the input is focused'), ...baseProperty }, [AccessibilityVerbositySettingId.KeybindingsEditor]: { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 0902ba579f8..215af1bdd1c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -655,7 +655,7 @@ export class InlineChatController implements IEditorContribution { if (!canContinue) { return State.ACCEPT; } - status = localize('editResponseMessage', "Use tab to navigate to the diff editor and review proposed changes."); + status = this._configurationService.getValue('accessibility.verbosity.inlineChat') === true ? localize('editResponseMessage', "Use tab to navigate to the diff editor and review proposed changes.") : ''; await this._strategy.renderChanges(response); } this._chatAccessibilityService.acceptResponse(status); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts index 2b58c359253..f2aa6fcee8a 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts @@ -73,6 +73,8 @@ export class AccessibleBufferWidget extends TerminalAccessibleWidget { await this.updateEditor(); this.element.classList.add(ClassName.Active); })); + // xterm's initial layout call has already happened + this.layout(); } navigateToCommand(type: NavigationType): void { From 3dc0d6d7b406ecd11e75dc95194107b5cac494d6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 12 Jul 2023 08:34:52 -0700 Subject: [PATCH 048/826] fix #187687 --- .../browser/accessibility.contribution.ts | 4 ++-- .../contrib/accessibility/browser/accessibleView.ts | 12 ++++++------ .../chat/browser/actions/chatAccessibilityHelp.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 2 +- .../contrib/codeEditor/browser/diffEditorHelper.ts | 2 +- .../notebook/browser/notebookAccessibilityHelp.ts | 2 +- .../browser/terminalAccessibilityHelp.ts | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 768860a6bf6..630aed6bc1b 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -32,7 +32,7 @@ class AccessibilityHelpProvider implements IAccessibleContentProvider { this._editor.focus(); } options: IAccessibleViewOptions = { type: AccessibleViewType.HelpMenu, ariaLabel: localize('editor-help', "editor accessibility help"), readMoreUrl: 'https://go.microsoft.com/fwlink/?linkid=851010' }; - id: string = 'editor'; + verbositySettingKey: string = 'editor'; constructor( private readonly _editor: ICodeEditor, @IKeybindingService private readonly _keybindingService: IKeybindingService @@ -115,7 +115,7 @@ class HoverAccessibleViewContribution extends Disposable { return false; } accessibleViewService.show({ - id: 'hover', + verbositySettingKey: 'hover', provideContent() { return content; }, onClose() { controller.focus(); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index ea0c2ebfcfb..c184c355c52 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -32,7 +32,7 @@ const enum DEFAULT { } export interface IAccessibleContentProvider { - id: string; + verbositySettingKey: string; provideContent(): string; onClose(): void; onKeyDown?(e: IKeyboardEvent): void; @@ -103,7 +103,7 @@ class AccessibleView extends Disposable { } })); this._register(this._configurationService.onDidChangeConfiguration(e => { - if (this._currentProvider && this._accessiblityHelpIsShown.get() && e.affectsConfiguration(`accessibility.verbosity.${this._currentProvider.id}`)) { + if (this._currentProvider && this._accessiblityHelpIsShown.get() && e.affectsConfiguration(`accessibility.verbosity.${this._currentProvider.verbositySettingKey}`)) { this.show(this._currentProvider); } })); @@ -130,7 +130,7 @@ class AccessibleView extends Disposable { private _render(provider: IAccessibleContentProvider, container: HTMLElement): IDisposable { this._currentProvider = provider; - const settingKey = `accessibility.verbosity.${provider.id}`; + const settingKey = `accessibility.verbosity.${provider.verbositySettingKey}`; const value = this._configurationService.getValue(settingKey); const readMoreLink = provider.options.readMoreUrl ? localize("openDoc", "\nPress H now to open a browser window with more information related to accessibility.\n") : ''; const disableHelpHint = provider.options.type === AccessibleViewType.HelpMenu && !!value ? localize('disable-help-hint', '\nTo disable the `accessibility.verbosity` hint for this feature, press D now.\n') : '\n'; @@ -142,7 +142,7 @@ class AccessibleView extends Disposable { ? AccessibilityHelpNLS.changeConfigToOnMac : AccessibilityHelpNLS.changeConfigToOnWinLinux ); - if (accessibilitySupport && provider.id === 'editor') { + if (accessibilitySupport && provider.verbositySettingKey === 'editor') { message = AccessibilityHelpNLS.auto_on; message += '\n'; } else if (!accessibilitySupport) { @@ -153,7 +153,7 @@ class AccessibleView extends Disposable { const fragment = message + provider.provideContent() + readMoreLink + disableHelpHint + localize('exit-tip', 'Exit this menu via the Escape key.'); - this._getTextModel(URI.from({ path: `accessible-view-${provider.id}`, scheme: 'accessible-view', fragment })).then((model) => { + this._getTextModel(URI.from({ path: `accessible-view-${provider.verbositySettingKey}`, scheme: 'accessible-view', fragment })).then((model) => { if (!model) { return; } @@ -176,7 +176,7 @@ class AccessibleView extends Disposable { // Delay to allow the context view to hide #186514 setTimeout(() => provider.onClose(), 100); } else if (e.keyCode === KeyCode.KeyD && this._configurationService.getValue(settingKey)) { - alert(localize('disableAccessibilityHelp', '{0} accessibility verbosity is now disabled', provider.id)); + alert(localize('disableAccessibilityHelp', '{0} accessibility verbosity is now disabled', provider.verbositySettingKey)); this._configurationService.updateValue(settingKey, false); } e.stopPropagation(); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index c505c984639..10a34e44a6a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -72,7 +72,7 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi inputEditor.getSupportedActions(); const helpText = getAccessibilityHelpText(accessor, type); accessibleViewService.show({ - id: type, + verbositySettingKey: type, provideContent: () => helpText, onClose: () => { if (type === 'panelChat' && cachedPosition) { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 7c4aa2fde5c..17eb7015f4e 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -136,7 +136,7 @@ class ChatAccessibleViewContribution extends Disposable { return false; } accessibleViewService.show({ - id: 'panelChat', + verbositySettingKey: 'panelChat', provideContent(): string { return responseContent; }, onClose() { widget.focus(focusedItem); diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 2fe58e8b68a..7414c5b26a5 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -65,7 +65,7 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont const keys = ['audioCues.diffLineDeleted', 'audioCues.diffLineInserted', 'audioCues.diffLineModified']; accessibleViewService.show({ - id: 'diffEditor', + verbositySettingKey: 'diffEditor', provideContent: () => [ nls.localize('msg1', "You are in a diff editor."), nls.localize('msg2', "Press {0} or {1} to view the next or previous diff in the diff review mode that is optimized for screen readers.", next, previous), diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts index 2a3bce2a6e2..717b6167614 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts @@ -46,7 +46,7 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi const accessibleViewService = accessor.get(IAccessibleViewService); const helpText = getAccessibilityHelpText(accessor); accessibleViewService.show({ - id: 'notebook', + verbositySettingKey: 'notebook', provideContent: () => helpText, onClose: () => { editor.focus(); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts index ac3d7f76b6b..d5d73c308a1 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts @@ -34,7 +34,7 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc ariaLabel: localize('terminal-help-label', "terminal accessibility help"), readMoreUrl: 'https://code.visualstudio.com/docs/editor/accessibility#_terminal-accessibility' }; - id: string = 'terminal'; + verbositySettingKey: string = 'terminal'; constructor( private readonly _instance: Pick, From 722328b7420341878895c3978ca5e1e4ff5e13a6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 12 Jul 2023 09:14:19 -0700 Subject: [PATCH 049/826] fix #186521 --- .../accessibility/browser/accessibility.contribution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 768860a6bf6..60f6a104d0a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -23,6 +23,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); @@ -125,7 +126,7 @@ class HoverAccessibleViewContribution extends Disposable { } }); return true; - })); + }, EditorContextKeys.hoverFocused)); } } From 05731984b3bbd53e124476051c615cd3b74ed1f6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 12 Jul 2023 18:45:45 +0200 Subject: [PATCH 050/826] fix #187731 (#187732) --- .../common/abstractExtensionManagementService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index a62ab547fcf..c2d5079f7cb 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -436,8 +436,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl try { compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease); } catch (error) { - if (error instanceof ExtensionManagementError && error.code === ExtensionManagementErrorCode.IncompatibleTargetPlatform && !isDependency) { - this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id); + if (!isDependency) { + this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id, getErrorMessage(error)); continue; } else { throw error; From 4c2df56f98d7e056077682326fec69d0fbfeacc0 Mon Sep 17 00:00:00 2001 From: aamunger Date: Wed, 12 Jul 2023 10:03:31 -0700 Subject: [PATCH 051/826] append html --- extensions/notebook-renderers/src/index.ts | 43 +++++++++++-------- .../notebook-renderers/src/textHelper.ts | 34 ++++++++++++++- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 0450aa54f10..430b00301a7 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import type { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer'; -import { createOutputContent, scrollableClass } from './textHelper'; +import { appendOutput, createOutputContent, scrollableClass } from './textHelper'; import { HtmlRenderingHook, IDisposable, IRichRenderContext, JavaScriptRenderingHook, RenderOptions } from './rendererTypes'; import { ttPolicy } from './htmlHelper'; @@ -172,7 +172,7 @@ function renderError( outputElement.classList.add('traceback'); const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); - const content = createOutputContent(outputInfo.id, [err.stack ?? ''], ctx.settings.lineLimit, outputScrolling, trustHTML); + const content = createOutputContent(outputInfo.id, err.stack ?? '', ctx.settings.lineLimit, outputScrolling, trustHTML); const contentParent = document.createElement('div'); contentParent.classList.toggle('word-wrap', ctx.settings.outputWordWrap); disposableStore.push(ctx.onDidChangeSettings(e => { @@ -276,26 +276,19 @@ interface OutputWithAppend extends OutputItem { // div output-item-id="{guid}" <-- content from outputItem parameter function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, error: boolean, ctx: IRichRenderContext): IDisposable { const appendedText = outputInfo.appendedText?.(); - if (appendedText) { - console.log(`appending output version ${appendedText}`); - } const disposableStore = createDisposableStore(); const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); outputElement.classList.add('output-stream'); - const text = outputInfo.text(); - const newContent = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false); - newContent.setAttribute('output-item-id', outputInfo.id); - if (error) { - newContent.classList.add('error'); - } + const scrollTop = outputScrolling ? findScrolledHeight(outputElement) : undefined; const previousOutputParent = getPreviousMatchingContentGroup(outputElement); // If the previous output item for the same cell was also a stream, append this output to the previous if (previousOutputParent) { + const newContent = createContent(outputInfo, ctx, outputScrolling, error); const existingContent = previousOutputParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; if (existingContent) { existingContent.replaceWith(newContent); @@ -309,15 +302,19 @@ function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, const existingContent = outputElement.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; let contentParent = existingContent?.parentElement; if (existingContent && contentParent) { - if (appendedText){ - existingContent + if (appendedText) { + appendOutput(existingContent, outputInfo.id, appendedText, ctx.settings.lineLimit, outputScrolling, false); } - existingContent.replaceWith(newContent); - while (newContent.nextSibling) { - // clear out any stale content if we had previously combined streaming outputs into this one - newContent.nextSibling.remove(); + else { + const newContent = createContent(outputInfo, ctx, outputScrolling, error); + existingContent.replaceWith(newContent); + while (newContent.nextSibling) { + // clear out any stale content if we had previously combined streaming outputs into this one + newContent.nextSibling.remove(); + } } } else { + const newContent = createContent(outputInfo, ctx, outputScrolling, error); contentParent = document.createElement('div'); contentParent.appendChild(newContent); while (outputElement.firstChild) { @@ -338,13 +335,23 @@ function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, return disposableStore; } +function createContent(outputInfo: OutputWithAppend, ctx: IRichRenderContext, outputScrolling: boolean, error: boolean) { + const text = outputInfo.text(); + const newContent = createOutputContent(outputInfo.id, text, ctx.settings.lineLimit, outputScrolling, false); + newContent.setAttribute('output-item-id', outputInfo.id); + if (error) { + newContent.classList.add('error'); + } + return newContent; +} + function renderText(outputInfo: OutputItem, outputElement: HTMLElement, ctx: IRichRenderContext): IDisposable { const disposableStore = createDisposableStore(); clearContainer(outputElement); const text = outputInfo.text(); const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); - const content = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false); + const content = createOutputContent(outputInfo.id, text, ctx.settings.lineLimit, outputScrolling, false); content.classList.add('output-plaintext'); if (ctx.settings.outputWordWrap) { content.classList.add('word-wrap'); diff --git a/extensions/notebook-renderers/src/textHelper.ts b/extensions/notebook-renderers/src/textHelper.ts index 8cc03fd543e..64d35eade87 100644 --- a/extensions/notebook-renderers/src/textHelper.ts +++ b/extensions/notebook-renderers/src/textHelper.ts @@ -100,9 +100,9 @@ function scrollableArrayOfString(id: string, buffer: string[], trustHtml: boolea return element; } -export function createOutputContent(id: string, outputs: string[], linesLimit: number, scrollable: boolean, trustHtml: boolean): HTMLElement { +export function createOutputContent(id: string, outputText: string, linesLimit: number, scrollable: boolean, trustHtml: boolean): HTMLElement { - const buffer = outputs.join('\n').split(/\r\n|\r|\n/g); + const buffer = outputText.split(/\r\n|\r|\n/g); if (scrollable) { return scrollableArrayOfString(id, buffer, trustHtml); @@ -110,3 +110,33 @@ export function createOutputContent(id: string, outputs: string[], linesLimit: n return truncatedArrayOfString(id, buffer, linesLimit, trustHtml); } } + +export function appendOutput(element: HTMLElement, id: string, outputText: string, linesLimit: number, scrollable: boolean, trustHtml: boolean) { + const buffer = outputText.split(/\r\n|\r|\n/g); + let newContent: HTMLDivElement; + if (scrollable) { + newContent = scrollableArrayOfString(id, buffer, trustHtml); + element.appendChild(newContent); + // contains 1 span per line + // const innerContainer = element.childNodes[0].childNodes[0]; + // const linesToAdd = newContent.childNodes[0].childNodes[0].childNodes; + + // linesToAdd.forEach(span => { + // innerContainer.appendChild(span); + // while (innerContainer.childNodes.length > 5000) { + // innerContainer.firstChild?.remove(); + // } + // }); + } else { + newContent = truncatedArrayOfString(id, buffer, linesLimit, trustHtml); + + // contains 1 span per line + const innerContainer = element.childNodes[0].childNodes[0]; + const linesToAdd = newContent.childNodes[0].childNodes[0].childNodes; + + linesToAdd.forEach(span => { + innerContainer.appendChild(span); + + }); + } +} From 87baaea6d1c56f6017e6d650d5bf6e0de616c0ab Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 12 Jul 2023 19:26:04 +0200 Subject: [PATCH 052/826] fix #187734 (#187735) --- .../common/abstractExtensionManagementService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index c2d5079f7cb..b9449b9df77 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -118,7 +118,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl await Promise.allSettled(extensions.map(async ({ extension, options }) => { try { - const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion); + const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion, false); installableExtensions.push({ ...compatible, options }); } catch (error) { results.push({ identifier: extension.identifier, operation: InstallOperation.Install, source: extension, error }); @@ -434,7 +434,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const isDependency = dependecies.some(id => areSameExtensions({ id }, galleryExtension.identifier)); let compatible; try { - compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease); + compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease, true); } catch (error) { if (!isDependency) { this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id, getErrorMessage(error)); @@ -454,7 +454,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return allDependenciesAndPacks; } - private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> { + private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean, fallbackToRelease: boolean): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> { const extensionsControlManifest = await this.getExtensionsControlManifest(); if (extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier))) { throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious); @@ -467,7 +467,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease); if (compatibleExtension) { - if (installPreRelease && !sameVersion && extension.hasPreReleaseVersion && !compatibleExtension.properties.isPreReleaseVersion) { + if (!fallbackToRelease && installPreRelease && !sameVersion && extension.hasPreReleaseVersion && !compatibleExtension.properties.isPreReleaseVersion) { throw new ExtensionManagementError(nls.localize('notFoundCompatiblePrereleaseDependency', "Can't install pre-release version of '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.IncompatiblePreRelease); } } else { From 77a8acf71907a9f9dd261b394d96a58b6acfb576 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 12 Jul 2023 11:30:58 -0700 Subject: [PATCH 053/826] don't rerender the same output --- extensions/notebook-renderers/src/index.ts | 7 ++--- .../notebook-renderers/src/textHelper.ts | 30 ++----------------- .../view/renderers/backLayerWebView.ts | 6 ++++ 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 430b00301a7..be59b011c12 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -281,8 +281,6 @@ function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, outputElement.classList.add('output-stream'); - - const scrollTop = outputScrolling ? findScrolledHeight(outputElement) : undefined; const previousOutputParent = getPreviousMatchingContentGroup(outputElement); @@ -302,8 +300,9 @@ function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, const existingContent = outputElement.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; let contentParent = existingContent?.parentElement; if (existingContent && contentParent) { - if (appendedText) { - appendOutput(existingContent, outputInfo.id, appendedText, ctx.settings.lineLimit, outputScrolling, false); + // appending output only in scrollable ouputs currently + if (appendedText && outputScrolling) { + appendOutput(existingContent, appendedText, false); } else { const newContent = createContent(outputInfo, ctx, outputScrolling, error); diff --git a/extensions/notebook-renderers/src/textHelper.ts b/extensions/notebook-renderers/src/textHelper.ts index 64d35eade87..f335d2cfdda 100644 --- a/extensions/notebook-renderers/src/textHelper.ts +++ b/extensions/notebook-renderers/src/textHelper.ts @@ -111,32 +111,8 @@ export function createOutputContent(id: string, outputText: string, linesLimit: } } -export function appendOutput(element: HTMLElement, id: string, outputText: string, linesLimit: number, scrollable: boolean, trustHtml: boolean) { +export function appendOutput(element: HTMLElement, outputText: string, trustHtml: boolean) { const buffer = outputText.split(/\r\n|\r|\n/g); - let newContent: HTMLDivElement; - if (scrollable) { - newContent = scrollableArrayOfString(id, buffer, trustHtml); - element.appendChild(newContent); - // contains 1 span per line - // const innerContainer = element.childNodes[0].childNodes[0]; - // const linesToAdd = newContent.childNodes[0].childNodes[0].childNodes; - - // linesToAdd.forEach(span => { - // innerContainer.appendChild(span); - // while (innerContainer.childNodes.length > 5000) { - // innerContainer.firstChild?.remove(); - // } - // }); - } else { - newContent = truncatedArrayOfString(id, buffer, linesLimit, trustHtml); - - // contains 1 span per line - const innerContainer = element.childNodes[0].childNodes[0]; - const linesToAdd = newContent.childNodes[0].childNodes[0].childNodes; - - linesToAdd.forEach(span => { - innerContainer.appendChild(span); - - }); - } + const newContent = handleANSIOutput(buffer.join('\n'), trustHtml); + element.appendChild(newContent); } 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 4e3b2573ec3..eda0b16c645 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -1499,6 +1499,12 @@ export class BackLayerWebView extends Themable { } const outputCache = this.insetMapping.get(content.source)!; + + if (outputCache.versionId === content.source.model.versionId) { + // already sent this output version to the renderer + return; + } + this.hiddenInsetMapping.delete(content.source); let updatedContent: ICreationContent | undefined = undefined; From cbb47129d378be07525588e4440f7f4e82f1d513 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 12 Jul 2023 11:58:09 -0700 Subject: [PATCH 054/826] cli: stop tunnel processes during update (#187738) * untested wip * make it work --- build/gulpfile.vscode.win32.js | 1 + build/win32/code.iss | 39 +++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index 2d6f14551b3..674eb41a503 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -98,6 +98,7 @@ function buildWin32Setup(arch, target) { AppMutex: product.win32MutexName, TunnelMutex: product.win32TunnelMutex, TunnelServiceMutex: product.win32TunnelServiceMutex, + TunnelApplicationName: product.tunnelApplicationName, ApplicationName: product.applicationName, Arch: arch, AppId: { 'ia32': ia32AppId, 'x64': x64AppId, 'arm64': arm64AppId }[arch], diff --git a/build/win32/code.iss b/build/win32/code.iss index 44c9f2f1f0b..b7336831374 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -1362,12 +1362,6 @@ begin end; end; - if IsNotBackgroundUpdate() and CheckForMutexes('{#TunnelMutex}') then - begin - MsgBox('{#NameShort} is still running a tunnel. Please stop the tunnel before installing.', mbInformation, MB_OK); - Result := false - end; - end; function WizardNotSilent(): Boolean; @@ -1380,6 +1374,31 @@ end; var ShouldRestartTunnelService: Boolean; +function StopTunnelOtherProcesses(): Boolean; +var + WaitCounter: Integer; + TaskKilled: Integer; +begin + Log('Stopping all tunnel services (at ' + ExpandConstant('"{app}\bin\{#TunnelApplicationName}.exe"') + ')'); + ShellExec('', 'powershell.exe', '-Command "Get-WmiObject Win32_Process | Where-Object { $_.ExecutablePath -eq ' + ExpandConstant('''{app}\bin\{#TunnelApplicationName}.exe''') + ' } | Select @{Name=''Id''; Expression={$_.ProcessId}} | Stop-Process -Force"', '', SW_HIDE, ewWaitUntilTerminated, TaskKilled) + + WaitCounter := 10; + while (WaitCounter > 0) and CheckForMutexes('{#TunnelMutex}') do + begin + Log('Tunnel process is is still running, waiting'); + Sleep(500); + WaitCounter := WaitCounter - 1 + end; + + if CheckForMutexes('{#TunnelMutex}') then + begin + Log('Unable to stop tunnel processes'); + Result := False; + end + else + Result := True; +end; + procedure StopTunnelServiceIfNeeded(); var StopServiceResultCode: Integer; @@ -1413,7 +1432,11 @@ function PrepareToInstall(var NeedsRestart: Boolean): String; begin if IsNotBackgroundUpdate() then StopTunnelServiceIfNeeded(); - Result := '' + + if IsNotBackgroundUpdate() and not StopTunnelOtherProcesses() then + Result := '{#NameShort} is still running a tunnel process. Please stop the tunnel before installing.' + else + Result := ''; end; // VS Code will create a flag file before the update starts (/update=C:\foo\bar) @@ -1607,4 +1630,4 @@ begin #endif Exec(ExpandConstant('{sys}\icacls.exe'), ExpandConstant('"{app}" /inheritancelevel:r ') + Permissions, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); -end; \ No newline at end of file +end; From 588b36f058b422903b489d2b5f19185fe2fb8e8d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 12 Jul 2023 12:00:29 -0700 Subject: [PATCH 055/826] make changes --- .../workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 57077181753..ef3951acd65 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -179,6 +179,7 @@ export class InlineChatWidget { private _isLayouting: boolean = false; private _preferredExpansionState: ExpansionState | undefined; private _expansionState: ExpansionState = ExpansionState.NOT_CROPPED; + private _slashCommandDetails: { command: string; detail: string }[] = []; constructor( private readonly parentEditor: ICodeEditor, @@ -418,9 +419,12 @@ export class InlineChatWidget { } readPlaceholder(): void { + const slashCommand = this._slashCommandDetails.find(c => `${c.command} ` === this._inputModel.getValue().substring(1)); const hasText = this._inputModel.getValueLength() > 0; if (!hasText) { aria.status(this._elements.placeholder.innerText); + } else if (slashCommand) { + aria.status(slashCommand.detail); } } @@ -621,6 +625,7 @@ export class InlineChatWidget { if (commands.length === 0) { return; } + this._slashCommandDetails = commands.filter(c => c.command && c.detail).map(c => { return { command: c.command!, detail: c.detail! }; }); const selector: LanguageSelector = { scheme: this._inputModel.uri.scheme, pattern: this._inputModel.uri.path, language: this._inputModel.getLanguageId() }; this._slashCommands.add(this._languageFeaturesService.completionProvider.register(selector, new class implements CompletionItemProvider { From 9bca8bebfcafa5467cb7847f91c5dbfb38cd7a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 12 Jul 2023 21:15:48 +0200 Subject: [PATCH 056/826] switch update/release notes actions (#187740) --- .../contrib/update/browser/update.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 39daccab96f..01fb0a75904 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -451,16 +451,6 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Updating) }); - CommandsRegistry.registerCommand('update.restart', () => this.updateService.quitAndInstall()); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '7_update', - command: { - id: 'update.restart', - title: nls.localize('restartToUpdate', "Restart to Update (1)") - }, - when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) - }); - CommandsRegistry.registerCommand('update.showUpdateReleaseNotes', () => { if (this.updateService.state.type !== StateType.Ready) { return; @@ -478,6 +468,16 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) }); + CommandsRegistry.registerCommand('update.restart', () => this.updateService.quitAndInstall()); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '7_update', + command: { + id: 'update.restart', + title: nls.localize('restartToUpdate', "Restart to Update (1)") + }, + when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) + }); + CommandsRegistry.registerCommand('_update.state', () => { return this.state; }); From cfaa152f707f30507a6ad877592946536c9c9eeb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 12 Jul 2023 13:40:09 -0700 Subject: [PATCH 057/826] fix #187753 --- .../workbench/contrib/accessibility/browser/accessibleView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index c184c355c52..8754d601121 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -28,7 +28,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; const enum DEFAULT { WIDTH = 800, - TOP = 25 + TOP = 3 } export interface IAccessibleContentProvider { From e0e8ca87f3b5def0bc3ce960616a22408d88a681 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 12 Jul 2023 13:51:53 -0700 Subject: [PATCH 058/826] Pick up latest TS for building VS code (#187751) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9fba1bf8246..2c98e0a79bf 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.2.7", - "typescript": "^5.2.0-dev.20230710", + "typescript": "^5.2.0-dev.20230712", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/yarn.lock b/yarn.lock index 65ca0297579..ac27990a8ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10036,10 +10036,10 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@^5.2.0-dev.20230710: - version "5.2.0-dev.20230710" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.0-dev.20230710.tgz#6e42862d126c2c7b4ce14ce6124396561dd808c0" - integrity sha512-D4u4/pfdrIi94+F+iVzxzuzCk8bE/cbe4jng6Sce2Y1szj/QiBqN43X5lMrIbMR9Kb1G2AdWzYPrcNv/ButJGA== +typescript@^5.2.0-dev.20230712: + version "5.2.0-dev.20230712" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.0-dev.20230712.tgz#6da271394793fd1c1cba26800f43bb8c25825a87" + integrity sha512-F/VND6YrBGr8RvnmpFpTDC5sT0Hh5cuXFWurGEhSBjwXodW+POmfGU0uLn+s/Rl0+Yvffpd2WRrpYC7bSDQS/Q== typical@^4.0.0: version "4.0.0" From 8303f042cd537aab144ce2f0944300cdc02ea528 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 12 Jul 2023 13:56:17 -0700 Subject: [PATCH 059/826] focus last if input box is focused --- .../contrib/chat/browser/chat.contribution.ts | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 17eb7015f4e..a4d715d9a75 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -40,8 +40,10 @@ import { registerClearActions } from 'vs/workbench/contrib/chat/browser/actions/ import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -125,8 +127,18 @@ class ChatAccessibleViewContribution extends Disposable { this._register(AccessibleViewAction.addImplementation(100, 'panelChat', accessor => { const accessibleViewService = accessor.get(IAccessibleViewService); const widgetService = accessor.get(IChatWidgetService); - const widget: IChatWidget | undefined = widgetService.lastFocusedWidget; - const focusedItem: ChatTreeItem | undefined = widget?.getFocus(); + const contextKeyService = accessor.get(IContextKeyService); + const commandService = accessor.get(ICommandService); + let widget: IChatWidget | undefined = widgetService.lastFocusedWidget; + let focusedItem: ChatTreeItem | undefined = widget?.getFocus(); + let focusedInput = false; + if (!contextKeyService.getContextKeyValue(CONTEXT_RESPONSE.key)) { + // focus is in the input box, so we need to focus the last widget first + commandService.executeCommand('chat.action.focus'); + widget = widgetService.lastFocusedWidget; + focusedItem = widget?.getFocus(); + focusedInput = true; + } if (!widget || !focusedItem || !isResponseVM(focusedItem)) { return false; } @@ -139,7 +151,11 @@ class ChatAccessibleViewContribution extends Disposable { verbositySettingKey: 'panelChat', provideContent(): string { return responseContent; }, onClose() { - widget.focus(focusedItem); + if (focusedInput) { + widget?.focusInput(); + } else { + widget!.focus(focusedItem!); + } }, options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View } }); From a4fde6f2eb53a4961846df2f0ad7260778cbcc37 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 12 Jul 2023 14:09:01 -0700 Subject: [PATCH 060/826] Add more specific error for TS web server (#187752) This should help us better understand why the access failed --- .../typescript-language-features/web/webServer.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/web/webServer.ts b/extensions/typescript-language-features/web/webServer.ts index 6580a84995c..1688a93fcc0 100644 --- a/extensions/typescript-language-features/web/webServer.ts +++ b/extensions/typescript-language-features/web/webServer.ts @@ -76,6 +76,15 @@ function toTsWatcherKind(event: 'create' | 'change' | 'delete') { throw new Error(`Unknown event: ${event}`); } +class AccessOutsideOfRootError extends Error { + constructor( + public readonly filepath: string, + public readonly projectRootPaths: readonly string[] + ) { + super(`Could not read file outside of project root ${filepath}`); + } +} + type ServerHostWithImport = ts.server.ServerHost & { importPlugin(root: string, moduleName: string): Promise }; function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient: ApiClient | undefined, args: string[], fsWatcher: MessagePort): ServerHostWithImport { @@ -455,7 +464,7 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient } if (allowRead === 'block') { - throw new Error(`Could not read file outside of project root ${filepath}`); + throw new AccessOutsideOfRootError(filepath, Array.from(projectRootPaths.keys())); } return uri; From 6e0d58ef7eac2de244fa3eb9e67e8117c6904f73 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 12 Jul 2023 14:17:40 -0700 Subject: [PATCH 061/826] fix #186678 --- .../terminal.accessibility.contribution.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index bdf148b2ef2..55b008fbe2f 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -129,8 +129,14 @@ registerTerminalAction({ keybinding: [ { primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - weight: KeybindingWeight.WorkbenchContrib + 2, - when: TerminalContextKeys.accessibleBufferFocus + when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + weight: KeybindingWeight.WorkbenchContrib + 2 + }, + { + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + mac: { primary: KeyMod.Alt | KeyCode.DownArrow }, + when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + weight: KeybindingWeight.WorkbenchContrib + 2 } ], run: async (c) => { @@ -151,8 +157,14 @@ registerTerminalAction({ keybinding: [ { primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - weight: KeybindingWeight.WorkbenchContrib + 2, - when: TerminalContextKeys.accessibleBufferFocus + when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + weight: KeybindingWeight.WorkbenchContrib + 2 + }, + { + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + mac: { primary: KeyMod.Alt | KeyCode.UpArrow }, + when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + weight: KeybindingWeight.WorkbenchContrib + 2 } ], run: async (c) => { From 600b5192e8b0d1d63f72e4548b05c961b220f729 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 12 Jul 2023 14:28:05 -0700 Subject: [PATCH 062/826] Render the tree once rather than once per key (#187742) --- .../contrib/preferences/browser/settingsEditor2.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 3ce681d6033..b45e1856f2a 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -1356,9 +1356,11 @@ export class SettingsEditor2 extends EditorPane { keys.forEach(key => this.settingsTreeModel.updateElementsByName(key)); } - keys.forEach(key => this.renderTree(key)); + // Attempt to render the tree once rather than + // once for each key to avoid redundant calls to this.refreshTree() + this.renderTree(); } else { - return this.renderTree(); + this.renderTree(); } } From ae0d63d5a1d05f602d9620df427669c6e9835ea1 Mon Sep 17 00:00:00 2001 From: Anthony Stewart <150152+a-stewart@users.noreply.github.com> Date: Wed, 12 Jul 2023 23:28:09 +0200 Subject: [PATCH 063/826] Increate the max width of action widgets to 80% of the browser width (#186157) --- src/vs/platform/actionWidget/browser/actionWidget.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/actionWidget/browser/actionWidget.css b/src/vs/platform/actionWidget/browser/actionWidget.css index c2ed9072d58..ae7c6ce75fa 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/src/vs/platform/actionWidget/browser/actionWidget.css @@ -7,7 +7,7 @@ font-size: 13px; border-radius: 0; min-width: 160px; - max-width: 500px; + max-width: 80vw; z-index: 40; display: block; width: 100%; From 4529b4af392ba9b93a82557ae4c84015245242a1 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 12 Jul 2023 14:29:31 -0700 Subject: [PATCH 064/826] Fix Cloud Changes enablement placeholder (#187761) --- .../browser/editSessions.contribution.ts | 6 ++-- .../browser/editSessionsStorageService.ts | 28 +++++++++---------- .../editSessions/common/editSessions.ts | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index 411241dbc61..0f6e359a961 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -227,7 +227,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo ) { // store the fact that we prompted the user this.storageService.store(EditSessionsContribution.APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.MACHINE); - await this.editSessionsStorageService.initialize(); + await this.editSessionsStorageService.initialize('read'); if (this.editSessionsStorageService.isSignedIn) { await this.progressService.withProgress(resumeProgressOptions, async (progress) => await this.resumeEditSession(undefined, true, undefined, undefined, progress)); } else { @@ -491,7 +491,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo this.logService.info(ref !== undefined ? `Resuming changes from cloud with ref ${ref}...` : 'Checking for pending cloud changes...'); - if (silent && !(await this.editSessionsStorageService.initialize(true))) { + if (silent && !(await this.editSessionsStorageService.initialize('read', true))) { return; } @@ -838,7 +838,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo return continueWithCloudChanges; } - const initialized = await this.editSessionsStorageService.initialize(); + const initialized = await this.editSessionsStorageService.initialize('write'); if (!initialized) { this.telemetryService.publicLog2('continueOn.editSessions.canStore.outcome', { outcome: 'didNotEnableEditSessionsWhenPrompted' }); } diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts index f394515c6e2..fb44c175699 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts @@ -106,7 +106,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes * @returns The ref of the stored state. */ async write(resource: SyncResource, content: string | EditSession): Promise { - await this.initialize(false); + await this.initialize('write', false); if (!this.initialized) { throw new Error('Please sign in to store your edit session.'); } @@ -131,7 +131,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes * @returns An object representing the requested or latest state, if any. */ async read(resource: SyncResource, ref: string | undefined): Promise<{ ref: string; content: string } | undefined> { - await this.initialize(false); + await this.initialize('read', false); if (!this.initialized) { throw new Error('Please sign in to apply your latest edit session.'); } @@ -159,7 +159,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes } async delete(resource: SyncResource, ref: string | null) { - await this.initialize(false); + await this.initialize('write', false); if (!this.initialized) { throw new Error(`Unable to delete edit session with ref ${ref}.`); } @@ -172,7 +172,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes } async list(resource: SyncResource): Promise { - await this.initialize(false); + await this.initialize('read', false); if (!this.initialized) { throw new Error(`Unable to list edit sessions.`); } @@ -186,11 +186,11 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes return []; } - public async initialize(silent: boolean = false) { + public async initialize(reason: 'read' | 'write', silent: boolean = false) { if (this.initialized) { return true; } - this.initialized = await this.doInitialize(silent); + this.initialized = await this.doInitialize(reason, silent); this.signedInContext.set(this.initialized); if (this.initialized) { this._didSignIn.fire(); @@ -205,7 +205,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes * meaning that authentication is configured and it * can be used to communicate with the remote storage service */ - private async doInitialize(silent: boolean): Promise { + private async doInitialize(reason: 'read' | 'write', silent: boolean): Promise { // Wait for authentication extensions to be registered await this.extensionService.whenInstalledExtensionsRegistered(); @@ -231,7 +231,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes return true; } - const authenticationSession = await this.getAuthenticationSession(silent); + const authenticationSession = await this.getAuthenticationSession(reason, silent); if (authenticationSession !== undefined) { this.authenticationInfo = authenticationSession; this.storeClient.setAuthToken(authenticationSession.token, authenticationSession.providerId); @@ -243,7 +243,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes private cachedMachines: Map | undefined; async getMachineById(machineId: string) { - await this.initialize(false); + await this.initialize('read', false); if (!this.cachedMachines) { const machines = await this.machineClient!.getMachines(); @@ -264,7 +264,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes return currentMachineId; } - private async getAuthenticationSession(silent: boolean) { + private async getAuthenticationSession(reason: 'read' | 'write', silent: boolean) { // If the user signed in previously and the session is still available, reuse that without prompting the user again if (this.existingSessionId) { this.logService.info(`Searching for existing authentication session with ID ${this.existingSessionId}`); @@ -295,7 +295,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes } // Ask the user to pick a preferred account - const authenticationSession = await this.getAccountPreference(); + const authenticationSession = await this.getAccountPreference(reason); if (authenticationSession !== undefined) { this.existingSessionId = authenticationSession.id; return { sessionId: authenticationSession.id, token: authenticationSession.idToken ?? authenticationSession.accessToken, providerId: authenticationSession.providerId }; @@ -312,10 +312,10 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes * * Prompts the user to pick an authentication option for storing and getting edit sessions. */ - private async getAccountPreference(): Promise { + private async getAccountPreference(reason: 'read' | 'write'): Promise { const quickpick = this.quickInputService.createQuickPick(); quickpick.ok = false; - quickpick.placeholder = localize('choose account placeholder', "Select an account to store your working changes in the cloud"); + quickpick.placeholder = reason === 'read' ? localize('choose account read placeholder', "Select an account to restore your working changes from the cloud") : localize('choose account placeholder', "Select an account to store your working changes in the cloud"); quickpick.ignoreFocusOut = true; quickpick.items = await this.createQuickpickItems(); @@ -482,7 +482,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes } async run() { - return await that.initialize(false); + return await that.initialize('write', false); } })); diff --git a/src/vs/workbench/contrib/editSessions/common/editSessions.ts b/src/vs/workbench/contrib/editSessions/common/editSessions.ts index 2c62c7f8ee2..53c39076411 100644 --- a/src/vs/workbench/contrib/editSessions/common/editSessions.ts +++ b/src/vs/workbench/contrib/editSessions/common/editSessions.ts @@ -38,7 +38,7 @@ export interface IEditSessionsStorageService { lastReadResources: Map; lastWrittenResources: Map; - initialize(silent?: boolean): Promise; + initialize(reason: 'read' | 'write', silent?: boolean): Promise; read(resource: SyncResource, ref: string | undefined): Promise<{ ref: string; content: string } | undefined>; write(resource: SyncResource, content: string | EditSession): Promise; delete(resource: SyncResource, ref: string | null): Promise; From 6809b95ed034e798e23a8c7c7e548c64403aff59 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 12 Jul 2023 14:32:17 -0700 Subject: [PATCH 065/826] better approach --- .../contrib/chat/browser/chat.contribution.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index a4d715d9a75..defbcbd581d 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -40,10 +40,9 @@ import { registerClearActions } from 'vs/workbench/contrib/chat/browser/actions/ import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -127,14 +126,19 @@ class ChatAccessibleViewContribution extends Disposable { this._register(AccessibleViewAction.addImplementation(100, 'panelChat', accessor => { const accessibleViewService = accessor.get(IAccessibleViewService); const widgetService = accessor.get(IChatWidgetService); - const contextKeyService = accessor.get(IContextKeyService); - const commandService = accessor.get(ICommandService); + const codeEditorService = accessor.get(ICodeEditorService); + let widget: IChatWidget | undefined = widgetService.lastFocusedWidget; let focusedItem: ChatTreeItem | undefined = widget?.getFocus(); let focusedInput = false; - if (!contextKeyService.getContextKeyValue(CONTEXT_RESPONSE.key)) { + const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); + if (editor) { // focus is in the input box, so we need to focus the last widget first - commandService.executeCommand('chat.action.focus'); + const editorUri = editor.getModel()?.uri; + if (!editorUri) { + return false; + } + widgetService.getWidgetByInputUri(editorUri)?.focusLastMessage(); widget = widgetService.lastFocusedWidget; focusedItem = widget?.getFocus(); focusedInput = true; From 5b78562d6c5aca8928ce442968c1f07ca0c284f1 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 12 Jul 2023 15:03:49 -0700 Subject: [PATCH 066/826] truncate long outputs --- extensions/notebook-renderers/src/index.ts | 10 +-- .../notebook-renderers/src/rendererTypes.ts | 4 ++ .../src/test/notebookRenderer.test.ts | 66 +++++++++++++++++-- .../notebook-renderers/src/textHelper.ts | 34 ++++++++-- 4 files changed, 95 insertions(+), 19 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index be59b011c12..4af59128a2c 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import type { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer'; -import { appendOutput, createOutputContent, scrollableClass } from './textHelper'; -import { HtmlRenderingHook, IDisposable, IRichRenderContext, JavaScriptRenderingHook, RenderOptions } from './rendererTypes'; +import { appendScrollableOutput, createOutputContent, scrollableClass } from './textHelper'; +import { HtmlRenderingHook, IDisposable, IRichRenderContext, JavaScriptRenderingHook, OutputWithAppend, RenderOptions } from './rendererTypes'; import { ttPolicy } from './htmlHelper'; function clearContainer(container: HTMLElement) { @@ -265,10 +265,6 @@ function scrollingEnabled(output: OutputItem, options: RenderOptions) { metadata.scrollable : options.outputScrolling; } -interface OutputWithAppend extends OutputItem { - appendedText?(): string | undefined; -} - // div.cell_container // div.output_container // div.output.output-stream <-- outputElement parameter @@ -302,7 +298,7 @@ function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, if (existingContent && contentParent) { // appending output only in scrollable ouputs currently if (appendedText && outputScrolling) { - appendOutput(existingContent, appendedText, false); + appendScrollableOutput(existingContent, outputInfo.id, appendedText, outputInfo.text(), false); } else { const newContent = createContent(outputInfo, ctx, outputScrolling, error); diff --git a/extensions/notebook-renderers/src/rendererTypes.ts b/extensions/notebook-renderers/src/rendererTypes.ts index 9da94aeef5d..77f9ebac472 100644 --- a/extensions/notebook-renderers/src/rendererTypes.ts +++ b/extensions/notebook-renderers/src/rendererTypes.ts @@ -35,3 +35,7 @@ export interface RenderOptions { } export type IRichRenderContext = RendererContext & { readonly settings: RenderOptions; readonly onDidChangeSettings: Event }; + +export interface OutputWithAppend extends OutputItem { + appendedText?(): string | undefined; +} diff --git a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts index e67d1d8ce26..3c0c609140d 100644 --- a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts +++ b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts @@ -5,8 +5,8 @@ import * as assert from 'assert'; import { activate } from '..'; -import { OutputItem, RendererApi } from 'vscode-notebook-renderer'; -import { IDisposable, IRichRenderContext, RenderOptions } from '../rendererTypes'; +import { RendererApi } from 'vscode-notebook-renderer'; +import { IDisposable, IRichRenderContext, OutputWithAppend, RenderOptions } from '../rendererTypes'; import { JSDOM } from "jsdom"; const dom = new JSDOM(); @@ -116,10 +116,13 @@ suite('Notebook builtin output renderer', () => { } } - function createOutputItem(text: string, mime: string, id: string = '123'): OutputItem { + function createOutputItem(text: string, mime: string, id: string = '123', appendedText?: string): OutputWithAppend { return { id: id, mime: mime, + appendedText() { + return appendedText; + }, text() { return text; }, @@ -177,9 +180,9 @@ suite('Notebook builtin output renderer', () => { assert.ok(renderer, 'Renderer not created'); const outputElement = new OutputHtml().getFirstOuputElement(); - const outputItem = createOutputItem('content', 'text/plain'); + const outputItem = createOutputItem('content', mimeType); await renderer!.renderOutputItem(outputItem, outputElement); - const outputItem2 = createOutputItem('replaced content', 'text/plain'); + const outputItem2 = createOutputItem('replaced content', mimeType); await renderer!.renderOutputItem(outputItem2, outputElement); const inserted = outputElement.firstChild as HTMLElement; @@ -189,6 +192,59 @@ suite('Notebook builtin output renderer', () => { }); + test('Append streaming output', async () => { + const context = createContext({ outputWordWrap: false, outputScrolling: false }); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + const outputItem = createOutputItem('content', stdoutMimeType, '123', 'ignoredAppend'); + await renderer!.renderOutputItem(outputItem, outputElement); + const outputItem2 = createOutputItem('content\nappended', stdoutMimeType, '\nappended'); + await renderer!.renderOutputItem(outputItem2, outputElement); + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted.innerHTML.indexOf('>contentappendedcontentcontent { + const context = createContext({ outputWordWrap: false, outputScrolling: false }); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + const lotsOfLines = new Array(4998).fill('line').join('\n') + 'endOfInitialContent'; + const firstOuput = lotsOfLines + 'expected1'; + const outputItem = createOutputItem(firstOuput, stdoutMimeType, '123'); + await renderer!.renderOutputItem(outputItem, outputElement); + const appended = '\n' + lotsOfLines + 'expectedAppend'; + const outputItem2 = createOutputItem(firstOuput + appended, stdoutMimeType, appended); + await renderer!.renderOutputItem(outputItem2, outputElement); + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted.innerHTML.indexOf('>expected1expectedAppend { + const context = createContext({ outputWordWrap: false, outputScrolling: false }); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + const lotsOfLines = new Array(11000).fill('line').join('\n') + 'endOfInitialContent'; + const firstOuput = 'shouldBeTruncated' + lotsOfLines + 'expected1'; + const outputItem = createOutputItem(firstOuput, stdoutMimeType, '123'); + await renderer!.renderOutputItem(outputItem, outputElement); + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted.innerHTML.indexOf('>endOfInitialContentshouldBeTruncated { const context = createContext({ outputWordWrap: true, outputScrolling: true }); const renderer = await activate(context); diff --git a/extensions/notebook-renderers/src/textHelper.ts b/extensions/notebook-renderers/src/textHelper.ts index f335d2cfdda..97b3a1ba694 100644 --- a/extensions/notebook-renderers/src/textHelper.ts +++ b/extensions/notebook-renderers/src/textHelper.ts @@ -4,9 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { handleANSIOutput } from './ansi'; - export const scrollableClass = 'scrollable'; +const softScrollableLineLimit = 5000; +const hardScrollableLineLimit = 8000; + /** * Output is Truncated. View as a [scrollable element] or open in a [text editor]. Adjust cell output [settings...] */ @@ -91,11 +93,11 @@ function truncatedArrayOfString(id: string, buffer: string[], linesLimit: number function scrollableArrayOfString(id: string, buffer: string[], trustHtml: boolean) { const element = document.createElement('div'); - if (buffer.length > 5000) { + if (buffer.length > softScrollableLineLimit) { element.appendChild(generateNestedViewAllElement(id)); } - element.appendChild(handleANSIOutput(buffer.slice(-5000).join('\n'), trustHtml)); + element.appendChild(handleANSIOutput(buffer.slice(-1 * softScrollableLineLimit).join('\n'), trustHtml)); return element; } @@ -111,8 +113,26 @@ export function createOutputContent(id: string, outputText: string, linesLimit: } } -export function appendOutput(element: HTMLElement, outputText: string, trustHtml: boolean) { - const buffer = outputText.split(/\r\n|\r|\n/g); - const newContent = handleANSIOutput(buffer.join('\n'), trustHtml); - element.appendChild(newContent); +const outputLengths: Record = {}; + +export function appendScrollableOutput(element: HTMLElement, id: string, appended: string, fullText: string, trustHtml: boolean) { + if (!outputLengths[id]) { + outputLengths[id] = 0; + } + + const buffer = appended.split(/\r\n|\r|\n/g); + const appendedLength = buffer.length + outputLengths[id]; + // Allow the output to grow to the hard limit then replace it with the last softLimit number of lines if it grows too large + if (buffer.length + outputLengths[id] > hardScrollableLineLimit) { + const fullBuffer = fullText.split(/\r\n|\r|\n/g); + outputLengths[id] = Math.min(fullBuffer.length, softScrollableLineLimit); + const newElement = scrollableArrayOfString(id, fullBuffer.slice(-1 * softScrollableLineLimit), trustHtml); + newElement.setAttribute('output-item-id', id); + element.replaceWith(); + } + else { + element.appendChild(handleANSIOutput(buffer.join('\n'), trustHtml)); + outputLengths[id] = appendedLength; + } } + From e68a34f9bdc86a2933f079b1ac1436fab4bc4c3a Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 12 Jul 2023 15:20:43 -0700 Subject: [PATCH 067/826] append when stream outputs are concatenated --- extensions/notebook-renderers/src/index.ts | 11 ++++++++--- extensions/notebook-renderers/src/textHelper.ts | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 4af59128a2c..62c9fe70295 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -282,12 +282,17 @@ function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, const previousOutputParent = getPreviousMatchingContentGroup(outputElement); // If the previous output item for the same cell was also a stream, append this output to the previous if (previousOutputParent) { - const newContent = createContent(outputInfo, ctx, outputScrolling, error); const existingContent = previousOutputParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; if (existingContent) { - existingContent.replaceWith(newContent); - + if (appendedText && outputScrolling) { + appendScrollableOutput(existingContent, outputInfo.id, appendedText, outputInfo.text(), false); + } + else { + const newContent = createContent(outputInfo, ctx, outputScrolling, error); + existingContent.replaceWith(newContent); + } } else { + const newContent = createContent(outputInfo, ctx, outputScrolling, error); previousOutputParent.appendChild(newContent); } previousOutputParent.classList.toggle('scrollbar-visible', previousOutputParent.scrollHeight > previousOutputParent.clientHeight); diff --git a/extensions/notebook-renderers/src/textHelper.ts b/extensions/notebook-renderers/src/textHelper.ts index 97b3a1ba694..0c65e0f6d93 100644 --- a/extensions/notebook-renderers/src/textHelper.ts +++ b/extensions/notebook-renderers/src/textHelper.ts @@ -128,7 +128,7 @@ export function appendScrollableOutput(element: HTMLElement, id: string, appende outputLengths[id] = Math.min(fullBuffer.length, softScrollableLineLimit); const newElement = scrollableArrayOfString(id, fullBuffer.slice(-1 * softScrollableLineLimit), trustHtml); newElement.setAttribute('output-item-id', id); - element.replaceWith(); + element.replaceWith(newElement); } else { element.appendChild(handleANSIOutput(buffer.join('\n'), trustHtml)); From 5b9cf3d53bad0f080318283e85aa580bcd2152f3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Jul 2023 00:40:53 +0200 Subject: [PATCH 068/826] Fix #176813 (#187750) * Fix #176813 * fix tests * fix tests * address feedback --- .../common/configurationModels.ts | 22 ++-- .../common/configurationService.ts | 2 +- .../test/common/configurationModels.test.ts | 48 ++++++++ .../test/common/configurations.test.ts | 8 +- .../browser/preferencesRenderers.ts | 25 ++-- .../settingsEditorSettingIndicators.ts | 17 ++- .../preferences/browser/settingsTree.ts | 76 +++++++++--- .../preferences/browser/settingsTreeModels.ts | 21 ++-- .../configuration/browser/configuration.ts | 50 ++++++-- .../browser/configurationService.ts | 113 +++++++++++++----- .../configuration/common/configuration.ts | 8 ++ .../common/configurationEditing.ts | 16 +-- .../test/browser/configurationService.test.ts | 104 +++++++++++++++- 13 files changed, 407 insertions(+), 103 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 9e171f9b6cb..4055e5ae0b9 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -269,8 +269,10 @@ export class ConfigurationModel implements IConfigurationModel { } export interface ConfigurationParseOptions { - scopes: ConfigurationScope[] | undefined; + scopes?: ConfigurationScope[]; skipRestricted?: boolean; + include?: string[]; + exclude?: string[]; } export class ConfigurationModelParser { @@ -383,7 +385,7 @@ export class ConfigurationModelParser { private filter(properties: any, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema | undefined }, filterOverriddenProperties: boolean, options?: ConfigurationParseOptions): { raw: {}; restricted: string[]; hasExcludedProperties: boolean } { let hasExcludedProperties = false; - if (!options?.scopes && !options?.skipRestricted) { + if (!options?.scopes && !options?.skipRestricted && !options?.exclude?.length) { return { raw: properties, restricted: [], hasExcludedProperties }; } const raw: any = {}; @@ -400,9 +402,10 @@ export class ConfigurationModelParser { if (propertySchema?.restricted) { restricted.push(key); } - // Load unregistered configurations always. - if ((scope === undefined || options.scopes === undefined || options.scopes.includes(scope)) // Check scopes - && !(options.skipRestricted && propertySchema?.restricted)) { // Check restricted + if (!options.exclude?.includes(key) /* Check exclude */ + && (options.include?.includes(key) /* Check include */ + || ((scope === undefined || options.scopes === undefined || options.scopes.includes(scope)) /* Check scopes */ + && !(options.skipRestricted && propertySchema?.restricted)))) /* Check restricted */ { raw[key] = properties[key]; } else { hasExcludedProperties = true; @@ -435,19 +438,17 @@ export class ConfigurationModelParser { export class UserSettings extends Disposable { private readonly parser: ConfigurationModelParser; - private readonly parseOptions: ConfigurationParseOptions; protected readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; constructor( private readonly userSettingsResource: URI, - private readonly scopes: ConfigurationScope[] | undefined, + protected parseOptions: ConfigurationParseOptions, extUri: IExtUri, private readonly fileService: IFileService ) { super(); this.parser = new ConfigurationModelParser(this.userSettingsResource.toString()); - this.parseOptions = { scopes: this.scopes }; this._register(this.fileService.watch(extUri.dirname(this.userSettingsResource))); // Also listen to the resource incase the resource is a symlink - https://github.com/microsoft/vscode/issues/118134 this._register(this.fileService.watch(this.userSettingsResource)); @@ -467,7 +468,10 @@ export class UserSettings extends Disposable { } } - reparse(): ConfigurationModel { + reparse(parseOptions?: ConfigurationParseOptions): ConfigurationModel { + if (parseOptions) { + this.parseOptions = parseOptions; + } this.parser.reparse(this.parseOptions); return this.parser.configurationModel; } diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 8a4ccfe329d..7eee99fd90e 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -37,7 +37,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe super(); this.defaultConfiguration = this._register(new DefaultConfiguration()); this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); - this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); + this.userConfiguration = this._register(new UserSettings(this.settingsResource, {}, extUriBiasedIgnorePathCase, fileService)); this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel()); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50)); diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 1969669fb62..db22861ee4a 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -6,11 +6,26 @@ import * as assert from 'assert'; import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; +import { IConfigurationRegistry, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; suite('ConfigurationModelParser', () => { + suiteSetup(() => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'ConfigurationModelParserTest', + 'type': 'object', + 'properties': { + 'ConfigurationModelParserTest.windowSetting': { + 'type': 'string', + 'default': 'isSet', + } + } + }); + }); + test('parse configuration model with single override identifier', () => { const testObject = new ConfigurationModelParser(''); @@ -35,6 +50,39 @@ suite('ConfigurationModelParser', () => { assert.deepStrictEqual(JSON.stringify(testObject.configurationModel.overrides), JSON.stringify([{ identifiers: ['x', 'y', 'z'], keys: ['a'], contents: { 'a': 1 } }])); }); + test('parse configuration model with exclude option', () => { + const testObject = new ConfigurationModelParser(''); + + testObject.parse(JSON.stringify({ 'a': 1, 'b': 2 }), { exclude: ['a'] }); + + assert.strictEqual(testObject.configurationModel.getValue('a'), undefined); + assert.strictEqual(testObject.configurationModel.getValue('b'), 2); + }); + + test('parse configuration model with exclude option even included', () => { + const testObject = new ConfigurationModelParser(''); + + testObject.parse(JSON.stringify({ 'a': 1, 'b': 2 }), { exclude: ['a'], include: ['a'] }); + + assert.strictEqual(testObject.configurationModel.getValue('a'), undefined); + assert.strictEqual(testObject.configurationModel.getValue('b'), 2); + }); + + test('parse configuration model with scopes filter', () => { + const testObject = new ConfigurationModelParser(''); + + testObject.parse(JSON.stringify({ 'ConfigurationModelParserTest.windowSetting': '1' }), { scopes: [ConfigurationScope.APPLICATION] }); + + assert.strictEqual(testObject.configurationModel.getValue('ConfigurationModelParserTest.windowSetting'), undefined); + }); + + test('parse configuration model with include option', () => { + const testObject = new ConfigurationModelParser(''); + + testObject.parse(JSON.stringify({ 'ConfigurationModelParserTest.windowSetting': '1' }), { include: ['ConfigurationModelParserTest.windowSetting'], scopes: [ConfigurationScope.APPLICATION] }); + + assert.strictEqual(testObject.configurationModel.getValue('ConfigurationModelParserTest.windowSetting'), '1'); + }); }); diff --git a/src/vs/platform/configuration/test/common/configurations.test.ts b/src/vs/platform/configuration/test/common/configurations.test.ts index 02d0f4e8890..30cddfd2144 100644 --- a/src/vs/platform/configuration/test/common/configurations.test.ts +++ b/src/vs/platform/configuration/test/common/configurations.test.ts @@ -62,15 +62,15 @@ suite('DefaultConfiguration', () => { test('Test registering a property after initialize', async () => { const testObject = new DefaultConfiguration(); - const promise = Event.toPromise(testObject.onDidChangeConfiguration); await testObject.initialize(); + const promise = Event.toPromise(testObject.onDidChangeConfiguration); configurationRegistry.registerConfiguration({ 'id': 'a', 'order': 1, 'title': 'a', 'type': 'object', 'properties': { - 'a': { + 'defaultConfiguration.testSetting1': { 'description': 'a', 'type': 'boolean', 'default': false, @@ -78,8 +78,8 @@ suite('DefaultConfiguration', () => { } }); const { defaults: actual, properties } = await promise; - assert.strictEqual(actual.getValue('a'), false); - assert.deepStrictEqual(properties, ['a']); + assert.strictEqual(actual.getValue('defaultConfiguration.testSetting1'), false); + assert.deepStrictEqual(properties, ['defaultConfiguration.testSetting1']); }); test('Test registering nested properties', async () => { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 76f3f201b1c..c80d1d641c8 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -44,6 +44,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { isEqual } from 'vs/base/common/resources'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IStringDictionary } from 'vs/base/common/collections'; +import { APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; export interface IPreferencesRenderer extends IDisposable { render(): void; @@ -481,7 +482,7 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc private readonly settingsEditorModel: SettingsEditorModel, @IMarkerService private readonly markerService: IMarkerService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @@ -603,19 +604,27 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc private handleLocalUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { if (!this.userDataProfileService.currentProfile.isDefault && !this.userDataProfileService.currentProfile.useDefaultFlags?.settings) { - if (isEqual(this.userDataProfilesService.defaultProfile.settingsResource, this.settingsEditorModel.uri) && configuration.scope !== ConfigurationScope.APPLICATION) { - // If we're in the default profile setting file, and the setting is not - // application-scoped, fade it out. + if (isEqual(this.userDataProfilesService.defaultProfile.settingsResource, this.settingsEditorModel.uri) && !this.configurationService.isSettingAppliedForAllProfiles(setting.key)) { + // If we're in the default profile setting file, and the setting cannot be applied in all profiles markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, message: nls.localize('defaultProfileSettingWhileNonDefaultActive', "This setting cannot be applied while a non-default profile is active. It will be applied when the default profile is active.") }); - } else if (isEqual(this.userDataProfileService.currentProfile.settingsResource, this.settingsEditorModel.uri) && configuration.scope === ConfigurationScope.APPLICATION) { - // If we're in a profile setting file, and the setting is - // application-scoped, fade it out. - markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); + } else if (isEqual(this.userDataProfileService.currentProfile.settingsResource, this.settingsEditorModel.uri)) { + if (configuration.scope === ConfigurationScope.APPLICATION) { + // If we're in a profile setting file, and the setting is application-scoped, fade it out. + markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); + } else if (this.configurationService.isSettingAppliedForAllProfiles(setting.key)) { + // If we're in the non-default profile setting file, and the setting can be applied in all profiles, fade it out. + markerData.push({ + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + ...setting.range, + message: nls.localize('allProfileSettingWhileInNonDefaultProfileSetting', "This setting cannot be applied because it is configured to be applied in all profiles using setting {0}. Value from the default profile will be used instead.", APPLY_ALL_PROFILES_SETTING) + }); + } } } if (this.environmentService.remoteAuthority && (configuration.scope === ConfigurationScope.MACHINE || configuration.scope === ConfigurationScope.MACHINE_OVERRIDABLE)) { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 0482a32a1a4..645127cd814 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -15,11 +15,12 @@ import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { POLICY_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover'; const $ = DOM.$; @@ -75,7 +76,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { constructor( container: HTMLElement, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, @IHoverService private readonly hoverService: IHoverService, @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @ILanguageService private readonly languageService: ILanguageService, @@ -330,14 +331,11 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { }, focus); }; this.addHoverDisposables(this.scopeOverridesIndicator.disposables, this.scopeOverridesIndicator.element, showHover); - } else if (this.profilesEnabled && element.matchesScope(ConfigurationTarget.APPLICATION, false)) { - // If the setting is an application-scoped setting, there are no overrides so we can use this - // indicator to display that information instead. + } else if (this.profilesEnabled && element.settingsTarget === ConfigurationTarget.USER_LOCAL && this.configurationService.isSettingAppliedForAllProfiles(element.setting.key)) { this.scopeOverridesIndicator.element.style.display = 'inline'; this.scopeOverridesIndicator.element.classList.add('setting-indicator'); - const applicationSettingText = localize('applicationSetting', "Applies to all profiles"); - this.scopeOverridesIndicator.label.text = applicationSettingText; + this.scopeOverridesIndicator.label.text = localize('applicationSetting', "Applies to all profiles"); const content = localize('applicationSettingDescription', "The setting is not specific to the current profile, and will retain its value when switching profiles."); const showHover = (focus: boolean) => { @@ -499,7 +497,7 @@ function getAccessibleScopeDisplayMidSentenceText(completeScope: string, languag return localizedScope; } -export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, configurationService: IConfigurationService, userDataProfilesService: IUserDataProfilesService, languageService: ILanguageService): string { +export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, configurationService: IWorkbenchConfigurationService, userDataProfilesService: IUserDataProfilesService, languageService: ILanguageService): string { const ariaLabelSections: string[] = []; // Add workspace trust text @@ -507,10 +505,9 @@ export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, ariaLabelSections.push(localize('workspaceUntrustedAriaLabel', "Workspace untrusted; setting value not applied")); } - const profilesEnabled = userDataProfilesService.isEnabled(); if (element.hasPolicyValue) { ariaLabelSections.push(localize('policyDescriptionAccessible', "Managed by organization policy; setting value not applied")); - } else if (profilesEnabled && element.matchesScope(ConfigurationTarget.APPLICATION, false)) { + } else if (userDataProfilesService.isEnabled() && element.settingsTarget === ConfigurationTarget.USER_LOCAL && configurationService.isSettingAppliedForAllProfiles(element.setting.key)) { ariaLabelSections.push(localize('applicationSettingDescriptionAccessible', "Setting value retained when switching profiles")); } else { // Add other overrides text diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index eb63f256d47..ff00480a0ee 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -23,6 +23,7 @@ import { IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; import { ITreeFilter, ITreeModel, ITreeNode, ITreeRenderer, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { Action, IAction, Separator } from 'vs/base/common/actions'; +import { distinct } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -63,7 +64,7 @@ import { ISettingsEditorViewState, SettingsTreeElement, SettingsTreeGroupChild, import { ExcludeSettingWidget, IListDataItem, IObjectDataItem, IObjectEnumOption, IObjectKeySuggester, IObjectValueSuggester, ISettingListChangeEvent, IncludeSettingWidget, ListSettingWidget, ObjectSettingCheckboxWidget, ObjectSettingDropdownWidget, ObjectValue } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { LANGUAGE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; -import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; @@ -758,7 +759,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre constructor( private readonly settingActions: IAction[], - private readonly disposableActionFactory: (setting: ISetting) => IAction[], + private readonly disposableActionFactory: (setting: ISetting, settingTarget: SettingsTarget) => IAction[], @IThemeService protected readonly _themeService: IThemeService, @IContextViewService protected readonly _contextViewService: IContextViewService, @IOpenerService protected readonly _openerService: IOpenerService, @@ -875,7 +876,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const element = node.element; template.context = element; template.toolbar.context = element; - const actions = this.disposableActionFactory(element.setting); + const actions = this.disposableActionFactory(element.setting, element.settingsTarget); actions.forEach(a => isDisposable(a) && template.elementDisposables.add(a)); template.toolbar.setActions([], [...this.settingActions, ...actions]); @@ -901,6 +902,11 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre } template.indicatorsLabel.updateScopeOverrides(element, this._onDidClickOverrideElement, this._onApplyFilter); + template.elementDisposables.add(this._configService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(APPLY_ALL_PROFILES_SETTING)) { + template.indicatorsLabel.updateScopeOverrides(element, this._onDidClickOverrideElement, this._onApplyFilter); + } + })); const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, @@ -1959,6 +1965,7 @@ export class SettingTreeRenderers { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IContextViewService private readonly _contextViewService: IContextViewService, + @IUserDataProfilesService private readonly _userDataProfilesService: IUserDataProfilesService, @IUserDataSyncEnablementService private readonly _userDataSyncEnablementService: IUserDataSyncEnablementService, ) { this.settingActions = [ @@ -1980,7 +1987,7 @@ export class SettingTreeRenderers { this._instantiationService.createInstance(CopySettingAsJSONAction), ]; - const actionFactory = (setting: ISetting) => this.getActionsForSetting(setting); + const actionFactory = (setting: ISetting, settingTarget: SettingsTarget) => this.getActionsForSetting(setting, settingTarget); const emptyActionFactory = (_: ISetting) => []; const settingRenderers = [ this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory), @@ -2015,14 +2022,18 @@ export class SettingTreeRenderers { ]; } - private getActionsForSetting(setting: ISetting): IAction[] { - const enableSync = this._userDataSyncEnablementService.isEnabled(); - return enableSync && !setting.disallowSyncIgnore ? - [ - new Separator(), - this._instantiationService.createInstance(SyncSettingAction, setting) - ] : - []; + private getActionsForSetting(setting: ISetting, settingTarget: SettingsTarget): IAction[] { + const actions: IAction[] = []; + if (this._userDataProfilesService.isEnabled() && setting.scope !== ConfigurationScope.APPLICATION && settingTarget === ConfigurationTarget.USER_LOCAL) { + actions.push(this._instantiationService.createInstance(ApplySettingToAllProfilesAction, setting)); + } + if (this._userDataSyncEnablementService.isEnabled() && !setting.disallowSyncIgnore) { + actions.push(this._instantiationService.createInstance(SyncSettingAction, setting)); + } + if (actions.length) { + actions.splice(0, 0, new Separator()); + } + return actions; } cancelSuggesters() { @@ -2295,7 +2306,7 @@ export class NonCollapsibleObjectTreeModel extends ObjectTreeModel { } class SettingsTreeAccessibilityProvider implements IListAccessibilityProvider { - constructor(private readonly configurationService: IConfigurationService, private readonly languageService: ILanguageService, private readonly userDataProfilesService: IUserDataProfilesService) { + constructor(private readonly configurationService: IWorkbenchConfigurationService, private readonly languageService: ILanguageService, private readonly userDataProfilesService: IUserDataProfilesService) { } getAriaLabel(element: SettingsTreeElement) { @@ -2337,7 +2348,7 @@ export class SettingsTree extends WorkbenchObjectTree { renderers: ITreeRenderer[], @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, - @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService, @IInstantiationService instantiationService: IInstantiationService, @ILanguageService languageService: ILanguageService, @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService @@ -2485,3 +2496,40 @@ class SyncSettingAction extends Action { } } + +class ApplySettingToAllProfilesAction extends Action { + static readonly ID = 'settings.applyToAllProfiles'; + static readonly LABEL = localize('applyToAllProfiles', "Apply Setting to all Profiles"); + + constructor( + private readonly setting: ISetting, + @IWorkbenchConfigurationService private readonly configService: IWorkbenchConfigurationService, + ) { + super(ApplySettingToAllProfilesAction.ID, ApplySettingToAllProfilesAction.LABEL); + this._register(Event.filter(configService.onDidChangeConfiguration, e => e.affectsConfiguration(APPLY_ALL_PROFILES_SETTING))(() => this.update())); + this.update(); + } + + update() { + const allProfilesSettings = this.configService.getValue(APPLY_ALL_PROFILES_SETTING); + this.checked = allProfilesSettings.includes(this.setting.key); + } + + override async run(): Promise { + // first remove the current setting completely from ignored settings + const value = this.configService.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; + + if (this.checked) { + value.splice(value.indexOf(this.setting.key), 1); + } else { + value.push(this.setting.key); + } + + const newValue = distinct(value); + await this.configService.updateValue(APPLY_ALL_PROFILES_SETTING, newValue.length ? newValue : undefined, ConfigurationTarget.USER_LOCAL); + if (!this.checked) { + await this.configService.updateValue(this.setting.key, this.configService.inspect(this.setting.key).userLocal?.value, ConfigurationTarget.USER_LOCAL); + } + } + +} diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 27d5d03efbd..e55eba8fa77 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -169,6 +169,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { parent: SettingsTreeGroupElement, inspectResult: IInspectResult, isWorkspaceTrusted: boolean, + readonly settingsTarget: SettingsTarget, private readonly languageService: ILanguageService, private readonly productService: IProductService ) { @@ -544,17 +545,21 @@ export class SettingsTreeModel { this.updateSettings([...this._treeElementsBySettingName.values()].flat().filter(s => s.isUntrusted)); } - private getTargetToInspect(settingScope: ConfigurationScope | undefined): SettingsTarget { - if (!this._userDataProfileService.currentProfile.isDefault && settingScope === ConfigurationScope.APPLICATION) { - return ConfigurationTarget.APPLICATION; - } else { - return this._viewState.settingsTarget; + private getTargetToInspect(setting: ISetting): SettingsTarget { + if (!this._userDataProfileService.currentProfile.isDefault) { + if (setting.scope === ConfigurationScope.APPLICATION) { + return ConfigurationTarget.APPLICATION; + } + if (this._configurationService.isSettingAppliedForAllProfiles(setting.key) && this._viewState.settingsTarget === ConfigurationTarget.USER_LOCAL) { + return ConfigurationTarget.APPLICATION; + } } + return this._viewState.settingsTarget; } private updateSettings(settings: SettingsTreeSettingElement[]): void { for (const element of settings) { - const target = this.getTargetToInspect(element.setting.scope); + const target = this.getTargetToInspect(element.setting); const inspectResult = inspectSetting(element.setting.key, target, this._viewState.languageFilter, this._configurationService); element.update(inspectResult, this._isWorkspaceTrusted); } @@ -591,9 +596,9 @@ export class SettingsTreeModel { } private createSettingsTreeSettingElement(setting: ISetting, parent: SettingsTreeGroupElement): SettingsTreeSettingElement { - const target = this.getTargetToInspect(setting.scope); + const target = this.getTargetToInspect(setting); const inspectResult = inspectSetting(setting.key, target, this._viewState.languageFilter, this._configurationService); - const element = new SettingsTreeSettingElement(setting, parent, inspectResult, this._isWorkspaceTrusted, this._languageService, this._productService); + const element = new SettingsTreeSettingElement(setting, parent, inspectResult, this._isWorkspaceTrusted, this._viewState.settingsTarget, this._languageService, this._productService); const nameElements = this._treeElementsBySettingName.get(setting.key) || []; nameElements.push(element); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 83e284a3df0..8f942efdd0e 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -11,7 +11,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult, FileOperation, FileOperationEvent } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; -import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; +import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES, APPLY_ALL_PROFILES_SETTING } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { WorkbenchState, IWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { ConfigurationScope, Extensions, IConfigurationRegistry, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; @@ -27,6 +27,7 @@ import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/envir import { isEmptyObject, isObject } from 'vs/base/common/types'; import { DefaultConfiguration as BaseDefaultConfiguration } from 'vs/platform/configuration/common/configurations'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; export class DefaultConfiguration extends BaseDefaultConfiguration { @@ -118,6 +119,37 @@ export class DefaultConfiguration extends BaseDefaultConfiguration { } +export class ApplicationConfiguration extends UserSettings { + + private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + + private readonly reloadConfigurationScheduler: RunOnceScheduler; + + constructor( + userDataProfilesService: IUserDataProfilesService, + fileService: IFileService, + uriIdentityService: IUriIdentityService, + ) { + super(userDataProfilesService.defaultProfile.settingsResource, { scopes: [ConfigurationScope.APPLICATION] }, uriIdentityService.extUri, fileService); + this._register(this.onDidChange(() => this.reloadConfigurationScheduler.schedule())); + this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); + } + + async initialize(): Promise { + return this.loadConfiguration(); + } + + override async loadConfiguration(): Promise { + const model = await super.loadConfiguration(); + const value = model.getValue(APPLY_ALL_PROFILES_SETTING); + const allProfilesSettings = Array.isArray(value) ? value : []; + return this.parseOptions.include || allProfilesSettings.length + ? this.reparse({ ...this.parseOptions, include: allProfilesSettings }) + : model; + } +} + export class UserConfiguration extends Disposable { private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); @@ -127,29 +159,26 @@ export class UserConfiguration extends Disposable { private readonly userConfigurationChangeDisposable = this._register(new MutableDisposable()); private readonly reloadConfigurationScheduler: RunOnceScheduler; - private configurationParseOptions: ConfigurationParseOptions; - get hasTasksLoaded(): boolean { return this.userConfiguration.value instanceof FileServiceBasedConfiguration; } constructor( private settingsResource: URI, private tasksResource: URI | undefined, - scopes: ConfigurationScope[] | undefined, + private configurationParseOptions: ConfigurationParseOptions, private readonly fileService: IFileService, private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, ) { super(); - this.configurationParseOptions = { scopes, skipRestricted: false }; - this.userConfiguration.value = new UserSettings(settingsResource, scopes, uriIdentityService.extUri, this.fileService); + this.userConfiguration.value = new UserSettings(settingsResource, this.configurationParseOptions, uriIdentityService.extUri, this.fileService); this.userConfigurationChangeDisposable.value = this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule()); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.userConfiguration.value!.loadConfiguration().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); } - async reset(settingsResource: URI, tasksResource: URI | undefined, scopes: ConfigurationScope[] | undefined): Promise { + async reset(settingsResource: URI, tasksResource: URI | undefined, configurationParseOptions: ConfigurationParseOptions): Promise { this.settingsResource = settingsResource; this.tasksResource = tasksResource; - this.configurationParseOptions = { scopes, skipRestricted: false }; + this.configurationParseOptions = configurationParseOptions; const folder = this.uriIdentityService.extUri.dirname(this.settingsResource); const standAloneConfigurationResources: [string, URI][] = this.tasksResource ? [[TASKS_CONFIGURATION_KEY, this.tasksResource]] : []; const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.settingsResource, standAloneConfigurationResources, this.configurationParseOptions, this.fileService, this.uriIdentityService, this.logService); @@ -172,10 +201,11 @@ export class UserConfiguration extends Disposable { if (this.hasTasksLoaded) { return this.userConfiguration.value!.loadConfiguration(); } - return this.reset(this.settingsResource, this.tasksResource, this.configurationParseOptions.scopes); + return this.reset(this.settingsResource, this.tasksResource, this.configurationParseOptions); } - reparse(): ConfigurationModel { + reparse(parseOptions?: Partial): ConfigurationModel { + this.configurationParseOptions = { ...this.configurationParseOptions, ...parseOptions }; return this.userConfiguration.value!.reparse(this.configurationParseOptions); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 7e68eba7dd7..9a920e59af0 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -15,13 +15,13 @@ import { ConfigurationModel, ConfigurationChangeEvent, mergeChanges } from 'vs/p import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService, IConfigurationUpdateOptions } from 'vs/platform/configuration/common/configuration'; import { IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId, APPLY_ALL_PROFILES_SETTING } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId } from 'vs/platform/configuration/common/configurationRegistry'; import { IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, getStoredWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditing, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration, DefaultConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; +import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration, DefaultConfiguration, ApplicationConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { mark } from 'vs/base/common/performance'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -44,6 +44,7 @@ import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/pol import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined { return userDataProfile.isDefault @@ -67,7 +68,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private initialized: boolean = false; private readonly defaultConfiguration: DefaultConfiguration; private readonly policyConfiguration: IPolicyConfiguration; - private applicationConfiguration: UserConfiguration | null = null; + private applicationConfiguration: ApplicationConfiguration | null = null; private readonly applicationConfigurationDisposables: DisposableStore; private readonly localUserConfiguration: UserConfiguration; private readonly remoteUserConfiguration: RemoteUserConfiguration | null = null; @@ -125,7 +126,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); this.applicationConfigurationDisposables = this._register(new DisposableStore()); this.createApplicationConfiguration(); - this.localUserConfiguration = this._register(new UserConfiguration(userDataProfileService.currentProfile.settingsResource, userDataProfileService.currentProfile.tasksResource, getLocalUserConfigurationScopes(userDataProfileService.currentProfile, !!remoteAuthority), fileService, uriIdentityService, logService)); + this.localUserConfiguration = this._register(new UserConfiguration(userDataProfileService.currentProfile.settingsResource, userDataProfileService.currentProfile.tasksResource, { scopes: getLocalUserConfigurationScopes(userDataProfileService.currentProfile, !!remoteAuthority) }, fileService, uriIdentityService, logService)); this.cachedFolderConfigs = new ResourceMap(); this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); if (remoteAuthority) { @@ -159,7 +160,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat if (this.userDataProfileService.currentProfile.isDefault || this.userDataProfileService.currentProfile.useDefaultFlags?.settings) { this.applicationConfiguration = null; } else { - this.applicationConfiguration = this.applicationConfigurationDisposables.add(this._register(new UserConfiguration(this.userDataProfilesService.defaultProfile.settingsResource, undefined, [ConfigurationScope.APPLICATION], this.fileService, this.uriIdentityService, this.logService))); + this.applicationConfiguration = this.applicationConfigurationDisposables.add(this._register(new ApplicationConfiguration(this.userDataProfilesService, this.fileService, this.uriIdentityService))); this.applicationConfigurationDisposables.add(this.applicationConfiguration.onDidChangeConfiguration(configurationModel => this.onApplicationConfigurationChanged(configurationModel))); } } @@ -488,6 +489,14 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.instantiationService = instantiationService; } + isSettingAppliedForAllProfiles(key: string): boolean { + if (this.configurationRegistry.getConfigurationProperties()[key]?.scope === ConfigurationScope.APPLICATION) { + return true; + } + const allProfilesSettings = this.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; + return Array.isArray(allProfilesSettings) && allProfilesSettings.includes(key); + } + private async createWorkspace(arg: IAnyWorkspaceIdentifier): Promise { if (isWorkspaceIdentifier(arg)) { return this.createMultiFolderWorkspace(arg); @@ -592,31 +601,30 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private async initializeConfiguration(trigger: boolean): Promise { await this.defaultConfiguration.initialize(); - const [, application, user] = await Promise.all([ - this.policyConfiguration.initialize(), - this.initializeApplicationConfiguration(), - (async () => { - mark('code/willInitUserConfiguration'); - const result = await this.initializeUserConfiguration(); - mark('code/didInitUserConfiguration'); - return result; - })() + const initPolicyConfigurationPromise = this.policyConfiguration.initialize(); + const initApplicationConfigurationPromise = this.applicationConfiguration ? this.applicationConfiguration.initialize() : Promise.resolve(new ConfigurationModel()); + const initUserConfiguration = async () => { + mark('code/willInitUserConfiguration'); + const result = await Promise.all([this.localUserConfiguration.initialize(), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())]); + if (this.applicationConfiguration) { + const applicationConfigurationModel = await initApplicationConfigurationPromise; + result[0] = this.localUserConfiguration.reparse({ exclude: applicationConfigurationModel.getValue(APPLY_ALL_PROFILES_SETTING) }); + } + mark('code/didInitUserConfiguration'); + return result; + }; + + const [, application, [local, remote]] = await Promise.all([ + initPolicyConfigurationPromise, + initApplicationConfigurationPromise, + initUserConfiguration() ]); mark('code/willInitWorkspaceConfiguration'); - await this.loadConfiguration(application, user.local, user.remote, trigger); + await this.loadConfiguration(application, local, remote, trigger); mark('code/didInitWorkspaceConfiguration'); } - private async initializeApplicationConfiguration(): Promise { - return this.applicationConfiguration ? this.applicationConfiguration.initialize() : Promise.resolve(new ConfigurationModel()); - } - - private async initializeUserConfiguration(): Promise<{ local: ConfigurationModel; remote: ConfigurationModel }> { - const [local, remote] = await Promise.all([this.localUserConfiguration.initialize(), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())]); - return { local, remote }; - } - private reloadDefaultConfiguration(): void { this.onDefaultConfigurationChanged(this.defaultConfiguration.reload()); } @@ -625,7 +633,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat if (!this.applicationConfiguration) { return new ConfigurationModel(); } - const model = await this.applicationConfiguration.reload(); + const model = await this.applicationConfiguration.loadConfiguration(); if (!donotTrigger) { this.onApplicationConfigurationChanged(model); } @@ -708,15 +716,19 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private onUserDataProfileChanged(e: DidChangeUserDataProfileEvent): void { e.join((async () => { const promises: Promise[] = []; - promises.push(this.localUserConfiguration.reset(e.profile.settingsResource, e.profile.tasksResource, getLocalUserConfigurationScopes(e.profile, !!this.remoteUserConfiguration))); + promises.push(this.localUserConfiguration.reset(e.profile.settingsResource, e.profile.tasksResource, { scopes: getLocalUserConfigurationScopes(e.profile, !!this.remoteUserConfiguration) })); if (e.previous.isDefault !== e.profile.isDefault) { this.createApplicationConfiguration(); if (this.applicationConfiguration) { promises.push(this.reloadApplicationConfiguration(true)); } } - const [localUser, application] = await Promise.all(promises); - await this.loadConfiguration(application ?? this._configuration.applicationConfiguration, localUser, this._configuration.remoteUserConfiguration, true); + let [localUser, application] = await Promise.all(promises); + application = application ?? this._configuration.applicationConfiguration; + if (this.applicationConfiguration) { + localUser = this.localUserConfiguration.reparse({ exclude: application.getValue(APPLY_ALL_PROFILES_SETTING) }); + } + await this.loadConfiguration(application, localUser, this._configuration.remoteUserConfiguration, true); })()); } @@ -759,9 +771,35 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private onApplicationConfigurationChanged(applicationConfiguration: ConfigurationModel): void { const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const previousAllProfilesSettings = this._configuration.applicationConfiguration.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; const change = this._configuration.compareAndUpdateApplicationConfiguration(applicationConfiguration); + const currentAllProfilesSettings = this.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; const configurationProperties = this.configurationRegistry.getConfigurationProperties(); - change.keys = change.keys.filter(key => configurationProperties[key]?.scope === ConfigurationScope.APPLICATION); + const changedKeys: string[] = []; + for (const changedKey of change.keys) { + if (configurationProperties[changedKey]?.scope === ConfigurationScope.APPLICATION) { + changedKeys.push(changedKey); + if (changedKey === APPLY_ALL_PROFILES_SETTING) { + for (const previousAllProfileSetting of previousAllProfilesSettings) { + if (!currentAllProfilesSettings.includes(previousAllProfileSetting)) { + changedKeys.push(previousAllProfileSetting); + } + } + for (const currentAllProfileSetting of currentAllProfilesSettings) { + if (!previousAllProfilesSettings.includes(currentAllProfileSetting)) { + changedKeys.push(currentAllProfileSetting); + } + } + } + } + else if (currentAllProfilesSettings.includes(changedKey)) { + changedKeys.push(changedKey); + } + } + change.keys = changedKeys; + if (change.keys.includes(APPLY_ALL_PROFILES_SETTING)) { + this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reparse({ exclude: currentAllProfilesSettings })); + } this.triggerConfigurationChange(change, previous, ConfigurationTarget.USER); } @@ -981,7 +1019,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat await this.configurationEditing.writeConfiguration(editableConfigurationTarget, { key, value }, { scopes: overrides, ...options }); switch (editableConfigurationTarget) { case EditableConfigurationTarget.USER_LOCAL: - if (this.applicationConfiguration && this.configurationRegistry.getConfigurationProperties()[key]?.scope === ConfigurationScope.APPLICATION) { + if (this.applicationConfiguration && this.isSettingAppliedForAllProfiles(key)) { await this.reloadApplicationConfiguration(); } else { await this.reloadLocalUserConfiguration(); @@ -1313,3 +1351,18 @@ const workbenchContributionsRegistry = Registry.as(Extensions.Configuration); +configurationRegistry.registerConfiguration({ + ...workbenchConfigurationNodeBase, + properties: { + [APPLY_ALL_PROFILES_SETTING]: { + 'type': 'array', + description: localize('setting description', "Configure settings to be applied for all profiles."), + 'default': [], + 'scope': ConfigurationScope.APPLICATION, + additionalProperties: true, + uniqueItems: true, + } + } +}); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 95888019697..043add6bd72 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -84,6 +84,14 @@ export interface IWorkbenchConfigurationService extends IConfigurationService { * @param arg workspace Identifier */ initialize(arg: IAnyWorkspaceIdentifier): Promise; + + /** + * Returns true if the setting can be applied for all profiles otherwise false. + * @param setting + */ + isSettingAppliedForAllProfiles(setting: string): boolean; } export const TASKS_DEFAULT = '{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": []\n}'; + +export const APPLY_ALL_PROFILES_SETTING = 'workbench.settings.applyToAllProfiles'; diff --git a/src/vs/workbench/services/configuration/common/configurationEditing.ts b/src/vs/workbench/services/configuration/common/configurationEditing.ts index 04982ee6684..adbf60a3ef6 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditing.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditing.ts @@ -12,8 +12,8 @@ import { Edit, FormattingOptions } from 'vs/base/common/jsonFormatter'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IConfigurationService, IConfigurationUpdateOptions, IConfigurationUpdateOverrides } from 'vs/platform/configuration/common/configuration'; -import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS, TASKS_DEFAULT, FOLDER_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; +import { IConfigurationUpdateOptions, IConfigurationUpdateOverrides } from 'vs/platform/configuration/common/configuration'; +import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS, TASKS_DEFAULT, FOLDER_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; @@ -144,7 +144,7 @@ export class ConfigurationEditing { constructor( private readonly remoteSettingsResource: URI | null, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @@ -586,10 +586,10 @@ export class ConfigurationEditing { const configurationScope = configurationProperties[key]?.scope; let jsonPath = overrides.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key]; if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) { - return { key, jsonPath, value: config.value, resource: withNullAsUndefined(this.getConfigurationFileResource(target, undefined, '', null, configurationScope)), target }; + return { key, jsonPath, value: config.value, resource: withNullAsUndefined(this.getConfigurationFileResource(target, key, '', null, configurationScope)), target }; } - const resource = this.getConfigurationFileResource(target, undefined, FOLDER_SETTINGS_PATH, overrides.resource, configurationScope); + const resource = this.getConfigurationFileResource(target, key, FOLDER_SETTINGS_PATH, overrides.resource, configurationScope); if (this.isWorkspaceConfigurationResource(resource)) { jsonPath = ['settings', ...jsonPath]; } @@ -601,12 +601,12 @@ export class ConfigurationEditing { return !!(workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath); } - private getConfigurationFileResource(target: EditableConfigurationTarget, standAloneConfigurationKey: string | undefined, relativePath: string, resource: URI | null | undefined, scope: ConfigurationScope | undefined): URI | null { + private getConfigurationFileResource(target: EditableConfigurationTarget, key: string, relativePath: string, resource: URI | null | undefined, scope: ConfigurationScope | undefined): URI | null { if (target === EditableConfigurationTarget.USER_LOCAL) { - if (standAloneConfigurationKey === TASKS_CONFIGURATION_KEY) { + if (key === TASKS_CONFIGURATION_KEY) { return this.userDataProfileService.currentProfile.tasksResource; } else { - if (scope === ConfigurationScope.APPLICATION && !this.userDataProfileService.currentProfile.isDefault) { + if (!this.userDataProfileService.currentProfile.isDefault && this.configurationService.isSettingAppliedForAllProfiles(key)) { return this.userDataProfilesService.defaultProfile.settingsResource; } return this.userDataProfileService.currentProfile.settingsResource; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 3ec31a25147..e0613cac0c7 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -28,7 +28,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; +import { APPLY_ALL_PROFILES_SETTING, IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; import { SignService } from 'vs/platform/sign/browser/signService'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; @@ -1529,6 +1529,11 @@ suite('WorkspaceConfigurationService - Profiles', () => { 'id': '_test', 'type': 'object', 'properties': { + [APPLY_ALL_PROFILES_SETTING]: { + 'type': 'array', + 'default': [], + 'scope': ConfigurationScope.APPLICATION, + }, 'configurationService.profiles.applicationSetting': { 'type': 'string', 'default': 'isSet', @@ -1682,6 +1687,63 @@ suite('WorkspaceConfigurationService - Profiles', () => { assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting3'), 'defaultProfile'); })); + test('initialize with custom all profiles settings', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + + await testObject.initialize(convertToWorkspacePayload(joinPath(ROOT, 'a'))); + + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); + })); + + test('update all profiles settings', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + + const changeEvent = await promise; + assert.deepStrictEqual([...changeEvent.affectedKeys], [APPLY_ALL_PROFILES_SETTING, 'configurationService.profiles.testSetting2']); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); + })); + + test('setting applied to all profiles is registered later', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await fileService.writeFile(instantiationService.get(IUserDataProfilesService).defaultProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting4": "userValue" }')); + await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting4": "profileValue" }')); + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting4'], ConfigurationTarget.USER_LOCAL); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting4'), 'userValue'); + + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.profiles.testSetting4': { + 'type': 'string', + 'default': 'isSet', + } + } + }); + + await testObject.reloadConfiguration(); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting4'), 'userValue'); + })); + + test('update setting that is applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await testObject.updateValue('configurationService.profiles.testSetting2', 'updatedValue', ConfigurationTarget.USER_LOCAL); + + const changeEvent = await promise; + assert.deepStrictEqual([...changeEvent.affectedKeys], ['configurationService.profiles.testSetting2']); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'updatedValue'); + })); + + test('test isSettingAppliedToAllProfiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + assert.strictEqual(testObject.isSettingAppliedForAllProfiles('configurationService.profiles.applicationSetting2'), true); + assert.strictEqual(testObject.isSettingAppliedForAllProfiles('configurationService.profiles.testSetting2'), false); + + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + assert.strictEqual(testObject.isSettingAppliedForAllProfiles('configurationService.profiles.testSetting2'), true); + })); + test('switch to default profile', () => runWithFakedTimers({ useFakeTimers: true }, async () => { await fileService.writeFile(instantiationService.get(IUserDataProfilesService).defaultProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "applicationValue", "configurationService.profiles.testSetting": "userValue" }')); await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "profileValue", "configurationService.profiles.testSetting": "profileValue" }')); @@ -1725,6 +1787,46 @@ suite('WorkspaceConfigurationService - Profiles', () => { assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'isSet'); })); + test('switch to default profile with settings applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + + await userDataProfileService.updateCurrentProfile(instantiationService.get(IUserDataProfilesService).defaultProfile); + + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); + })); + + test('switch to non default profile with settings applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + + const profile = toUserDataProfile('custom2', 'custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2'), joinPath(environmentService.cacheHome, 'profilesCache')); + await fileService.writeFile(profile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting": "profileValue", "configurationService.profiles.testSetting2": "profileValue2" }')); + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await userDataProfileService.updateCurrentProfile(profile); + + const changeEvent = await promise; + assert.deepStrictEqual([...changeEvent.affectedKeys], ['configurationService.profiles.testSetting']); + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'profileValue'); + })); + + test('switch to non default from default profile with settings applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + await userDataProfileService.updateCurrentProfile(instantiationService.get(IUserDataProfilesService).defaultProfile); + + const profile = toUserDataProfile('custom2', 'custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2'), joinPath(environmentService.cacheHome, 'profilesCache')); + await fileService.writeFile(profile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting": "profileValue", "configurationService.profiles.testSetting2": "profileValue2" }')); + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await userDataProfileService.updateCurrentProfile(profile); + + const changeEvent = await promise; + assert.deepStrictEqual([...changeEvent.affectedKeys], ['configurationService.profiles.testSetting']); + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'profileValue'); + })); + }); suite('WorkspaceConfigurationService-Multiroot', () => { From 3881831877aa72d90c3a9524c810e870594ea15c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Jul 2023 01:20:50 +0200 Subject: [PATCH 069/826] Apply extension to all profiles (#187767) Apply extension to all profiles #157492 --- .../browser/extensions.contribution.ts | 18 ++++++++++++++++++ .../extensions/browser/extensionsActions.ts | 6 +++++- .../browser/extensionsWorkbenchService.ts | 10 +++++++++- .../contrib/extensions/common/extensions.ts | 1 + .../common/extensionManagementService.ts | 4 ++-- 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f0f2c01fbbd..a3859aab822 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1442,6 +1442,24 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } }); + this.registerExtensionAction({ + id: 'workbench.extensions.action.toggleApplyToAllProfiles', + title: { value: localize('workbench.extensions.action.toggleApplyToAllProfiles', "Apply this Extension to all Profiles"), original: `Apply this Extension to all Profiles` }, + toggled: ContextKeyExpr.has('isApplicationScopedExtension'), + menu: { + id: MenuId.ExtensionContext, + group: '2_configure', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('isDefaultApplicationScopedExtension').negate()), + order: 4 + }, + run: async (accessor: ServicesAccessor, id: string) => { + const extension = this.extensionsWorkbenchService.local.find(e => areSameExtensions({ id }, e.identifier)); + if (extension) { + return this.extensionsWorkbenchService.toggleApplyExtensionToAllProfiles(extension); + } + } + }); + this.registerExtensionAction({ id: 'workbench.extensions.action.ignoreRecommendation', title: { value: localize('workbench.extensions.action.ignoreRecommendation', "Ignore Recommendation"), original: `Ignore Recommendation` }, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 9f651b6eee2..ee6c57e9959 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -18,7 +18,7 @@ import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOp import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { areSameExtensions, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, getWorkspaceSupportTypeMessage, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, getWorkspaceSupportTypeMessage, TargetPlatform, isApplicationScopedExtension } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IFileService, IFileContent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -1010,6 +1010,8 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n if (extension) { cksOverlay.push(['extension', extension.identifier.id]); cksOverlay.push(['isBuiltinExtension', extension.isBuiltin]); + cksOverlay.push(['isDefaultApplicationScopedExtension', extension.local && isApplicationScopedExtension(extension.local.manifest)]); + cksOverlay.push(['isApplicationScopedExtension', extension.local && extension.local.isApplicationScoped]); cksOverlay.push(['extensionHasConfiguration', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes.configuration]); cksOverlay.push(['extensionHasKeybindings', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes.keybindings]); cksOverlay.push(['extensionHasCommands', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes?.commands]); @@ -1179,6 +1181,8 @@ export class MenuItemExtensionAction extends ExtensionAction { } if (this.action.id === TOGGLE_IGNORE_EXTENSION_ACTION_ID) { this.checked = !this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension); + } else { + this.checked = this.action.checked; } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index db1ded35930..b333b339afe 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -34,7 +34,7 @@ import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IFileService } from 'vs/platform/files/common/files'; -import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, TargetPlatform, ExtensionIdentifier, IExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, TargetPlatform, ExtensionIdentifier, IExtensionIdentifier, IExtensionDescription, isApplicationScopedExtension } from 'vs/platform/extensions/common/extensions'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { IProductService } from 'vs/platform/product/common/productService'; import { FileAccess } from 'vs/base/common/network'; @@ -50,6 +50,7 @@ import { getLocale } from 'vs/platform/languagePacks/common/languagePacks'; import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; interface IExtensionStateProvider { (extension: Extension): T; @@ -774,6 +775,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @ILocaleService private readonly localeService: ILocaleService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IFileService private readonly fileService: IFileService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, ) { super(); const preferPreReleasesValue = configurationService.getValue('_extensions.preferPreReleases'); @@ -1619,6 +1621,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension await this.userDataAutoSyncService.triggerSync(['IgnoredExtensionsUpdated'], false, false); } + async toggleApplyExtensionToAllProfiles(extension: IExtension): Promise { + if (extension.local && !isApplicationScopedExtension(extension.local.manifest)) { + await this.extensionManagementService.updateMetadata(extension.local, { isApplicationScoped: !extension.local.isApplicationScoped }, this.userDataProfilesService.defaultProfile.extensionsResource); + } + } + private isInstalledExtensionSynced(extension: ILocalExtension): boolean { if (extension.isMachineScoped) { return false; diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 4c34dc5ba04..1ce6153fb8b 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -120,6 +120,7 @@ export interface IExtensionsWorkbenchService { // Sync APIs isExtensionIgnoredToSync(extension: IExtension): boolean; toggleExtensionIgnoredToSync(extension: IExtension): Promise; + toggleApplyExtensionToAllProfiles(extension: IExtension): Promise; } export const enum ExtensionEditorTab { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index ab17bcf9f8b..1b378e1b24a 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -153,10 +153,10 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return Promise.reject(`Invalid location ${extension.location.toString()}`); } - updateMetadata(extension: ILocalExtension, metadata: Partial): Promise { + updateMetadata(extension: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise { const server = this.getServer(extension); if (server) { - return server.extensionManagementService.updateMetadata(extension, metadata); + return server.extensionManagementService.updateMetadata(extension, metadata, profileLocation); } return Promise.reject(`Invalid location ${extension.location.toString()}`); } From 3ffd570e52ac3a9f3c783c86cc77fe1d80c58e7c Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 12 Jul 2023 16:41:30 -0700 Subject: [PATCH 070/826] Exclude node_modules from CodeQL (#187773) Exclude node_modules --- CodeQL.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CodeQL.yml b/CodeQL.yml index 9ef07560af3..eecb813a96f 100644 --- a/CodeQL.yml +++ b/CodeQL.yml @@ -21,3 +21,9 @@ path_classifiers: - "out-build" - "out-vscode" - "**/out/**" + + # The default behavior is to tag library code as `library`. Results are hidden + # for library code. You can tag further files as being library code by adding them + # to the `library` section. + library: + - "**/node_modules/**" From 2f1013a310bbd74410e74fad99497841edad0eda Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 12 Jul 2023 17:00:32 -0700 Subject: [PATCH 071/826] Render slash commands as blocks (#187620) --- src/vs/workbench/contrib/chat/browser/chat.ts | 1 + .../contrib/chat/browser/chatWidget.ts | 4 + .../browser/contrib/chatInputEditorContrib.ts | 118 +++++++++++++++--- .../contrib/chat/common/chatService.ts | 1 + .../vscode.proposed.interactive.d.ts | 1 + 5 files changed, 108 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index b42cad92d7e..8c51aaa2175 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -60,6 +60,7 @@ export interface IChatWidget { acceptInput(query?: string): void; focusLastMessage(): void; focusInput(): void; + getSlashCommandsSync(): ISlashCommand[] | undefined; getSlashCommands(): Promise; getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined; getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[]; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index fa29aac0229..9fe7dc95836 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -219,6 +219,10 @@ export class ChatWidget extends Disposable implements IChatWidget { } } + getSlashCommandsSync(): ISlashCommand[] | undefined { + return this.lastSlashCommands; + } + async getSlashCommands(): Promise { if (!this.viewModel) { return; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 1bf461d1ae3..830d85d35b6 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -15,24 +15,34 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { editorForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorForeground, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { ContentWidgetPositionPreference, IContentWidget } from 'vs/editor/browser/editorBrowser'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Selection } from 'vs/editor/common/core/selection'; const decorationDescription = 'chat'; const slashCommandPlaceholderDecorationType = 'chat-session-detail'; const slashCommandTextDecorationType = 'chat-session-text'; +const slashCommandContentWidgetId = 'chat-session-content-widget'; class InputEditorDecorations extends Disposable { + private _slashCommandDomNode = document.createElement('div'); + private _slashCommandContentWidget: IContentWidget | undefined; + private _previouslyUsedSlashCommands = new Set(); + constructor( private readonly widget: IChatWidget, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IThemeService private readonly themeService: IThemeService, + @IChatService private readonly chatService: IChatService, ) { super(); @@ -43,14 +53,25 @@ class InputEditorDecorations extends Disposable { this.updateInputEditorDecorations(); this._register(this.widget.inputEditor.onDidChangeModelContent(() => this.updateInputEditorDecorations())); - this._register(this.widget.onDidChangeViewModel(() => this.updateInputEditorDecorations())); + this._register(this.widget.onDidChangeViewModel(() => { + this._previouslyUsedSlashCommands.clear(); + this.updateInputEditorDecorations(); + })); + this._register(this.chatService.onDidSubmitSlashCommand((e) => { + if (e.sessionId === this.widget.viewModel?.sessionId && !this._previouslyUsedSlashCommands.has(e.slashCommand)) { + this._previouslyUsedSlashCommands.add(e.slashCommand); + } + })); } private updateRegisteredDecorationTypes() { - const theme = this.themeService.getColorTheme(); this.codeEditorService.removeDecorationType(slashCommandTextDecorationType); + this.updateInputEditorContentWidgets({ hide: true }); this.codeEditorService.registerDecorationType(decorationDescription, slashCommandTextDecorationType, { - color: theme.getColor(textLinkForeground)?.toString() + opacity: '0', + after: { + contentText: ' ', + } }); this.updateInputEditorDecorations(); } @@ -62,10 +83,10 @@ class InputEditorDecorations extends Disposable { } private async updateInputEditorDecorations() { - const value = this.widget.inputEditor.getValue(); + const inputValue = this.widget.inputEditor.getValue(); const slashCommands = await this.widget.getSlashCommands(); // TODO this async call can lead to a flicker of the placeholder text when switching editor tabs - if (!value) { + if (!inputValue) { const extensionPlaceholder = this.widget.viewModel?.inputPlaceholder; const defaultPlaceholder = slashCommands?.length ? localize('interactive.input.placeholderWithCommands', "Ask a question or type '/' for topics") : @@ -88,32 +109,44 @@ class InputEditorDecorations extends Disposable { } ]; this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandPlaceholderDecorationType, decoration); + this.updateInputEditorContentWidgets({ hide: true }); return; } - const command = value && slashCommands?.find(c => value.startsWith(`/${c.command} `)); - if (command && command.detail && value === `/${command.command} `) { - const decoration: IDecorationOptions[] = [ - { + let slashCommandPlaceholderDecoration: IDecorationOptions[] | undefined; + const command = inputValue && slashCommands?.find(c => inputValue.startsWith(`/${c.command} `)); + if (command && inputValue === `/${command.command} `) { + const isFollowupSlashCommand = this._previouslyUsedSlashCommands.has(command.command); + const shouldRenderFollowupPlaceholder = command.followupPlaceholder && isFollowupSlashCommand; + if (shouldRenderFollowupPlaceholder || command.detail) { + slashCommandPlaceholderDecoration = [{ range: { startLineNumber: 1, endLineNumber: 1, - startColumn: command.command.length + 2, + startColumn: command && typeof command !== 'string' ? (command?.command.length + 2) : 1, endColumn: 1000 }, renderOptions: { after: { - contentText: command.detail, - color: this.getPlaceholderColor() + contentText: shouldRenderFollowupPlaceholder ? command.followupPlaceholder : command.detail, + color: this.getPlaceholderColor(), + padding: '0 0 0 5px' } } - } - ]; - this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandPlaceholderDecorationType, decoration); - } else { + }]; + this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandPlaceholderDecorationType, slashCommandPlaceholderDecoration); + } + } + if (!slashCommandPlaceholderDecoration) { this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandPlaceholderDecorationType, []); } + if (command && inputValue.startsWith(`/${command.command} `)) { + this.updateInputEditorContentWidgets({ command: command.command }); + } else { + this.updateInputEditorContentWidgets({ hide: true }); + } + if (command && command.detail) { const textDecoration: IDecorationOptions[] = [ { @@ -130,6 +163,40 @@ class InputEditorDecorations extends Disposable { this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandTextDecorationType, []); } } + + private async updateInputEditorContentWidgets(arg: { command: string } | { hide: true }) { + const domNode = this._slashCommandDomNode; + + if (this._slashCommandContentWidget && 'hide' in arg) { + domNode.toggleAttribute('hidden', true); + this.widget.inputEditor.removeContentWidget(this._slashCommandContentWidget); + return; + } else if ('command' in arg) { + const theme = this.themeService.getColorTheme(); + domNode.style.padding = '0 0.4em'; + domNode.style.borderRadius = '3px'; + domNode.style.backgroundColor = theme.getColor(textCodeBlockBackground)?.toString() ?? ''; + domNode.style.color = theme.getColor(textLinkForeground)?.toString() ?? ''; + domNode.innerText = `${arg.command} `; + domNode.toggleAttribute('hidden', false); + + this._slashCommandContentWidget = { + getId() { return slashCommandContentWidgetId; }, + getDomNode() { return domNode; }, + getPosition() { + return { + position: { + lineNumber: 1, + column: 1 + }, + preference: [ContentWidgetPositionPreference.EXACT] + }; + }, + }; + + this.widget.inputEditor.addContentWidget(this._slashCommandContentWidget); + } + } } class InputEditorSlashCommandFollowups extends Disposable { @@ -139,6 +206,7 @@ class InputEditorSlashCommandFollowups extends Disposable { ) { super(); this._register(this.chatService.onDidSubmitSlashCommand(({ slashCommand, sessionId }) => this.repopulateSlashCommand(slashCommand, sessionId))); + this._register(this.widget.inputEditor.onKeyUp((e) => this.handleKeyUp(e))); } private async repopulateSlashCommand(slashCommand: string, sessionId: string) { @@ -159,6 +227,22 @@ class InputEditorSlashCommandFollowups extends Disposable { } } + + private handleKeyUp(e: IKeyboardEvent) { + if (e.keyCode !== KeyCode.Backspace) { + return; + } + + const value = this.widget.inputEditor.getValue().split(' ')[0]; + const currentSelection = this.widget.inputEditor.getSelection(); + if (!value.startsWith('/') || !currentSelection?.isEmpty() || currentSelection?.startLineNumber !== 1 || currentSelection?.startColumn !== value.length + 1) { + return; + } + + if (this.widget.getSlashCommandsSync()?.find((command) => `/${command.command}` === value)) { + this.widget.inputEditor.executeEdits('chat-input-editor-slash-commands', [{ range: new Range(1, 1, 1, currentSelection.startColumn), text: null }], [new Selection(1, 1, 1, 1)]); + } + } } ChatWidget.CONTRIBS.push(InputEditorDecorations, InputEditorSlashCommandFollowups); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 95ebca45502..59f031eb120 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -71,6 +71,7 @@ export interface ISlashCommand { provider?: ISlashCommandProvider; sortText?: string; detail?: string; + followupPlaceholder?: string; } export interface IChatReplyFollowup { diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index e8fefb4ba74..91224906d1b 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -133,6 +133,7 @@ declare module 'vscode' { shouldRepopulate?: boolean; kind: CompletionItemKind; detail?: string; + followupPlaceholder?: string; } export interface InteractiveSessionReplyFollowup { From 5f8f10b5111a71f1a5903b775edf5a20689accb0 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Wed, 12 Jul 2023 19:53:36 -0500 Subject: [PATCH 072/826] Fix #186781 --- .../contrib/terminalContrib/find/browser/terminalFindWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts index 2a3587555c5..2dc2579d56e 100644 --- a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts @@ -84,7 +84,7 @@ export class TerminalFindWidget extends SimpleFindWidget { override hide() { super.hide(); this._findWidgetVisible.reset(); - this._instance.focus(); + this._instance.focus(true); this._instance.xterm?.clearSearchDecorations(); } From 6b3e4d31dbb73cd42000b0c233d3b679023bf14e Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Wed, 12 Jul 2023 20:25:38 -0500 Subject: [PATCH 073/826] Show terminal find input action shorcuts in label --- .../browser/find/simpleFindWidget.ts | 22 ++++++++++--------- .../find/browser/terminalFindWidget.ts | 15 ++++++++++++- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index c402fb22a42..e604a67e2c1 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -18,7 +18,6 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { ContextScopedFindInput } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; import * as strings from 'vs/base/common/strings'; -import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint'; import { status } from 'vs/base/browser/ui/aria/aria'; @@ -34,9 +33,12 @@ interface IFindOptions { showCommonFindToggles?: boolean; checkImeCompletionState?: boolean; showResultCount?: boolean; - appendCaseSensitiveLabel?: string; - appendRegexLabel?: string; - appendWholeWordsLabel?: string; + appendCaseSensitiveActionId?: string; + appendRegexActionId?: string; + appendWholeWordsActionId?: string; + previousMatchActionId?: string; + nextMatchActionId?: string; + closeWidgetActionId?: string; matchesLimit?: number; type?: 'Terminal' | 'Webview'; } @@ -89,9 +91,9 @@ export abstract class SimpleFindWidget extends Widget { } }, showCommonFindToggles: options.showCommonFindToggles, - appendCaseSensitiveLabel: options.appendCaseSensitiveLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindCaseSensitive) : undefined, - appendRegexLabel: options.appendRegexLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindRegex) : undefined, - appendWholeWordsLabel: options.appendWholeWordsLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindWholeWord) : undefined, + appendCaseSensitiveLabel: options.appendCaseSensitiveActionId ? this._getKeybinding(options.appendCaseSensitiveActionId) : undefined, + appendRegexLabel: options.appendRegexActionId ? this._getKeybinding(options.appendRegexActionId) : undefined, + appendWholeWordsLabel: options.appendWholeWordsActionId ? this._getKeybinding(options.appendWholeWordsActionId) : undefined, showHistoryHint: () => showHistoryKeybindingHint(_keybindingService), inputBoxStyles: defaultInputBoxStyles, toggleStyles: defaultToggleStyles @@ -131,7 +133,7 @@ export abstract class SimpleFindWidget extends Widget { })); this.prevBtn = this._register(new SimpleButton({ - label: NLS_PREVIOUS_MATCH_BTN_LABEL, + label: NLS_PREVIOUS_MATCH_BTN_LABEL + (options.previousMatchActionId ? this._getKeybinding(options.previousMatchActionId) : ''), icon: findPreviousMatchIcon, onTrigger: () => { this.find(true); @@ -139,7 +141,7 @@ export abstract class SimpleFindWidget extends Widget { })); this.nextBtn = this._register(new SimpleButton({ - label: NLS_NEXT_MATCH_BTN_LABEL, + label: NLS_NEXT_MATCH_BTN_LABEL + (options.nextMatchActionId ? this._getKeybinding(options.nextMatchActionId) : ''), icon: findNextMatchIcon, onTrigger: () => { this.find(false); @@ -147,7 +149,7 @@ export abstract class SimpleFindWidget extends Widget { })); const closeBtn = this._register(new SimpleButton({ - label: NLS_CLOSE_BTN_LABEL, + label: NLS_CLOSE_BTN_LABEL + (options.closeWidgetActionId ? this._getKeybinding(options.closeWidgetActionId) : ''), icon: widgetClose, onTrigger: () => { this.hide(); diff --git a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts index 2a3587555c5..126447dbef9 100644 --- a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts @@ -13,6 +13,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Event } from 'vs/base/common/event'; import type { ISearchOptions } from 'xterm-addon-search'; +import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; export class TerminalFindWidget extends SimpleFindWidget { private _findInputFocused: IContextKey; @@ -27,7 +28,19 @@ export class TerminalFindWidget extends SimpleFindWidget { @IThemeService private readonly _themeService: IThemeService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { - super({ showCommonFindToggles: true, checkImeCompletionState: true, showResultCount: true, type: 'Terminal', matchesLimit: XtermTerminalConstants.SearchHighlightLimit }, _contextViewService, _contextKeyService, keybindingService); + super({ + showCommonFindToggles: true, + checkImeCompletionState: true, + showResultCount: true, + appendCaseSensitiveActionId: TerminalCommandId.ToggleFindCaseSensitive, + appendRegexActionId: TerminalCommandId.ToggleFindRegex, + appendWholeWordsActionId: TerminalCommandId.ToggleFindWholeWord, + previousMatchActionId: TerminalCommandId.FindPrevious, + nextMatchActionId: TerminalCommandId.FindNext, + closeWidgetActionId: TerminalCommandId.FindHide, + type: 'Terminal', + matchesLimit: XtermTerminalConstants.SearchHighlightLimit + }, _contextViewService, _contextKeyService, keybindingService); this._register(this.state.onFindReplaceStateChange(() => { this.show(); From c8fe3e58a9e40e75efaa0ee86d6f435079866793 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 13 Jul 2023 11:28:25 +0200 Subject: [PATCH 074/826] Fixes #162450 - Support Inner Diff Line Alignment --- .../diffEditorWidget2/diffEditorWidget2.ts | 19 ++--- .../widget/diffEditorWidget2/lineAlignment.ts | 71 +++++++++++++++---- 2 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts index 91f99d55c6b..1dab8ec750e 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts @@ -136,15 +136,16 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { this._register(autorunWithStore2('DiffEditorDecorations', (reader, store) => { store.add(new (readHotReloadableExport(DiffEditorDecorations, reader))(this._editors, this._diffModel, this._options)); })); - - this._register(this._instantiationService.createInstance( - ViewZoneManager, - this._editors, - this._diffModel, - this._options, - this, - () => this.unchangedRangesFeature.isUpdatingViewZones, - )); + this._register(autorunWithStore2('ViewZoneManager', (reader, store) => { + store.add(this._instantiationService.createInstance( + readHotReloadableExport(ViewZoneManager, reader), + this._editors, + this._diffModel, + this._options, + this, + () => this.unchangedRangesFeature.isUpdatingViewZones, + )); + })); this._register(autorunWithStore2('OverviewRulerPart', (reader, store) => { store.add(this._instantiationService.createInstance(readHotReloadableExport(OverviewRulerPart, reader), this._editors, diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts b/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts index 12f0c9bafed..507e6d2039c 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts @@ -7,7 +7,7 @@ import { $ } from 'vs/base/browser/dom'; import { ArrayQueue } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { IObservable, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; import { autorun, autorunWithStore2 } from 'vs/base/common/observableImpl/autorun'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -340,6 +340,17 @@ export class ViewZoneManager extends Disposable { scrollState.restore(this._editors.modified); })); + this._register(toDisposable(() => { + this._editors.original.changeViewZones((a) => { + for (const id of alignmentViewZoneIdsOrig) { a.removeZone(id); } + alignmentViewZoneIdsOrig.clear(); + }); + this._editors.modified.changeViewZones((a) => { + for (const id of alignmentViewZoneIdsMod) { a.removeZone(id); } + alignmentViewZoneIdsMod.clear(); + }); + })); + let ignoreChange = false; this._register(this._editors.original.onDidScrollChange(e => { if (e.scrollLeftChanged && !ignoreChange) { @@ -504,20 +515,52 @@ function computeRangeAlignment( const c = m.lineRangeMapping; handleAlignmentsOutsideOfDiffs(c.originalRange.startLineNumber, c.modifiedRange.startLineNumber); - const originalAdditionalHeight = originalLineHeightOverrides - .takeWhile(v => v.lineNumber < c.originalRange.endLineNumberExclusive) - ?.reduce((p, c) => p + c.heightInPx, 0) ?? 0; - const modifiedAdditionalHeight = modifiedLineHeightOverrides - .takeWhile(v => v.lineNumber < c.modifiedRange.endLineNumberExclusive) - ?.reduce((p, c) => p + c.heightInPx, 0) ?? 0; + let first = true; + let lastModLineNumber = c.modifiedRange.startLineNumber; + let lastOrigLineNumber = c.originalRange.startLineNumber; - result.push({ - originalRange: c.originalRange, - modifiedRange: c.modifiedRange, - originalHeightInPx: c.originalRange.length * origLineHeight + originalAdditionalHeight, - modifiedHeightInPx: c.modifiedRange.length * modLineHeight + modifiedAdditionalHeight, - diff: m.lineRangeMapping, - }); + function emitAlignment(origLineNumberExclusive: number, modLineNumberExclusive: number) { + if (origLineNumberExclusive < lastOrigLineNumber || modLineNumberExclusive < lastModLineNumber) { + return; + } + if (first) { + first = false; + } else if (origLineNumberExclusive === lastOrigLineNumber || modLineNumberExclusive === lastModLineNumber) { + return; + } + const originalRange = new LineRange(lastOrigLineNumber, origLineNumberExclusive); + const modifiedRange = new LineRange(lastModLineNumber, modLineNumberExclusive); + if (originalRange.isEmpty && modifiedRange.isEmpty) { + return; + } + + const originalAdditionalHeight = originalLineHeightOverrides + .takeWhile(v => v.lineNumber < origLineNumberExclusive) + ?.reduce((p, c) => p + c.heightInPx, 0) ?? 0; + const modifiedAdditionalHeight = modifiedLineHeightOverrides + .takeWhile(v => v.lineNumber < modLineNumberExclusive) + ?.reduce((p, c) => p + c.heightInPx, 0) ?? 0; + + result.push({ + originalRange, + modifiedRange, + originalHeightInPx: originalRange.length * origLineHeight + originalAdditionalHeight, + modifiedHeightInPx: modifiedRange.length * modLineHeight + modifiedAdditionalHeight, + }); + + lastOrigLineNumber = origLineNumberExclusive; + lastModLineNumber = modLineNumberExclusive; + } + + for (const i of c.innerChanges || []) { + if (i.originalRange.startColumn > 1 && i.modifiedRange.startColumn > 1) { + // There is some unmodified text on this line + emitAlignment(i.originalRange.startLineNumber, i.modifiedRange.startLineNumber); + } + emitAlignment(i.originalRange.endLineNumber, i.modifiedRange.endLineNumber); + } + + emitAlignment(c.originalRange.endLineNumberExclusive, c.modifiedRange.endLineNumberExclusive); lastOriginalLineNumber = c.originalRange.endLineNumberExclusive; lastModifiedLineNumber = c.modifiedRange.endLineNumberExclusive; From 369585db99845a28851b0bcfde7a5e66aba43397 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 12:12:23 +0200 Subject: [PATCH 075/826] work in progress --- .../browser/inlineChatController.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 05cb6b8e8df..4a5c801b3da 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -217,16 +217,16 @@ export class InlineChatController implements IEditorContribution { private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { console.log('inside of CREATE_SESSION'); - console.log('this._activeSession : ', this._activeSession); + console.log('inside of CREATE_SESSION, this._activeSession : ', this._activeSession); if (this._activeSession) { - console.log('before clearing the session store'); + console.log('inside of CREATE_SESSION, before clearing the session store'); this._sessionStore.clear(); - console.log('before releasing teh session'); + console.log('inside of CREATE_SESSION, before releasing the session'); this._inlineChatSessionService.releaseSession(this._activeSession); - console.log('before calling pause'); + console.log('inside of CREATE_SESSION, before calling pause'); await this[State.PAUSE](); } - console.log('this._activeSession after the cleaning : ', this._activeSession); + console.log('inside of CREATE_SESSION, this._activeSession after the cleaning : ', this._activeSession); assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); @@ -239,7 +239,7 @@ export class InlineChatController implements IEditorContribution { if (!session) { const createSessionCts = new CancellationTokenSource(); const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of the msgListener code of CREATE_SESSION'); + console.log('inside of CREATE_SESSION, inside of the msgListener code of CREATE_SESSION'); this._log('state=_createSession) message received', m); if (m === Message.ACCEPT_INPUT) { // user accepted the input before having a session @@ -292,7 +292,7 @@ export class InlineChatController implements IEditorContribution { private async [State.INIT_UI](options: InlineChatRunOptions): Promise { console.log('inside of init ui'); - console.log('this._activeSession : ', this._activeSession); + console.log('inside of INIT_UI, this._activeSession : ', this._activeSession); assertType(this._activeSession); // hide/cancel inline completions when invoking IE @@ -353,20 +353,20 @@ export class InlineChatController implements IEditorContribution { if (editIsOutsideOfWholeRange) { this._log('text changed outside of whole range, FINISH session'); - console.log('before the third finish existing session'); + console.log('inside of INIT_UI, before the third finish existing session'); this.finishExistingSession(); } })); if (!this._activeSession.lastExchange) { - console.log('before waiting for input'); + console.log('inside of INIT_UI,before waiting for input'); return State.WAIT_FOR_INPUT; } else if (options.isUnstashed) { delete options.isUnstashed; - console.log('before apply response'); + console.log('inside of INIT_UI,before apply response'); return State.APPLY_RESPONSE; } else { - console.log('before show response'); + console.log('inside of INIT_UI,before show response'); return State.SHOW_RESPONSE; } } @@ -386,6 +386,8 @@ export class InlineChatController implements IEditorContribution { private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { + console.log('inside of wait for input'); + assertType(this._activeSession); assertType(this._strategy); @@ -406,7 +408,7 @@ export class InlineChatController implements IEditorContribution { } else { const barrier = new Barrier(); const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of msgListener of WAIT FOR INPUT'); + console.log('inside of WAIT_FOR_INPUT, inside of msgListener'); this._log('state=_waitForInput) message received', m); message = m; barrier.open(); @@ -418,16 +420,14 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.selectAll(); if (message & (Message.CANCEL_INPUT | Message.CANCEL_SESSION)) { - console.log('inside of wait for input'); - console.log('entered into the case when message cancel session'); + console.log('inside of WAIT_FOR_INPUT,entered into the case when message cancel session'); await this[State.CANCEL](); return; // return State.CANCEL; } if (message & Message.ACCEPT_SESSION) { - console.log('inside of wait for input'); - console.log('entered into the case when message accept'); + console.log('inside of WAIT_FOR_INPUT,entered into the case when message accept'); await this[State.ACCEPT](); return; // return State.ACCEPT; @@ -681,7 +681,7 @@ export class InlineChatController implements IEditorContribution { } private async [State.ACCEPT]() { - console.log('inside of accept'); + console.log('inside of State.ACCEPT'); assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); @@ -835,12 +835,15 @@ export class InlineChatController implements IEditorContribution { if (this._activeSession) { if (this._activeSession.editMode === EditMode.Preview) { this._log('finishing existing session, using CANCEL', this._activeSession.editMode); + console.log('before cancelling inside of finish existing session'); this.cancelSession(); } else { this._log('finishing existing session, using APPLY', this._activeSession.editMode); + console.log('before accepting inside of finish existing session'); this.acceptSession(); } } + console.log('at the end of finish existing session'); } unstashLastSession(): Session | undefined { From f73d83fc52b548041ab457bda4e5dab0e3acba83 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 12:30:58 +0200 Subject: [PATCH 076/826] adding changes from review --- src/vs/editor/contrib/colorPicker/browser/colorPicker.css | 6 +++--- .../editor/contrib/colorPicker/browser/colorPickerWidget.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPicker.css b/src/vs/editor/contrib/colorPicker/browser/colorPicker.css index f84c7ba6f2b..f484517caba 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPicker.css +++ b/src/vs/editor/contrib/colorPicker/browser/colorPicker.css @@ -48,19 +48,19 @@ cursor: pointer; color: white; flex: 1; + white-space: nowrap; + overflow: hidden; } .colorpicker-header .picked-color .picked-color-presentation { white-space: nowrap; - margin-left: 30px; + margin-left: 5px; margin-right: 5px; } .colorpicker-header .picked-color .codicon { color: inherit; font-size: 14px; - position: absolute; - left: 8px; } .colorpicker-header .picked-color.light { diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index 6c8cc3ab4e0..f5ed98581b5 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -38,9 +38,9 @@ export class ColorPickerHeader extends Disposable { dom.append(container, this._domNode); this._pickedColorNode = dom.append(this._domNode, $('.picked-color')); - this._pickedColorPresentation = dom.append(this._pickedColorNode, document.createElement('div')); + dom.append(this._pickedColorNode, $('span.codicon.codicon-color-mode')); + this._pickedColorPresentation = dom.append(this._pickedColorNode, document.createElement('span')); this._pickedColorPresentation.classList.add('picked-color-presentation'); - dom.append(this._pickedColorNode, $('.codicon.codicon-color-mode')); const tooltip = localize('clickToToggleColorOptions', "Click to toggle color options (rgb/hsl/hex)"); this._pickedColorNode.setAttribute('title', tooltip); From 01851f692ad8b890346f24187a4903697c14c702 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 12:47:09 +0200 Subject: [PATCH 077/826] placing instead the minimum size determination as the initial size of the content hover when it is instantiated --- .../contrib/hover/browser/contentHover.ts | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 5cc8ab5028e..e18fc352eb9 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -30,7 +30,7 @@ export class ContentHoverController extends Disposable { private readonly _participants: IEditorHoverParticipant[]; - private readonly _widget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor)); + private readonly _widget: ContentHoverWidget; getWidgetContent(): string | undefined { const node = this._widget.getDomNode(); @@ -52,6 +52,11 @@ export class ContentHoverController extends Disposable { ) { super(); + const initialHeight = this._editor.getOption(EditorOption.lineHeight) + 8; + const initialWidth = 4 / 3 * initialHeight; + const initialSize = new dom.Dimension(initialWidth, initialHeight); + this._widget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor, initialSize)); + // Instantiate participants and sort them by `hoverOrdinal` which is relevant for rendering order. this._participants = []; for (const participant of HoverParticipantRegistry.getAll()) { @@ -481,9 +486,10 @@ export class ContentHoverWidget extends ResizableContentWidget { constructor( editor: ICodeEditor, + initialSize: dom.Dimension, @IContextKeyService contextKeyService: IContextKeyService ) { - super(editor); + super(editor, initialSize); this._hoverVisibleKey = EditorContextKeys.hoverVisible.bindTo(contextKeyService); this._hoverFocusedKey = EditorContextKeys.hoverFocused.bindTo(contextKeyService); @@ -504,7 +510,6 @@ export class ContentHoverWidget extends ResizableContentWidget { this._hoverFocusedKey.set(false); })); this._setHoverData(undefined); - this._setMinimumDimensions(); this._layout(); this._editor.addContentWidget(this); } @@ -519,15 +524,6 @@ export class ContentHoverWidget extends ResizableContentWidget { return ContentHoverWidget.ID; } - private _setMinimumDimensions(): void { - const width = 50; - const height = this._editor.getOption(EditorOption.lineHeight) + 8; - const contentsDomNode = this._hover.contentsDomNode; - contentsDomNode.style.minWidth = width + 'px'; - contentsDomNode.style.minHeight = height + 'px'; - this._resizableNode.minSize = new dom.Dimension(width, height); - } - private static _applyDimensions(container: HTMLElement, width: number | string, height: number | string): void { const transformedWidth = typeof width === 'number' ? `${width}px` : width; const transformedHeight = typeof height === 'number' ? `${height}px` : height; From bc4492981c3fc11012a11567f5b59bb7465e0c45 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 12:52:46 +0200 Subject: [PATCH 078/826] changing initial size to minimum size --- src/vs/editor/contrib/hover/browser/contentHover.ts | 12 ++++++------ .../contrib/hover/browser/resizableContentWidget.ts | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index e18fc352eb9..8f33f317138 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -52,10 +52,10 @@ export class ContentHoverController extends Disposable { ) { super(); - const initialHeight = this._editor.getOption(EditorOption.lineHeight) + 8; - const initialWidth = 4 / 3 * initialHeight; - const initialSize = new dom.Dimension(initialWidth, initialHeight); - this._widget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor, initialSize)); + const minimumHeight = this._editor.getOption(EditorOption.lineHeight) + 8; + const minimumWidth = 4 / 3 * minimumHeight; + const minimumSize = new dom.Dimension(minimumWidth, minimumHeight); + this._widget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor, minimumSize)); // Instantiate participants and sort them by `hoverOrdinal` which is relevant for rendering order. this._participants = []; @@ -486,10 +486,10 @@ export class ContentHoverWidget extends ResizableContentWidget { constructor( editor: ICodeEditor, - initialSize: dom.Dimension, + minimumSize: dom.Dimension, @IContextKeyService contextKeyService: IContextKeyService ) { - super(editor, initialSize); + super(editor, minimumSize); this._hoverVisibleKey = EditorContextKeys.hoverVisible.bindTo(contextKeyService); this._hoverFocusedKey = EditorContextKeys.hoverFocused.bindTo(contextKeyService); diff --git a/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts b/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts index 69b72483fb3..0243d9e88bf 100644 --- a/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts +++ b/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts @@ -25,13 +25,13 @@ export abstract class ResizableContentWidget extends Disposable implements ICont constructor( protected readonly _editor: ICodeEditor, - initialSize: dom.IDimension = new dom.Dimension(10, 10) + minimumSize: dom.IDimension = new dom.Dimension(10, 10) ) { super(); this._resizableNode.domNode.style.position = 'absolute'; - this._resizableNode.minSize = new dom.Dimension(10, 10); + this._resizableNode.minSize = dom.Dimension.lift(minimumSize); + this._resizableNode.layout(minimumSize.height, minimumSize.width); this._resizableNode.enableSashes(true, true, true, true); - this._resizableNode.layout(initialSize.height, initialSize.width); this._register(this._resizableNode.onDidResize(e => { this._resize(new dom.Dimension(e.dimension.width, e.dimension.height)); if (e.done) { From 49e56b6e780af9b5cc374f6d16b0cb7879962940 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 13:00:25 +0200 Subject: [PATCH 079/826] removing the view category from toggle inline diff --- src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 4db24fc6c50..aa5a3689321 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -457,7 +457,6 @@ export class ToggleInlineDiff extends AbstractInlineChatAction { mnemonicTitle: localize({ key: 'miToggleDiff', comment: ['&& denotes a mnemonic'] }, "&&Toggle Diff"), original: 'Toggle Diff', }, - category: Categories.View, toggled: { condition: ContextKeyExpr.equals('config.inlineChat.showDiff', true), title: localize('toggleDiff2', "Toggle Diff"), From 415aef325f3aac4ac4c46871661d9652bd6544f4 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 13:04:15 +0200 Subject: [PATCH 080/826] changing the text toggle diff to show diff --- .../contrib/inlineChat/browser/inlineChatActions.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index aa5a3689321..5673709af89 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -453,14 +453,14 @@ export class ToggleInlineDiff extends AbstractInlineChatAction { super({ id: 'inlineChat.toggleDiff', title: { - value: localize('toggleDiff', 'Toggle Diff'), - mnemonicTitle: localize({ key: 'miToggleDiff', comment: ['&& denotes a mnemonic'] }, "&&Toggle Diff"), - original: 'Toggle Diff', + original: 'Show Diff', + value: localize('showDiff', 'Show Diff'), + mnemonicTitle: localize({ key: 'miShowDiff', comment: ['&& denotes a mnemonic'] }, "&&Show Diff"), }, toggled: { condition: ContextKeyExpr.equals('config.inlineChat.showDiff', true), - title: localize('toggleDiff2', "Toggle Diff"), - mnemonicTitle: localize({ key: 'miToggleDiff2', comment: ['&& denotes a mnemonic'] }, "&&Toggle Diff") + title: localize('showDiff2', "Show Diff"), + mnemonicTitle: localize({ key: 'miShowDiff2', comment: ['&& denotes a mnemonic'] }, "&&Show Diff") }, menu: [ { id: MenuId.CommandPalette }, From 1f0f32f1be19ca7b3b7f1f6fb604263f8a2dd54a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 13:09:34 +0200 Subject: [PATCH 081/826] adding text to specify that the showDiff only works when inlineChat.mode is live or livePreview --- src/vs/workbench/contrib/inlineChat/common/inlineChat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index f38b88eccfa..2f9a6665b42 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -201,7 +201,7 @@ Registry.as(Extensions.Configuration).registerConfigurat id: 'editor', properties: { 'inlineChat.showDiff': { - description: localize('showDiff', "Enable/disable showing the diff when edits are generated."), + description: localize('showDiff', "Enable/disable showing the diff when edits are generated. Works only with inlineChat.mode equal to live or livePreview."), default: true, type: 'boolean' } From 0a5437b9442f36ec4b4d120a15741586c91c1398 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 13:11:17 +0200 Subject: [PATCH 082/826] mergging several registered properties into one --- .../contrib/inlineChat/browser/inlineChatActions.ts | 1 - src/vs/workbench/contrib/inlineChat/common/inlineChat.ts | 8 +------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 5673709af89..c9ae443901d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -31,7 +31,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { Position } from 'vs/editor/common/core/position'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Categories } from 'vs/platform/action/common/actionCommonCategories'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 2f9a6665b42..61cf7d493a1 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -193,13 +193,7 @@ Registry.as(Extensions.Configuration).registerConfigurat localize('mode.preview', "Changes are previewed only and need to be accepted via the apply button. Ending a session will discard the changes."), localize('mode.live', "Changes are applied directly to the document but can be highlighted via inline diffs. Ending a session will keep the changes."), ] - } - } -}); - -Registry.as(Extensions.Configuration).registerConfiguration({ - id: 'editor', - properties: { + }, 'inlineChat.showDiff': { description: localize('showDiff', "Enable/disable showing the diff when edits are generated. Works only with inlineChat.mode equal to live or livePreview."), default: true, From 445ed8ecc3b11a8b48eeb518cff47edde8741fc7 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 13:12:31 +0200 Subject: [PATCH 083/826] addinf an underscore in front of the onContextMenu call --- .../workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index d5679287a37..d1711bcb911 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -332,11 +332,11 @@ export class InlineChatWidget { this._store.add(markdownMessageToolbar); this._store.add(addDisposableListener(this._elements.root, EventType.CONTEXT_MENU, async (event: MouseEvent) => { - this.onContextMenu(event); + this._onContextMenu(event); })); } - private onContextMenu(event: MouseEvent) { + private _onContextMenu(event: MouseEvent) { this._contextMenuService.showContextMenu({ menuId: MENU_INLINE_CHAT_WIDGET_TOGGLE, getAnchor: () => event, From fe0645573ddf54267a5fdd66ddf20fa633f7e5c1 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 13 Jul 2023 14:08:06 +0200 Subject: [PATCH 084/826] Comments widget input box indistinguishable from rest of comments widget when it has content (#187819) Fixes #187745 --- src/vs/workbench/contrib/comments/browser/media/review.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 13be5cf92fb..dc27b9a50ae 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -349,6 +349,12 @@ outline-width: 1px; } +.review-widget .body .comment-form .monaco-editor, +.review-widget .body .comment-form .monaco-editor .monaco-editor-background, +.review-widget .body .edit-container .monaco-editor .monaco-editor-background { + background-color: var(--vscode-peekViewTitle-background); +} + .review-widget .body .comment-form .monaco-editor, .review-widget .body .edit-container .monaco-editor { width: 100%; From 7895f9bc23f85ffe6db5e3d27cae984f9fffab1c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Jul 2023 14:08:47 +0200 Subject: [PATCH 085/826] revert the feature (#187816) #176813 revert the feature --- .../browser/preferencesRenderers.ts | 10 +- .../preferences/browser/settingsTree.ts | 49 +-------- .../configuration/browser/configuration.ts | 10 +- .../browser/configurationService.ts | 56 +---------- .../configuration/common/configuration.ts | 2 - .../test/browser/configurationService.test.ts | 99 +------------------ 6 files changed, 8 insertions(+), 218 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index c80d1d641c8..3983b46e191 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -44,7 +44,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { isEqual } from 'vs/base/common/resources'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IStringDictionary } from 'vs/base/common/collections'; -import { APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; export interface IPreferencesRenderer extends IDisposable { render(): void; @@ -616,14 +616,6 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc if (configuration.scope === ConfigurationScope.APPLICATION) { // If we're in a profile setting file, and the setting is application-scoped, fade it out. markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); - } else if (this.configurationService.isSettingAppliedForAllProfiles(setting.key)) { - // If we're in the non-default profile setting file, and the setting can be applied in all profiles, fade it out. - markerData.push({ - severity: MarkerSeverity.Hint, - tags: [MarkerTag.Unnecessary], - ...setting.range, - message: nls.localize('allProfileSettingWhileInNonDefaultProfileSetting', "This setting cannot be applied because it is configured to be applied in all profiles using setting {0}. Value from the default profile will be used instead.", APPLY_ALL_PROFILES_SETTING) - }); } } } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index ff00480a0ee..9124e87d359 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -23,7 +23,6 @@ import { IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; import { ITreeFilter, ITreeModel, ITreeNode, ITreeRenderer, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { Action, IAction, Separator } from 'vs/base/common/actions'; -import { distinct } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -64,7 +63,7 @@ import { ISettingsEditorViewState, SettingsTreeElement, SettingsTreeGroupChild, import { ExcludeSettingWidget, IListDataItem, IObjectDataItem, IObjectEnumOption, IObjectKeySuggester, IObjectValueSuggester, ISettingListChangeEvent, IncludeSettingWidget, ListSettingWidget, ObjectSettingCheckboxWidget, ObjectSettingDropdownWidget, ObjectValue } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { LANGUAGE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; -import { APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; @@ -902,11 +901,6 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre } template.indicatorsLabel.updateScopeOverrides(element, this._onDidClickOverrideElement, this._onApplyFilter); - template.elementDisposables.add(this._configService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(APPLY_ALL_PROFILES_SETTING)) { - template.indicatorsLabel.updateScopeOverrides(element, this._onDidClickOverrideElement, this._onApplyFilter); - } - })); const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, @@ -1965,7 +1959,6 @@ export class SettingTreeRenderers { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IContextViewService private readonly _contextViewService: IContextViewService, - @IUserDataProfilesService private readonly _userDataProfilesService: IUserDataProfilesService, @IUserDataSyncEnablementService private readonly _userDataSyncEnablementService: IUserDataSyncEnablementService, ) { this.settingActions = [ @@ -2024,9 +2017,6 @@ export class SettingTreeRenderers { private getActionsForSetting(setting: ISetting, settingTarget: SettingsTarget): IAction[] { const actions: IAction[] = []; - if (this._userDataProfilesService.isEnabled() && setting.scope !== ConfigurationScope.APPLICATION && settingTarget === ConfigurationTarget.USER_LOCAL) { - actions.push(this._instantiationService.createInstance(ApplySettingToAllProfilesAction, setting)); - } if (this._userDataSyncEnablementService.isEnabled() && !setting.disallowSyncIgnore) { actions.push(this._instantiationService.createInstance(SyncSettingAction, setting)); } @@ -2496,40 +2486,3 @@ class SyncSettingAction extends Action { } } - -class ApplySettingToAllProfilesAction extends Action { - static readonly ID = 'settings.applyToAllProfiles'; - static readonly LABEL = localize('applyToAllProfiles', "Apply Setting to all Profiles"); - - constructor( - private readonly setting: ISetting, - @IWorkbenchConfigurationService private readonly configService: IWorkbenchConfigurationService, - ) { - super(ApplySettingToAllProfilesAction.ID, ApplySettingToAllProfilesAction.LABEL); - this._register(Event.filter(configService.onDidChangeConfiguration, e => e.affectsConfiguration(APPLY_ALL_PROFILES_SETTING))(() => this.update())); - this.update(); - } - - update() { - const allProfilesSettings = this.configService.getValue(APPLY_ALL_PROFILES_SETTING); - this.checked = allProfilesSettings.includes(this.setting.key); - } - - override async run(): Promise { - // first remove the current setting completely from ignored settings - const value = this.configService.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; - - if (this.checked) { - value.splice(value.indexOf(this.setting.key), 1); - } else { - value.push(this.setting.key); - } - - const newValue = distinct(value); - await this.configService.updateValue(APPLY_ALL_PROFILES_SETTING, newValue.length ? newValue : undefined, ConfigurationTarget.USER_LOCAL); - if (!this.checked) { - await this.configService.updateValue(this.setting.key, this.configService.inspect(this.setting.key).userLocal?.value, ConfigurationTarget.USER_LOCAL); - } - } - -} diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 8f942efdd0e..70f1fcd1e93 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -11,7 +11,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult, FileOperation, FileOperationEvent } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; -import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES, APPLY_ALL_PROFILES_SETTING } from 'vs/workbench/services/configuration/common/configuration'; +import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { WorkbenchState, IWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { ConfigurationScope, Extensions, IConfigurationRegistry, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; @@ -140,14 +140,6 @@ export class ApplicationConfiguration extends UserSettings { return this.loadConfiguration(); } - override async loadConfiguration(): Promise { - const model = await super.loadConfiguration(); - const value = model.getValue(APPLY_ALL_PROFILES_SETTING); - const allProfilesSettings = Array.isArray(value) ? value : []; - return this.parseOptions.include || allProfilesSettings.length - ? this.reparse({ ...this.parseOptions, include: allProfilesSettings }) - : model; - } } export class UserConfiguration extends Disposable { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 9a920e59af0..b02fbc29e61 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -15,7 +15,7 @@ import { ConfigurationModel, ConfigurationChangeEvent, mergeChanges } from 'vs/p import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService, IConfigurationUpdateOptions } from 'vs/platform/configuration/common/configuration'; import { IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId, APPLY_ALL_PROFILES_SETTING } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId } from 'vs/platform/configuration/common/configurationRegistry'; import { IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, getStoredWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; @@ -44,7 +44,6 @@ import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/pol import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined { return userDataProfile.isDefault @@ -490,11 +489,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } isSettingAppliedForAllProfiles(key: string): boolean { - if (this.configurationRegistry.getConfigurationProperties()[key]?.scope === ConfigurationScope.APPLICATION) { - return true; - } - const allProfilesSettings = this.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; - return Array.isArray(allProfilesSettings) && allProfilesSettings.includes(key); + return this.configurationRegistry.getConfigurationProperties()[key]?.scope === ConfigurationScope.APPLICATION; } private async createWorkspace(arg: IAnyWorkspaceIdentifier): Promise { @@ -606,10 +601,6 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat const initUserConfiguration = async () => { mark('code/willInitUserConfiguration'); const result = await Promise.all([this.localUserConfiguration.initialize(), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())]); - if (this.applicationConfiguration) { - const applicationConfigurationModel = await initApplicationConfigurationPromise; - result[0] = this.localUserConfiguration.reparse({ exclude: applicationConfigurationModel.getValue(APPLY_ALL_PROFILES_SETTING) }); - } mark('code/didInitUserConfiguration'); return result; }; @@ -723,12 +714,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat promises.push(this.reloadApplicationConfiguration(true)); } } - let [localUser, application] = await Promise.all(promises); - application = application ?? this._configuration.applicationConfiguration; - if (this.applicationConfiguration) { - localUser = this.localUserConfiguration.reparse({ exclude: application.getValue(APPLY_ALL_PROFILES_SETTING) }); - } - await this.loadConfiguration(application, localUser, this._configuration.remoteUserConfiguration, true); + const [localUser, application] = await Promise.all(promises); + await this.loadConfiguration(application ?? this._configuration.applicationConfiguration, localUser, this._configuration.remoteUserConfiguration, true); })()); } @@ -771,35 +758,15 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private onApplicationConfigurationChanged(applicationConfiguration: ConfigurationModel): void { const previous = { data: this._configuration.toData(), workspace: this.workspace }; - const previousAllProfilesSettings = this._configuration.applicationConfiguration.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; const change = this._configuration.compareAndUpdateApplicationConfiguration(applicationConfiguration); - const currentAllProfilesSettings = this.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; const configurationProperties = this.configurationRegistry.getConfigurationProperties(); const changedKeys: string[] = []; for (const changedKey of change.keys) { if (configurationProperties[changedKey]?.scope === ConfigurationScope.APPLICATION) { changedKeys.push(changedKey); - if (changedKey === APPLY_ALL_PROFILES_SETTING) { - for (const previousAllProfileSetting of previousAllProfilesSettings) { - if (!currentAllProfilesSettings.includes(previousAllProfileSetting)) { - changedKeys.push(previousAllProfileSetting); - } - } - for (const currentAllProfileSetting of currentAllProfilesSettings) { - if (!previousAllProfilesSettings.includes(currentAllProfileSetting)) { - changedKeys.push(currentAllProfileSetting); - } - } - } - } - else if (currentAllProfilesSettings.includes(changedKey)) { - changedKeys.push(changedKey); } } change.keys = changedKeys; - if (change.keys.includes(APPLY_ALL_PROFILES_SETTING)) { - this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reparse({ exclude: currentAllProfilesSettings })); - } this.triggerConfigurationChange(change, previous, ConfigurationTarget.USER); } @@ -1351,18 +1318,3 @@ const workbenchContributionsRegistry = Registry.as(Extensions.Configuration); -configurationRegistry.registerConfiguration({ - ...workbenchConfigurationNodeBase, - properties: { - [APPLY_ALL_PROFILES_SETTING]: { - 'type': 'array', - description: localize('setting description', "Configure settings to be applied for all profiles."), - 'default': [], - 'scope': ConfigurationScope.APPLICATION, - additionalProperties: true, - uniqueItems: true, - } - } -}); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 043add6bd72..227c827eb2a 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -93,5 +93,3 @@ export interface IWorkbenchConfigurationService extends IConfigurationService { } export const TASKS_DEFAULT = '{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": []\n}'; - -export const APPLY_ALL_PROFILES_SETTING = 'workbench.settings.applyToAllProfiles'; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index e0613cac0c7..b336cb2fec3 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -28,7 +28,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { APPLY_ALL_PROFILES_SETTING, IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; +import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; import { SignService } from 'vs/platform/sign/browser/signService'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; @@ -1529,11 +1529,6 @@ suite('WorkspaceConfigurationService - Profiles', () => { 'id': '_test', 'type': 'object', 'properties': { - [APPLY_ALL_PROFILES_SETTING]: { - 'type': 'array', - 'default': [], - 'scope': ConfigurationScope.APPLICATION, - }, 'configurationService.profiles.applicationSetting': { 'type': 'string', 'default': 'isSet', @@ -1687,61 +1682,9 @@ suite('WorkspaceConfigurationService - Profiles', () => { assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting3'), 'defaultProfile'); })); - test('initialize with custom all profiles settings', () => runWithFakedTimers({ useFakeTimers: true }, async () => { - await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); - - await testObject.initialize(convertToWorkspacePayload(joinPath(ROOT, 'a'))); - - assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); - assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); - })); - - test('update all profiles settings', () => runWithFakedTimers({ useFakeTimers: true }, async () => { - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); - - const changeEvent = await promise; - assert.deepStrictEqual([...changeEvent.affectedKeys], [APPLY_ALL_PROFILES_SETTING, 'configurationService.profiles.testSetting2']); - assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); - })); - - test('setting applied to all profiles is registered later', () => runWithFakedTimers({ useFakeTimers: true }, async () => { - await fileService.writeFile(instantiationService.get(IUserDataProfilesService).defaultProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting4": "userValue" }')); - await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting4": "profileValue" }')); - await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting4'], ConfigurationTarget.USER_LOCAL); - assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting4'), 'userValue'); - - configurationRegistry.registerConfiguration({ - 'id': '_test', - 'type': 'object', - 'properties': { - 'configurationService.profiles.testSetting4': { - 'type': 'string', - 'default': 'isSet', - } - } - }); - - await testObject.reloadConfiguration(); - assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting4'), 'userValue'); - })); - - test('update setting that is applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { - await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await testObject.updateValue('configurationService.profiles.testSetting2', 'updatedValue', ConfigurationTarget.USER_LOCAL); - - const changeEvent = await promise; - assert.deepStrictEqual([...changeEvent.affectedKeys], ['configurationService.profiles.testSetting2']); - assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'updatedValue'); - })); - test('test isSettingAppliedToAllProfiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { assert.strictEqual(testObject.isSettingAppliedForAllProfiles('configurationService.profiles.applicationSetting2'), true); assert.strictEqual(testObject.isSettingAppliedForAllProfiles('configurationService.profiles.testSetting2'), false); - - await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); - assert.strictEqual(testObject.isSettingAppliedForAllProfiles('configurationService.profiles.testSetting2'), true); })); test('switch to default profile', () => runWithFakedTimers({ useFakeTimers: true }, async () => { @@ -1787,46 +1730,6 @@ suite('WorkspaceConfigurationService - Profiles', () => { assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'isSet'); })); - test('switch to default profile with settings applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { - await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); - - await userDataProfileService.updateCurrentProfile(instantiationService.get(IUserDataProfilesService).defaultProfile); - - assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); - assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); - })); - - test('switch to non default profile with settings applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { - await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); - - const profile = toUserDataProfile('custom2', 'custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2'), joinPath(environmentService.cacheHome, 'profilesCache')); - await fileService.writeFile(profile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting": "profileValue", "configurationService.profiles.testSetting2": "profileValue2" }')); - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await userDataProfileService.updateCurrentProfile(profile); - - const changeEvent = await promise; - assert.deepStrictEqual([...changeEvent.affectedKeys], ['configurationService.profiles.testSetting']); - assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); - assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); - assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'profileValue'); - })); - - test('switch to non default from default profile with settings applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { - await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); - await userDataProfileService.updateCurrentProfile(instantiationService.get(IUserDataProfilesService).defaultProfile); - - const profile = toUserDataProfile('custom2', 'custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2'), joinPath(environmentService.cacheHome, 'profilesCache')); - await fileService.writeFile(profile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting": "profileValue", "configurationService.profiles.testSetting2": "profileValue2" }')); - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await userDataProfileService.updateCurrentProfile(profile); - - const changeEvent = await promise; - assert.deepStrictEqual([...changeEvent.affectedKeys], ['configurationService.profiles.testSetting']); - assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); - assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); - assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'profileValue'); - })); - }); suite('WorkspaceConfigurationService-Multiroot', () => { From 429d6d203af96da2edb38682ba8b9fd0ed173be2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Jul 2023 14:09:05 +0200 Subject: [PATCH 086/826] remove the extension action to apply in all profiles #157492 (#187815) --- .../browser/extensions.contribution.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index a3859aab822..f0f2c01fbbd 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1442,24 +1442,6 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } }); - this.registerExtensionAction({ - id: 'workbench.extensions.action.toggleApplyToAllProfiles', - title: { value: localize('workbench.extensions.action.toggleApplyToAllProfiles', "Apply this Extension to all Profiles"), original: `Apply this Extension to all Profiles` }, - toggled: ContextKeyExpr.has('isApplicationScopedExtension'), - menu: { - id: MenuId.ExtensionContext, - group: '2_configure', - when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('isDefaultApplicationScopedExtension').negate()), - order: 4 - }, - run: async (accessor: ServicesAccessor, id: string) => { - const extension = this.extensionsWorkbenchService.local.find(e => areSameExtensions({ id }, e.identifier)); - if (extension) { - return this.extensionsWorkbenchService.toggleApplyExtensionToAllProfiles(extension); - } - } - }); - this.registerExtensionAction({ id: 'workbench.extensions.action.ignoreRecommendation', title: { value: localize('workbench.extensions.action.ignoreRecommendation', "Ignore Recommendation"), original: `Ignore Recommendation` }, From ca6ebd67d1edfa2d1f4ebe53211160106028496a Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 13 Jul 2023 14:09:30 +0200 Subject: [PATCH 087/826] Update shellscript grammar (#187811) --- extensions/shellscript/cgmanifest.json | 4 ++-- .../syntaxes/shell-unix-bash.tmLanguage.json | 24 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/extensions/shellscript/cgmanifest.json b/extensions/shellscript/cgmanifest.json index 87be4976392..f246d45fe21 100644 --- a/extensions/shellscript/cgmanifest.json +++ b/extensions/shellscript/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jeff-hykin/better-shell-syntax", "repositoryUrl": "https://github.com/jeff-hykin/better-shell-syntax", - "commitHash": "1bad17d8badf6283125aaa7c31be06ba64146a0f" + "commitHash": "ce62ea59e8e522f8a07d8d8a2d1f992c6c600b91" } }, "license": "MIT", - "version": "1.5.4" + "version": "1.6.2" } ], "version": 1 diff --git a/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json b/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json index e132b9e5699..68055eb7b29 100644 --- a/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json +++ b/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jeff-hykin/better-shell-syntax/commit/1bad17d8badf6283125aaa7c31be06ba64146a0f", + "version": "https://github.com/jeff-hykin/better-shell-syntax/commit/ce62ea59e8e522f8a07d8d8a2d1f992c6c600b91", "name": "Shell Script", "scopeName": "source.shell", "patterns": [ @@ -14,7 +14,7 @@ ], "repository": { "alias_statement": { - "begin": "(alias)[ \\t]*+[ \\t]*+(?:((?<=^|;|&|[ \\t])(?:export|declare|typeset|local|readonly)(?=[ \\t]|;|&|$))[ \\t]*+)?((?|#|\\n|$|;|[ \\t]))(?!foreach\\b(?!\\/)|select\\b(?!\\/)|repeat\\b(?!\\/)|until\\b(?!\\/)|while\\b(?!\\/)|case\\b(?!\\/)|done\\b(?!\\/)|elif\\b(?!\\/)|else\\b(?!\\/)|esac\\b(?!\\/)|then\\b(?!\\/)|for\\b(?!\\/)|end\\b(?!\\/)|in\\b(?!\\/)|fi\\b(?!\\/)|do\\b(?!\\/)|if\\b(?!\\/))(?:((?<=^|;|&|[ \\t])(?:export|declare|typeset|local|readonly)(?=[ \\t]|;|&|$))|((?!\"|'|\\\\\\n?$)[^!'\" \\t\\n\\r]+?))(?:(?= |\\t)|(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(?|#|\\n|$|;|[ \\t]))(?!foreach\\b(?!\\/)|select\\b(?!\\/)|repeat\\b(?!\\/)|until\\b(?!\\/)|while\\b(?!\\/)|case\\b(?!\\/)|done\\b(?!\\/)|elif\\b(?!\\/)|else\\b(?!\\/)|esac\\b(?!\\/)|then\\b(?!\\/)|for\\b(?!\\/)|end\\b(?!\\/)|in\\b(?!\\/)|fi\\b(?!\\/)|do\\b(?!\\/)|if\\b(?!\\/))(?:((?<=^|;|&|[ \\t])(?:readonly|declare|typeset|export|local)(?=[ \\t]|;|&|$))|((?!\"|'|\\\\\\n?$)[^!'\" \\t\\n\\r]+?))(?:(?= |\\t)|(?=;|\\||&|\\n|\\)|\\`|\\{|\\}|[ \\t]*#|\\])(? Date: Thu, 13 Jul 2023 14:13:37 +0200 Subject: [PATCH 088/826] does not appear to work with when clauses inside of menu --- .../workbench/contrib/inlineChat/browser/inlineChatActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index c9ae443901d..03e8cf4238f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -462,8 +462,8 @@ export class ToggleInlineDiff extends AbstractInlineChatAction { mnemonicTitle: localize({ key: 'miShowDiff2', comment: ['&& denotes a mnemonic'] }, "&&Show Diff") }, menu: [ - { id: MenuId.CommandPalette }, - { id: MENU_INLINE_CHAT_WIDGET_TOGGLE } + { id: MenuId.CommandPalette, when: CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview) }, + { id: MENU_INLINE_CHAT_WIDGET_TOGGLE, when: CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview) } ] }); } From a07bb86a618f10d43eb3b82d155b3408f37c4080 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 14:18:17 +0200 Subject: [PATCH 089/826] adding all the comments --- .../browser/inlineChatController.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 4a5c801b3da..58067e15c59 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -482,7 +482,7 @@ export class InlineChatController implements IEditorContribution { let message = Message.NONE; const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of msgListener of MAKE REQUEST'); + console.log('inside of MAKE_REQUEST, inside of msgListener of MAKE REQUEST'); this._log('state=_makeRequest) message received', m); message = m; requestCts.cancel(); @@ -537,14 +537,12 @@ export class InlineChatController implements IEditorContribution { this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); if (message & Message.CANCEL_SESSION) { - console.log('inside of make request'); - console.log('cancelling the session'); + console.log('inside of MAKE_REQUEST, cancelling the session'); return State.CANCEL; } else if (message & Message.PAUSE_SESSION) { return State.PAUSE; } else if (message & Message.ACCEPT_SESSION) { - console.log('inside of make request'); - console.log('accepting'); + console.log('inside of MAKE_REQUEST, accepting'); return State.ACCEPT; } else { return State.APPLY_RESPONSE; @@ -685,25 +683,25 @@ export class InlineChatController implements IEditorContribution { assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); - console.log('after assert type'); + console.log('inside of State.ACCEPT, after assert type'); try { - console.log('before strategy apply'); + console.log('inside of State.ACCEPT, before strategy apply'); await this._strategy.apply(); - console.log('after strategy apply'); + console.log('inside of State.ACCEPT, after strategy apply'); // TODO: ASK WHY DESPITE AWAIT AFTER STRATEFY NOT PRINTED BEFORE CREATE SESSION } catch (err) { - console.log('when error obtained'); + console.log('inside of State.ACCEPT, when error obtained'); this._dialogService.error(localize('err.apply', "Failed to apply changes.", toErrorMessage(err))); this._log('FAILED to apply changes'); this._log(err); } - console.log('before release session'); + console.log('inside of State.ACCEPT, before release session'); await this._inlineChatSessionService.releaseSession(this._activeSession); - console.log('before state pause'); + console.log('inside of State.ACCEPT, before state pause'); await this[State.PAUSE](); } From d80f78877bdd9f4228f2153d0502f3e964b736d4 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 14:30:47 +0200 Subject: [PATCH 090/826] adding some more comments --- .../contrib/inlineChat/browser/inlineChatController.ts | 7 ++++++- .../contrib/inlineChat/browser/inlineChatSession.ts | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 58067e15c59..6059b0c35a7 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -144,6 +144,7 @@ export class InlineChatController implements IEditorContribution { dispose(): void { console.log('inside of dispose'); this._stashedSession.clear(); + console.log('inside of dispose before calling finishExistingSession'); this.finishExistingSession(); this._store.dispose(); this._log('controller disposed'); @@ -177,6 +178,7 @@ export class InlineChatController implements IEditorContribution { async run(options: InlineChatRunOptions | undefined = {}): Promise { this._log('session starting inside of run'); + console.log('before finishExistingSession inside of run'); await this.finishExistingSession(); this._stashedSession.clear(); console.log('before calling create session inside of run'); @@ -353,7 +355,7 @@ export class InlineChatController implements IEditorContribution { if (editIsOutsideOfWholeRange) { this._log('text changed outside of whole range, FINISH session'); - console.log('inside of INIT_UI, before the third finish existing session'); + console.log('inside of INIT_UI, before calling finish existing session'); this.finishExistingSession(); } })); @@ -729,6 +731,7 @@ export class InlineChatController implements IEditorContribution { // only stash sessions that had edits this._stashedSession.value = this._instaService.createInstance(StashedSession, this._editor, mySession); } else { + console.log('before releasing the session of cancel'); this._inlineChatSessionService.releaseSession(mySession); } } @@ -870,6 +873,7 @@ class StashedSession { this._ctxHasStashedSession.set(true); this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { this._session = undefined; + console.log('before release session of constructor'); this._sessionService.releaseSession(session); this._ctxHasStashedSession.reset(); }); @@ -879,6 +883,7 @@ class StashedSession { this._listener.dispose(); this._ctxHasStashedSession.reset(); if (this._session) { + console.log('before release session of dispose'); this._sessionService.releaseSession(this._session); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 83aa7b064ff..79ac5e76633 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -477,6 +477,7 @@ export class InlineChatSessionService implements IInlineChatSessionService { } releaseSession(session: Session): void { + console.log('at the beginning of the release session'); const { editor } = session; @@ -498,6 +499,7 @@ export class InlineChatSessionService implements IInlineChatSessionService { // send telemetry this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); + console.log('at the end of the release session'); } getSession(editor: ICodeEditor, uri: URI): Session | undefined { From 3c632446d5052f7e4f7c8b9b464efaf2a2d7a02e Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 14:47:21 +0200 Subject: [PATCH 091/826] go over this and try to understand why it does not work --- .../browser/inlineChatController.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 6059b0c35a7..f15eff338cd 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -180,6 +180,7 @@ export class InlineChatController implements IEditorContribution { this._log('session starting inside of run'); console.log('before finishExistingSession inside of run'); await this.finishExistingSession(); + console.log('after finish existing session inside of run'); this._stashedSession.clear(); console.log('before calling create session inside of run'); await this._nextState(State.CREATE_SESSION, options); @@ -220,14 +221,15 @@ export class InlineChatController implements IEditorContribution { private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { console.log('inside of CREATE_SESSION'); console.log('inside of CREATE_SESSION, this._activeSession : ', this._activeSession); - if (this._activeSession) { - console.log('inside of CREATE_SESSION, before clearing the session store'); - this._sessionStore.clear(); - console.log('inside of CREATE_SESSION, before releasing the session'); - this._inlineChatSessionService.releaseSession(this._activeSession); - console.log('inside of CREATE_SESSION, before calling pause'); - await this[State.PAUSE](); - } + // if (this._activeSession) { + // console.log('inside of CREATE_SESSION, before clearing the session store'); + // this._sessionStore.clear(); + // console.log('inside of CREATE_SESSION, before releasing the session'); + // this._inlineChatSessionService.releaseSession(this._activeSession); + // console.log('inside of CREATE_SESSION, before calling pause'); + // // Doesn't appear to be properly awaited? + // await this[State.PAUSE](); + // } console.log('inside of CREATE_SESSION, this._activeSession after the cleaning : ', this._activeSession); assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); @@ -691,7 +693,7 @@ export class InlineChatController implements IEditorContribution { console.log('inside of State.ACCEPT, before strategy apply'); await this._strategy.apply(); console.log('inside of State.ACCEPT, after strategy apply'); - // TODO: ASK WHY DESPITE AWAIT AFTER STRATEFY NOT PRINTED BEFORE CREATE SESSION + // TODO: ASK WHY DESPITE AWAIT, AFTER STRATEFY NOT PRINTED BEFORE CREATE SESSION } catch (err) { console.log('inside of State.ACCEPT, when error obtained'); this._dialogService.error(localize('err.apply', "Failed to apply changes.", toErrorMessage(err))); @@ -812,6 +814,7 @@ export class InlineChatController implements IEditorContribution { acceptSession(): void { console.log('inside of acceptSession method'); + // Will fire a message which will be picked up by the controller and some other code will be run this._messages.fire(Message.ACCEPT_SESSION); } From 930a745d60ebf5367e4e34a0e126ff609a7e6b1a Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 13 Jul 2023 06:18:12 -0700 Subject: [PATCH 092/826] Improve slash command rendering in inline chat (#187769) * Render inline chat slash commands as blocks * Allow backspace to delete slash command block * Remove unnecessary template string literal --- .../contrib/inlineChat/browser/inlineChat.css | 9 ++- .../inlineChat/browser/inlineChatWidget.ts | 72 ++++++++++++++++++- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index 23073e494ba..5beab56e7f2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -278,13 +278,20 @@ } .monaco-editor .inline-chat-slash-command { - color: var(--vscode-textLink-foreground) + opacity: 0; } .monaco-editor .inline-chat-slash-command-detail { opacity: 0.5; } +.monaco-editor .inline-chat-slash-command-content-widget { + padding: 0 0.4em; + border-radius: 3px; + background-color: var(--vscode-textCodeBlock-background); + color: var(--vscode-textLink-foreground); +} + /* diff zone */ .monaco-editor .inline-chat-diff-widget { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 57077181753..dc9490e8c4f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./inlineChat'; -import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { localize } from 'vs/nls'; @@ -49,6 +49,7 @@ import { ExpansionState } from 'vs/workbench/contrib/inlineChat/browser/inlineCh import { IdleValue } from 'vs/base/common/async'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IMenuWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; +import { KeyCode } from 'vs/base/common/keyCodes'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -180,6 +181,8 @@ export class InlineChatWidget { private _preferredExpansionState: ExpansionState | undefined; private _expansionState: ExpansionState = ExpansionState.NOT_CROPPED; + private _slashCommandContentWidget: SlashCommandContentWidget; + constructor( private readonly parentEditor: ICodeEditor, @IModelService private readonly _modelService: IModelService, @@ -278,6 +281,11 @@ export class InlineChatWidget { this._store.add(this._inputModel.onDidChangeContent(togglePlaceholder)); togglePlaceholder(); + // slash command content widget + + this._slashCommandContentWidget = new SlashCommandContentWidget(this._inputEditor); + this._store.add(this._slashCommandContentWidget); + // toolbars const toolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, MENU_INLINE_CHAT_WIDGET, { @@ -654,6 +662,8 @@ export class InlineChatWidget { const decorations = this._inputEditor.createDecorationsCollection(); const updateSlashDecorations = () => { + this._slashCommandContentWidget.hide(); + const newDecorations: IModelDeltaDecoration[] = []; for (const command of commands) { const withSlash = `/${command.command}`; @@ -664,9 +674,16 @@ export class InlineChatWidget { options: { description: 'inline-chat-slash-command', inlineClassName: 'inline-chat-slash-command', + after: { + // Force some space between slash command and placeholder + content: ' ' + } } }); + this._slashCommandContentWidget.setCommandText(command.command); + this._slashCommandContentWidget.show(); + // inject detail when otherwise empty if (firstLine === `/${command.command} `) { newDecorations.push({ @@ -691,6 +708,57 @@ export class InlineChatWidget { } } +class SlashCommandContentWidget extends Disposable implements IContentWidget { + private _domNode = document.createElement('div'); + private _lastSlashCommandText: string | undefined; + + constructor(private _editor: ICodeEditor) { + super(); + + this._domNode.toggleAttribute('hidden', true); + this._domNode.classList.add('inline-chat-slash-command-content-widget'); + + // If backspace at a slash command boundary, remove the slash command + this._register(this._editor.onKeyUp((e) => { + if (e.keyCode !== KeyCode.Backspace) { + return; + } + + const firstLine = this._editor.getModel()?.getLineContent(1); + const selection = this._editor.getSelection(); + const withSlash = `/${this._lastSlashCommandText}`; + if (!firstLine?.startsWith(withSlash) || !selection?.isEmpty() || selection?.startLineNumber !== 1 || selection?.startColumn !== withSlash.length + 1) { + return; + } + + // Allow to undo the backspace + this._editor.executeEdits('inline-chat-slash-command', [{ + range: new Range(1, 1, 1, selection.startColumn), + text: null + }]); + })); + } + + show() { + this._domNode.toggleAttribute('hidden', false); + this._editor.addContentWidget(this); + } + + hide() { + this._domNode.toggleAttribute('hidden', true); + this._editor.removeContentWidget(this); + } + + setCommandText(slashCommand: string) { + this._domNode.innerText = `${slashCommand} `; + this._lastSlashCommandText = slashCommand; + } + + getId() { return 'inline-chat-slash-command-content-widget'; } + getDomNode() { return this._domNode; } + getPosition() { return { position: { lineNumber: 1, column: 1 }, preference: [ContentWidgetPositionPreference.EXACT] }; } +} + export class InlineChatZoneWidget extends ZoneWidget { readonly widget: InlineChatWidget; From d40162dd41d14dfc23b3026edc0ff4b9a8eaeeb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 13 Jul 2023 15:36:14 +0200 Subject: [PATCH 093/826] update distro (#187830) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2c98e0a79bf..a1961f94d9d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.81.0", - "distro": "bed7b201bc6ebbfc63268cb4c390d788a86b0192", + "distro": "97db68abc58fce4ef0a70cdaa6255836e6a8d085", "author": { "name": "Microsoft Corporation" }, @@ -231,4 +231,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} +} \ No newline at end of file From 034fd0518396d1efbce5b99bcc6fd9c713597786 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 13 Jul 2023 16:00:16 +0200 Subject: [PATCH 094/826] inline chat - workaround not being able to hide all lines of an editor (#187836) --- .../browser/inlineChatLivePreviewWidget.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index e39c8d6d6ab..afc0d55b3ce 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -307,15 +307,22 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { } private _hideEditorRanges(editor: ICodeEditor, lineRanges: LineRange[]): void { + assertType(editor.hasModel()); + lineRanges = lineRanges.filter(range => !range.isEmpty); if (lineRanges.length === 0) { // todo? this._logService.debug(`[IE] diff NOTHING to hide for ${editor.getId()} with ${String(editor.getModel()?.uri)}`); return; } - const ranges = lineRanges.map(lineRangeAsRange); - editor.setHiddenAreas(ranges, InlineChatLivePreviewWidget._hideId); - this._logService.debug(`[IE] diff HIDING ${ranges} for ${editor.getId()} with ${String(editor.getModel()?.uri)}`); + + let hiddenRanges = lineRanges.map(lineRangeAsRange); + if (LineRange.fromRange(hiddenRanges.reduce(Range.plusRange)).equals(LineRange.ofLength(1, editor.getModel().getLineCount()))) { + // TODO not every line can be hidden, keep the first line around + hiddenRanges = [editor.getModel().getFullModelRange().delta(1)]; + } + editor.setHiddenAreas(hiddenRanges, InlineChatLivePreviewWidget._hideId); + this._logService.debug(`[IE] diff HIDING ${hiddenRanges} for ${editor.getId()} with ${String(editor.getModel()?.uri)}`); } protected override revealRange(range: Range, isLastLine: boolean): void { From b92a1b8e7433ff97154590183287336eb2973f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 13 Jul 2023 16:04:45 +0200 Subject: [PATCH 095/826] Do not show "Show Update Release Notes" for recovery releases (#187838) fixes #187828 --- src/vs/workbench/contrib/update/browser/update.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 01fb0a75904..538bbbd5578 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -32,6 +32,7 @@ import { Event } from 'vs/base/common/event'; import { Action } from 'vs/base/common/actions'; export const CONTEXT_UPDATE_STATE = new RawContextKey('updateState', StateType.Uninitialized); +export const MAJOR_MINOR_UPDATE_AVAILABLE = new RawContextKey('majorMinorUpdateAvailable', false); export const RELEASE_NOTES_URL = new RawContextKey('releaseNotesUrl', ''); export const DOWNLOAD_URL = new RawContextKey('downloadUrl', ''); @@ -160,6 +161,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu private state: UpdateState; private readonly badgeDisposable = this._register(new MutableDisposable()); private updateStateContextKey: IContextKey; + private majorMinorUpdateAvailableContextKey: IContextKey; constructor( @IStorageService private readonly storageService: IStorageService, @@ -176,6 +178,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu super(); this.state = updateService.state; this.updateStateContextKey = CONTEXT_UPDATE_STATE.bindTo(this.contextKeyService); + this.majorMinorUpdateAvailableContextKey = MAJOR_MINOR_UPDATE_AVAILABLE.bindTo(this.contextKeyService); this._register(updateService.onStateChange(this.onUpdateStateChange, this)); this.onUpdateStateChange(this.updateService.state); @@ -237,9 +240,13 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu this.onUpdateDownloaded(state.update); break; - case StateType.Ready: + case StateType.Ready: { + const currentVersion = parseVersion(this.productService.version); + const nextVersion = parseVersion(state.update.productVersion); + this.majorMinorUpdateAvailableContextKey.set(Boolean(currentVersion && nextVersion && isMajorMinorUpdate(currentVersion, nextVersion))); this.onUpdateReady(state.update); break; + } } let badge: IBadge | undefined = undefined; @@ -461,16 +468,18 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu }); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '7_update', + order: 1, command: { id: 'update.showUpdateReleaseNotes', title: nls.localize('showUpdateReleaseNotes', "Show Update Release Notes") }, - when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) + when: ContextKeyExpr.and(CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready), MAJOR_MINOR_UPDATE_AVAILABLE) }); CommandsRegistry.registerCommand('update.restart', () => this.updateService.quitAndInstall()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '7_update', + order: 2, command: { id: 'update.restart', title: nls.localize('restartToUpdate', "Restart to Update (1)") From 201401dc6df97f0655a5efad34303a2d6b49b678 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 13 Jul 2023 16:08:48 +0200 Subject: [PATCH 096/826] Sends telemetry when a line is slow to tokenize. (#187835) Fixes https://github.com/microsoft/vscode-internalbacklog/issues/4454 --- .../browser/textMateTokenizationFeatureImpl.ts | 14 +++++++++----- .../textMateTokenizationSupport.ts | 14 +++++++++----- .../textMate/browser/worker/textMate.worker.ts | 4 ++-- .../textMate/browser/worker/textMateWorkerModel.ts | 8 ++++++-- .../browser/workerHost/textMateWorkerHost.ts | 6 +++--- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts index b36b7e5910e..dd19178ffe9 100644 --- a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts +++ b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts @@ -57,7 +57,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate private _currentTokenColorMap: string[] | null = null; private readonly _workerHost = this._instantiationService.createInstance( TextMateWorkerHost, - (timeMs, languageId, sourceExtensionId, lineLength) => this.reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, true) + (timeMs, languageId, sourceExtensionId, lineLength, isRandomSample) => this.reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, true, isRandomSample) ); constructor( @@ -291,9 +291,10 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate r.containsEmbeddedLanguages, (textModel, tokenStore) => this._workerHost.createBackgroundTokenizer(textModel, tokenStore, maxTokenizationLineLength), () => this._configurationService.getValue('editor.experimental.asyncTokenizationVerification'), - (timeMs, lineLength) => { - this.reportTokenizationTime(timeMs, languageId, r.sourceExtensionId, lineLength, false); - } + (timeMs, lineLength, isRandomSample) => { + this.reportTokenizationTime(timeMs, languageId, r.sourceExtensionId, lineLength, false, isRandomSample); + }, + true, ); tokenization.onDidEncounterLanguage((encodedLanguageId) => { if (!this._encounteredLanguages[encodedLanguageId]) { @@ -377,7 +378,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate } } - public reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, fromWorker: boolean): void { + public reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, fromWorker: boolean, isRandomSample: boolean): void { // 50 events per hour (one event has a low probability) if (TextMateTokenizationFeature.reportTokenizationTimeCounter > 50) { // Don't flood telemetry with too many events @@ -396,6 +397,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate lineLength: number; fromWorker: boolean; sourceExtensionId: string | undefined; + isRandomSample: boolean; }, { owner: 'hediet'; @@ -404,6 +406,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate lineLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'To relate the performance to the line length' }; fromWorker: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'To figure out if this line was tokenized sync or async' }; sourceExtensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'To figure out which extension contributed the grammar' }; + isRandomSample: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'To figure out if this is a random sample or measured because of some other condition.' }; comment: 'This event gives insight about the performance certain grammars.'; }>('editor.tokenizedLine', { @@ -412,6 +415,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate lineLength, fromWorker, sourceExtensionId, + isRandomSample, }); } } diff --git a/src/vs/workbench/services/textMate/browser/tokenizationSupport/textMateTokenizationSupport.ts b/src/vs/workbench/services/textMate/browser/tokenizationSupport/textMateTokenizationSupport.ts index 8a643cedce1..6a7328d31cd 100644 --- a/src/vs/workbench/services/textMate/browser/tokenizationSupport/textMateTokenizationSupport.ts +++ b/src/vs/workbench/services/textMate/browser/tokenizationSupport/textMateTokenizationSupport.ts @@ -20,9 +20,10 @@ export class TextMateTokenizationSupport extends Disposable implements ITokeniza private readonly _grammar: IGrammar, private readonly _initialState: StateStack, private readonly _containsEmbeddedLanguages: boolean, - private readonly _createBackgroundTokenizer?: (textModel: ITextModel, tokenStore: IBackgroundTokenizationStore) => IBackgroundTokenizer | undefined, - private readonly _backgroundTokenizerShouldOnlyVerifyTokens: () => boolean = () => false, - private readonly _reportTokenizationTime?: (timeMs: number, lineLength: number) => void, + private readonly _createBackgroundTokenizer: ((textModel: ITextModel, tokenStore: IBackgroundTokenizationStore) => IBackgroundTokenizer | undefined) | undefined, + private readonly _backgroundTokenizerShouldOnlyVerifyTokens: () => boolean, + private readonly _reportTokenizationTime: (timeMs: number, lineLength: number, isRandomSample: boolean) => void, + private readonly _reportSlowTokenization: boolean, ) { super(); } @@ -47,12 +48,15 @@ export class TextMateTokenizationSupport extends Disposable implements ITokeniza } public tokenizeEncoded(line: string, hasEOL: boolean, state: StateStack): EncodedTokenizationResult { - const shouldMeasure = this._reportTokenizationTime && (Math.random() * 10_000 < 1); + const isRandomSample = Math.random() * 10_000 < 1; + const shouldMeasure = this._reportSlowTokenization || isRandomSample; const sw = shouldMeasure ? new StopWatch(true) : undefined; const textMateResult = this._grammar.tokenizeLine2(line, state, 500); if (shouldMeasure) { const timeMS = sw!.elapsed(); - this._reportTokenizationTime!(timeMS, line.length); + if (isRandomSample || timeMS > 32) { + this._reportTokenizationTime!(timeMS, line.length, isRandomSample); + } } if (textMateResult.stoppedEarly) { diff --git a/src/vs/workbench/services/textMate/browser/worker/textMate.worker.ts b/src/vs/workbench/services/textMate/browser/worker/textMate.worker.ts index 9f4a7f42512..275db923233 100644 --- a/src/vs/workbench/services/textMate/browser/worker/textMate.worker.ts +++ b/src/vs/workbench/services/textMate/browser/worker/textMate.worker.ts @@ -136,8 +136,8 @@ export class TextMateTokenizationWorker { this._host.setTokensAndStates(resource, versionId, tokens, stateDeltas); } - public reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number): void { - this._host.reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength); + public reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void { + this._host.reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, isRandomSample); } // #endregion diff --git a/src/vs/workbench/services/textMate/browser/worker/textMateWorkerModel.ts b/src/vs/workbench/services/textMate/browser/worker/textMateWorkerModel.ts index db68091ac5f..c3979384867 100644 --- a/src/vs/workbench/services/textMate/browser/worker/textMateWorkerModel.ts +++ b/src/vs/workbench/services/textMate/browser/worker/textMateWorkerModel.ts @@ -98,8 +98,12 @@ export class TextMateWorkerModel extends MirrorTextModel { if (r.grammar) { const tokenizationSupport = new TokenizationSupportWithLineLimit( this._encodedLanguageId, - new TextMateTokenizationSupport(r.grammar, r.initialState, false, undefined, undefined, - (timeMs, lineLength) => { this._worker.reportTokenizationTime(timeMs, languageId, r.sourceExtensionId, lineLength); }), + new TextMateTokenizationSupport(r.grammar, r.initialState, false, undefined, () => false, + (timeMs, lineLength, isRandomSample) => { + this._worker.reportTokenizationTime(timeMs, languageId, r.sourceExtensionId, lineLength, isRandomSample); + }, + false + ), this._maxTokenizationLineLength ); this._tokenizationStateStore = new TokenizerWithStateStore(this._lines.length, tokenizationSupport); diff --git a/src/vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost.ts b/src/vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost.ts index 71032a5943f..723152d3d6b 100644 --- a/src/vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost.ts +++ b/src/vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost.ts @@ -38,7 +38,7 @@ export class TextMateWorkerHost implements IDisposable { private _grammarDefinitions: IValidGrammarDefinition[] = []; constructor( - private readonly _reportTokenizationTime: (timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number) => void, + private readonly _reportTokenizationTime: (timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean) => void, @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, @IModelService private readonly _modelService: IModelService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, @@ -204,8 +204,8 @@ export class TextMateWorkerHost implements IDisposable { } } - public reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number): void { - this._reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength); + public reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void { + this._reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, isRandomSample); } // #endregion From a015a64c25d2782c01c5d0cb7a11da1893ce1880 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 16:16:28 +0200 Subject: [PATCH 097/826] review change --- .../contrib/inlineChat/browser/inlineChatActions.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 03e8cf4238f..95fef855285 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -461,9 +461,10 @@ export class ToggleInlineDiff extends AbstractInlineChatAction { title: localize('showDiff2', "Show Diff"), mnemonicTitle: localize({ key: 'miShowDiff2', comment: ['&& denotes a mnemonic'] }, "&&Show Diff") }, + precondition: ContextKeyExpr.notEquals('config.inlineChat.mode', 'preview'), menu: [ - { id: MenuId.CommandPalette, when: CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview) }, - { id: MENU_INLINE_CHAT_WIDGET_TOGGLE, when: CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview) } + { id: MenuId.CommandPalette }, + { id: MENU_INLINE_CHAT_WIDGET_TOGGLE } ] }); } From 5284fb58407eb494befe1564691558d739798cfd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 13 Jul 2023 16:18:33 +0200 Subject: [PATCH 098/826] make live preview always show a side-by-side diff (#187840) --- .../browser/inlineChatLivePreviewWidget.ts | 98 ++----------------- 1 file changed, 8 insertions(+), 90 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index afc0d55b3ce..9fd5b51465c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -10,7 +10,7 @@ import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; -import { IModelDecorationOptions, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { ITextModel } from 'vs/editor/common/model'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; @@ -21,7 +21,7 @@ import { LineRange } from 'vs/editor/common/core/lineRange'; import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer'; import { Position } from 'vs/editor/common/core/position'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { IEditorDecorationsCollection, ScrollType } from 'vs/editor/common/editorCommon'; +import { ScrollType } from 'vs/editor/common/editorCommon'; import { ILogService } from 'vs/platform/log/common/log'; import { lineRangeAsRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ResourceLabel } from 'vs/workbench/browser/labels'; @@ -43,7 +43,6 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { private readonly _sessionStore = this._disposables.add(new DisposableStore()); private readonly _diffEditor: IDiffEditor; - private readonly _inlineDiffDecorations: IEditorDecorationsCollection; private _dim: Dimension | undefined; private _isVisible: boolean = false; private _isDiffLocked: boolean = false; @@ -59,8 +58,6 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { super.create(); assertType(editor.hasModel()); - this._inlineDiffDecorations = editor.createDecorationsCollection(); - const diffContributions = EditorExtensionsRegistry .getEditorContributions() .filter(c => c.id !== INLINE_CHAT_ID && c.id !== FoldingController.ID); @@ -120,10 +117,6 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { this._disposables.add(themeService.onDidColorThemeChange(doStyle)); } - override dispose(): void { - this._inlineDiffDecorations.clear(); - super.dispose(); - } protected override _fillContainer(container: HTMLElement): void { container.appendChild(this._elements.domNode); @@ -137,7 +130,6 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { override hide(): void { this._cleanupFullDiff(); - this._cleanupInlineDiff(); this._sessionStore.clear(); super.hide(); this._isVisible = false; @@ -175,70 +167,9 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { return; } - if (changes.length === 0 || this._session.textModel0.getValueLength() === 0) { - // no change or changes to an empty file - this._logService.debug('[IE] livePreview-mode: no diff'); - this.hide(); - - } else if (changes.every(isInlineDiffFriendly)) { - // simple changes - this._logService.debug('[IE] livePreview-mode: inline diff'); - this._cleanupFullDiff(); - this._renderChangesWithInlineDiff(changes); - - } else { - // complex changes - this._logService.debug('[IE] livePreview-mode: full diff'); - this._cleanupInlineDiff(); - this._renderChangesWithFullDiff(changes, range); - } - } - - // --- inline diff - - private _renderChangesWithInlineDiff(changes: readonly LineRangeMapping[]) { - const original = this._session.textModel0; - - const decorations: IModelDeltaDecoration[] = []; - - for (const { innerChanges } of changes) { - if (!innerChanges) { - continue; - } - for (const { modifiedRange, originalRange } of innerChanges) { - - const options: IModelDecorationOptions = { - description: 'interactive-diff-inline', - showIfCollapsed: true, - }; - - if (!modifiedRange.isEmpty()) { - options.className = 'inline-chat-lines-inserted-range'; - } - - if (!originalRange.isEmpty()) { - let content = original.getValueInRange(originalRange); - if (content.length > 7) { - content = content.substring(0, 7) + '…'; - } - options.before = { - content, - inlineClassName: 'inline-chat-lines-deleted-range-inline' - }; - } - - decorations.push({ - range: modifiedRange, - options - }); - } - } - - this._inlineDiffDecorations.set(decorations); - } - - private _cleanupInlineDiff() { - this._inlineDiffDecorations.clear(); + // complex changes + this._logService.debug('[IE] livePreview-mode: full diff'); + this._renderChangesWithFullDiff(changes, range); } // --- full diff @@ -273,7 +204,9 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { } private _computeHiddenRanges(model: ITextModel, range: Range, changes: readonly LineRangeMapping[]) { - assertType(changes.length > 0); + if (changes.length === 0) { + changes = [new LineRangeMapping(LineRange.fromRange(range), LineRange.fromRange(range), undefined)]; + } let originalLineRange = changes[0].originalRange; let modifiedLineRange = changes[0].modifiedRange; @@ -347,21 +280,6 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { } } -function isInlineDiffFriendly(mapping: LineRangeMapping): boolean { - if (!mapping.modifiedRange.equals(mapping.originalRange)) { - return false; - } - if (!mapping.innerChanges) { - return false; - } - for (const { modifiedRange, originalRange } of mapping.innerChanges) { - if (Range.spansMultipleLines(modifiedRange) || Range.spansMultipleLines(originalRange)) { - return false; - } - } - return true; -} - export class InlineChatFileCreatePreviewWidget extends ZoneWidget { From 7a2d479ab9659b177a49135424c1a737f428e676 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Jul 2023 16:21:53 +0200 Subject: [PATCH 099/826] implement feedback (#187841) --- .../browser/userDataProfile.ts | 83 ++++++----------- .../browser/userDataProfilePreview.ts | 2 +- .../userDataProfileImportExportService.ts | 92 ++++++++----------- .../userDataProfile/common/userDataProfile.ts | 4 +- 4 files changed, 68 insertions(+), 113 deletions(-) diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index ec4a10c8f33..ced5b63ed9b 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -31,10 +31,16 @@ import { IRequestService, asJson } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; +const CREATE_EMPTY_PROFILE_ACTION_ID = 'workbench.profiles.actions.createEmptyProfile'; +const CREATE_EMPTY_PROFILE_ACTION_TITLE = { + value: localize('create empty profile', "Create an Empty Profile..."), + original: 'Create an Empty Profile...' +}; + const CREATE_FROM_CURRENT_PROFILE_ACTION_ID = 'workbench.profiles.actions.createFromCurrentProfile'; const CREATE_FROM_CURRENT_PROFILE_ACTION_TITLE = { - value: localize('save profile as', "Create from Current Profile..."), - original: 'Create from Current Profile...' + value: localize('save profile as', "Save Current Profile As..."), + original: 'Save Current Profile As...' }; const CREATE_NEW_PROFILE_ACTION_ID = 'workbench.profiles.actions.createNewProfile'; @@ -113,7 +119,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.registerCreateNewProfileAction(); this.registerCreateProfileAction(); this.registerDeleteProfileAction(); - this.registerCreateProfileFromTemplatesAction(); this.registerHelpAction(); } @@ -445,11 +450,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this._register(registerAction2(class CreateEmptyProfileAction extends Action2 { constructor() { super({ - id: 'workbench.profiles.actions.createEmptyProfile', - title: { - value: localize('create empty profile', "Create an Empty Profile..."), - original: 'Create an Empty Profile...' - }, + id: CREATE_EMPTY_PROFILE_ACTION_ID, + title: CREATE_EMPTY_PROFILE_ACTION_TITLE, category: PROFILES_CATEGORY, f1: true, precondition: PROFILES_ENABLEMENT_CONTEXT @@ -480,7 +482,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements return; } try { - await this.userDataProfileImportExportService.createFromCurrentProfile(name); + await this.userDataProfileImportExportService.SaveCurrentProfileAs(name); } catch (error) { this.notificationService.error(error); } @@ -489,13 +491,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements private async createNewProfile(): Promise { const title = localize('create new profle', "Create New Profile..."); - const name = await this.getNameForProfile(title); - if (!name) { - return; - } - const settings: IQuickPickItem = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: true }; - const keybindings: IQuickPickItem = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: false }; + const keybindings: IQuickPickItem = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: true }; const snippets: IQuickPickItem = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: true }; const tasks: IQuickPickItem = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: true }; const extensions: IQuickPickItem = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: true }; @@ -510,7 +507,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements quickPick.hideCheckAll = true; quickPick.ignoreFocusOut = true; quickPick.customLabel = localize('create', "Create Profile"); - quickPick.description = localize('customise the profile', "Choose the data that should be scoped to the new profile."); + quickPick.description = localize('customise the profile', "Unselect to link to the Default Profile"); const disposables = new DisposableStore(); const update = () => { @@ -523,7 +520,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements let needUpdate = false; for (const resource of resources) { resource.picked = items.includes(resource); - const description = resource.picked ? undefined : localize('use default profile', "From Default Profile"); + const description = resource.picked ? undefined : localize('use default profile', "Default Profile"); if (resource.description !== description) { resource.description = description; needUpdate = true; @@ -554,6 +551,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements return; } + const name = await this.getNameForProfile(title); + if (!name) { + return; + } + try { const useDefaultFlags: UseDefaultProfileFlags | undefined = result.length !== resources.length ? { settings: !result.includes(settings), @@ -609,11 +611,16 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const commandService = accessor.get(ICommandService); const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); const quickPickItems: QuickPickItem[] = [{ + id: CREATE_EMPTY_PROFILE_ACTION_ID, + label: localize('empty profile', "Empty Profile..."), + }, { id: CREATE_NEW_PROFILE_ACTION_ID, label: localize('new profile', "New Profile..."), + }, { + type: 'separator', }, { id: CREATE_FROM_CURRENT_PROFILE_ACTION_ID, - label: localize('using current', "From Current Profile..."), + label: localize('using current', "Save Current Profile As..."), }]; const profileTemplateQuickPickItems = await that.getProfileTemplatesQuickPickItems(); if (profileTemplateQuickPickItems.length) { @@ -643,7 +650,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }; that.telemetryService.publicLog2('profileCreationAction:builtinTemplate', { profileName: (pick).name }); const uri = URI.parse((pick).url); - return userDataProfileImportExportService.importProfile(uri); + return userDataProfileImportExportService.importProfile(uri, { mode: 'apply' }); } } } @@ -705,44 +712,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }); } - private registerCreateProfileFromTemplatesAction(): void { - const that = this; - this._register(registerAction2(class CreateProfileFromTemplatesAction extends Action2 { - constructor() { - super({ - id: 'workbench.profiles.actions.createProfileFromTemplates', - title: { - value: localize('create profile from templates', "Create Profile from Templates..."), - original: 'Create Profile from Templates...' - }, - category: PROFILES_CATEGORY, - precondition: PROFILES_ENABLEMENT_CONTEXT, - }); - } - - async run(accessor: ServicesAccessor) { - const quickInputService = accessor.get(IQuickInputService); - const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); - const notificationService = accessor.get(INotificationService); - const profileTemplateQuickPickItems = await that.getProfileTemplatesQuickPickItems(); - if (profileTemplateQuickPickItems.length) { - const pick = await quickInputService.pick(profileTemplateQuickPickItems, - { - hideInput: true, - canPickMany: false, - title: localize('create profile from template title', "{0}: Create...", PROFILES_CATEGORY.value) - }); - if ((pick)?.url) { - const uri = URI.parse((pick).url); - return userDataProfileImportExportService.importProfile(uri); - } - } else { - notificationService.info(localize('no templates', "There are no templates to create from")); - } - } - })); - } - private registerHelpAction(): void { this._register(registerAction2(class HelpAction extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview.ts index 63abe7798c6..095948fa26f 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview.ts @@ -20,7 +20,7 @@ export class UserDataProfilePreviewContribution extends Disposable implements IW ) { super(); if (environmentService.options?.profileToPreview) { - userDataProfileImportExportService.importProfile(URI.revive(environmentService.options.profileToPreview), { preview: true }) + userDataProfileImportExportService.importProfile(URI.revive(environmentService.options.profileToPreview), { mode: 'preview' }) .then(null, error => logService.error('Error while previewing the profile', getErrorMessage(error))); } } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 18fc59d8551..3fe9085063c 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -199,18 +199,21 @@ export class UserDataProfileImportExportService extends Disposable implements IU disposables.add(toDisposable(() => this.isProfileImportInProgressContextKey.set(false))); try { + const mode = options?.mode ?? 'preview'; const profileTemplate = await this.progressService.withProgress({ location: ProgressLocation.Window, command: showWindowLogActionId, - title: localize('resolving uri', "{0}: Resolving profile content...", options?.preview ? localize('preview profile', "Preview Profile") : localize('import profile', "Create Profile")), + title: localize('resolving uri', "{0}: Resolving profile content...", options?.mode ? localize('preview profile', "Preview Profile") : localize('import profile', "Create Profile")), }, () => this.resolveProfileTemplate(uri)); if (!profileTemplate) { return; } - if (options?.preview) { - await this.previewProfile(uri, profileTemplate); - } else { - await this.doImportProfile(profileTemplate); + if (mode === 'preview') { + await this.previewProfile(profileTemplate); + } else if (mode === 'apply') { + await this.createAndSwitch(profileTemplate, false, true, localize('create profile', "Create Profile")); + } else if (mode === 'both') { + await this.importAndPreviewProfile(uri, profileTemplate); } } finally { disposables.dispose(); @@ -246,11 +249,11 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - async createFromCurrentProfile(name: string): Promise { + async SaveCurrentProfileAs(name: string): Promise { const userDataProfilesExportState = this.instantiationService.createInstance(UserDataProfileExportState, this.userDataProfileService.currentProfile); try { const profileTemplate = await userDataProfilesExportState.getProfileTemplate(name, undefined); - await this.doImportProfile(profileTemplate); + await this.createAndSwitch(profileTemplate, false, true, localize('save profile as', "Save Profile As")); } finally { userDataProfilesExportState.dispose(); } @@ -266,7 +269,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU sticky: true, }, async progress => { const reportProgress = (message: string) => progress.report({ message: localize('troubleshoot profile progress', "Setting up Troubleshoot Profile: {0}", message) }); - const profile = await this.importWithProgress(profileTemplate, true, false, reportProgress); + const profile = await this.createProfile(profileTemplate, true, false, reportProgress); if (profile) { reportProgress(localize('progress extensions', "Applying Extensions...")); await this.instantiationService.createInstance(ExtensionsResource).copy(this.userDataProfileService.currentProfile, profile, true); @@ -359,21 +362,21 @@ export class UserDataProfileImportExportService extends Disposable implements IU return profileTemplate; } - private async previewProfile(uri: URI, profileTemplate: IUserDataProfileTemplate): Promise { + private async importAndPreviewProfile(uri: URI, profileTemplate: IUserDataProfileTemplate): Promise { const disposables = new DisposableStore(); try { const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate)); profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); - const importedProfile = await this.importAndSwitch(profileTemplate, true, false, localize('preview profile', "Preview Profile")); + const importedProfile = await this.createAndSwitch(profileTemplate, true, false, localize('preview profile', "Preview Profile")); if (!importedProfile) { return; } const barrier = new Barrier(); - const importAction = this.getImportAction(barrier, userDataProfileImportState); + const importAction = this.getCreateAction(barrier, userDataProfileImportState); const primaryAction = isWeb ? new Action('importInDesktop', localize('import in desktop', "Create Profile in {0}", this.productService.nameLong), undefined, true, async () => this.openerService.open(uri, { openExternal: true })) : importAction; @@ -432,69 +435,52 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - private async doImportProfile(profileTemplate: IUserDataProfileTemplate): Promise { + private async previewProfile(profileTemplate: IUserDataProfileTemplate): Promise { const disposables = new DisposableStore(); try { const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate)); - const barrier = new Barrier(); - const importAction = this.getImportAction(barrier, userDataProfileImportState); if (userDataProfileImportState.isEmpty()) { - await importAction.run(); + await this.createAndSwitch(profileTemplate, false, true, localize('create profile', "Create Profile")); } else { + const barrier = new Barrier(); + const importAction = this.getCreateAction(barrier, userDataProfileImportState); await this.showProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW, profileTemplate.name, importAction, new BarrierAction(barrier, new Action('cancel', localize('cancel', "Cancel")), this.notificationService), false, userDataProfileImportState); + await barrier.wait(); + await this.hideProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW); } - await barrier.wait(); - await this.hideProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW); } finally { disposables.dispose(); } } - private getImportAction(barrier: Barrier, userDataProfileImportState: UserDataProfileImportState): IAction { - const title = localize('import', "Create Profile", userDataProfileImportState.profile.name); - const importAction = new BarrierAction(barrier, new Action('import', title, undefined, true, () => { - const importProfileFn = async () => { - importAction.enabled = false; - const profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); - const importedProfile = await this.importAndSwitch(profileTemplate, false, true, title); - if (!importedProfile) { - return; - } - }; - if (userDataProfileImportState.isEmpty()) { - return importProfileFn(); - } else { - return this.progressService.withProgress({ - location: IMPORT_PROFILE_PREVIEW_VIEW, - }, () => importProfileFn()); - } + private getCreateAction(barrier: Barrier, userDataProfileImportState: UserDataProfileImportState): IAction { + const importAction = new BarrierAction(barrier, new Action('title', localize('import', "Create Profile"), undefined, true, async () => { + importAction.enabled = false; + const profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); + return this.createAndSwitch(profileTemplate, false, true, localize('create profile', "Create Profile")); }), this.notificationService); return importAction; } - private async importAndSwitch(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, title: string): Promise { + private async createAndSwitch(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, title: string): Promise { return this.progressService.withProgress({ - location: ProgressLocation.Window, - command: showWindowLogActionId, + location: ProgressLocation.Notification, + delay: 500, + sticky: true, }, async (progress) => { - progress.report({ message: localize('Importing profile', "{0} ({1})...", title, profileTemplate.name) }); - return this.importAndSwitchWithProgress(profileTemplate, temporaryProfile, extensions, message => progress.report({ message: `${title} (${profileTemplate.name}): ${message}` })); + title = `${title} (${profileTemplate.name})`; + progress.report({ message: title }); + const reportProgress = (message: string) => progress.report({ message: `${title}: ${message}` }); + const profile = await this.createProfile(profileTemplate, temporaryProfile, extensions, reportProgress); + if (profile) { + reportProgress(localize('switching profile', "Switching Profile...")); + await this.userDataProfileManagementService.switchProfile(profile); + } + return profile; }); } - private async importAndSwitchWithProgress(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, progress: (message: string) => void): Promise { - const profile = await this.importWithProgress(profileTemplate, temporaryProfile, extensions, progress); - - if (!profile) { - return; - } - - progress(localize('switching profile', "Switching Profile...")); - await this.userDataProfileManagementService.switchProfile(profile); - return profile; - } - - private async importWithProgress(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, progress: (message: string) => void): Promise { + private async createProfile(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, progress: (message: string) => void): Promise { const profile = await this.getProfileToImport(profileTemplate, temporaryProfile); if (!profile) { return undefined; diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 99b78b05bab..bd126081ab0 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -74,7 +74,7 @@ export function toUserDataProfileUri(path: string, productService: IProductServi } export interface IProfileImportOptions { - readonly preview?: boolean; + readonly mode?: 'preview' | 'apply' | 'both'; } export const IUserDataProfileImportExportService = createDecorator('IUserDataProfileImportExportService'); @@ -87,7 +87,7 @@ export interface IUserDataProfileImportExportService { exportProfile(): Promise; importProfile(uri: URI, options?: IProfileImportOptions): Promise; showProfileContents(): Promise; - createFromCurrentProfile(name: string): Promise; + SaveCurrentProfileAs(name: string): Promise; createTroubleshootProfile(): Promise; setProfile(profile: IUserDataProfileTemplate): Promise; } From 22bb4b95ccd2f128e4ab08e5b2099b58f51cbc9e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 13 Jul 2023 07:50:43 -0700 Subject: [PATCH 100/826] build: exclude the cli third party notices from hygenie (#187843) This might get moved later on, but for now exclude them --- build/filters.js | 1 + 1 file changed, 1 insertion(+) diff --git a/build/filters.js b/build/filters.js index 095cf733f8c..d729582eb6c 100644 --- a/build/filters.js +++ b/build/filters.js @@ -32,6 +32,7 @@ module.exports.unicodeFilter = [ '**', '!**/ThirdPartyNotices.txt', + '!**/ThirdPartyNotices.cli.txt', '!**/LICENSE.{txt,rtf}', '!LICENSES.chromium.html', '!**/LICENSE', From d88605d5423555c91ec5876b4f20bc33884dbb38 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 17:13:14 +0200 Subject: [PATCH 101/826] cleaning the code --- .../inlineChat/browser/inlineChatActions.ts | 1 + .../browser/inlineChatController.ts | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 83b481739a1..b65aab455b6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -67,6 +67,7 @@ export class StartSessionAction extends EditorAction2 { options = arg; } InlineChatController.get(editor)?.run(options); + InlineChatController.get(editor)?.run(options); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index f15eff338cd..55b8744a5f8 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -176,15 +176,17 @@ export class InlineChatController implements IEditorContribution { return this._zone.value.position; } + private _currentRun?: Promise; + async run(options: InlineChatRunOptions | undefined = {}): Promise { - this._log('session starting inside of run'); - console.log('before finishExistingSession inside of run'); - await this.finishExistingSession(); - console.log('after finish existing session inside of run'); + this.finishExistingSession(); + if (this._currentRun) { + await this._currentRun; + } this._stashedSession.clear(); - console.log('before calling create session inside of run'); - await this._nextState(State.CREATE_SESSION, options); - this._log('session done or paused'); + this._currentRun = this._nextState(State.CREATE_SESSION, options); + await this._currentRun; + this._currentRun = undefined; } // ---- state machine @@ -833,7 +835,7 @@ export class InlineChatController implements IEditorContribution { return result; } - async finishExistingSession(): Promise { + finishExistingSession(): void { console.log('inside of finish existing session'); console.log(this._activeSession); if (this._activeSession) { @@ -846,6 +848,7 @@ export class InlineChatController implements IEditorContribution { console.log('before accepting inside of finish existing session'); this.acceptSession(); } + } console.log('at the end of finish existing session'); } From 26151020a859156fb692dd82c298915de618a5fa Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 17:16:14 +0200 Subject: [PATCH 102/826] cleaning the code --- .../workbench/contrib/inlineChat/browser/inlineChatSession.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 79ac5e76633..83aa7b064ff 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -477,7 +477,6 @@ export class InlineChatSessionService implements IInlineChatSessionService { } releaseSession(session: Session): void { - console.log('at the beginning of the release session'); const { editor } = session; @@ -499,7 +498,6 @@ export class InlineChatSessionService implements IInlineChatSessionService { // send telemetry this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); - console.log('at the end of the release session'); } getSession(editor: ICodeEditor, uri: URI): Session | undefined { From 15db6060dbea7e707191c96fb62f63dedfda335f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 17:16:24 +0200 Subject: [PATCH 103/826] cleaning the code --- .../inlineChat/browser/inlineChatActions.ts | 1 - .../browser/inlineChatController.ts | 71 ++----------------- .../browser/inlineChatStrategies.ts | 5 +- 3 files changed, 7 insertions(+), 70 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index b65aab455b6..83b481739a1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -67,7 +67,6 @@ export class StartSessionAction extends EditorAction2 { options = arg; } InlineChatController.get(editor)?.run(options); - InlineChatController.get(editor)?.run(options); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 55b8744a5f8..39436818fa9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -134,7 +134,6 @@ export class InlineChatController implements IEditorContribution { } this._log('session RESUMING', e); - console.log('before calling create session of the constructor'); await this._nextState(State.CREATE_SESSION, { existingSession }); this._log('session done or paused'); })); @@ -142,9 +141,7 @@ export class InlineChatController implements IEditorContribution { } dispose(): void { - console.log('inside of dispose'); this._stashedSession.clear(); - console.log('inside of dispose before calling finishExistingSession'); this.finishExistingSession(); this._store.dispose(); this._log('controller disposed'); @@ -221,18 +218,6 @@ export class InlineChatController implements IEditorContribution { } private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { - console.log('inside of CREATE_SESSION'); - console.log('inside of CREATE_SESSION, this._activeSession : ', this._activeSession); - // if (this._activeSession) { - // console.log('inside of CREATE_SESSION, before clearing the session store'); - // this._sessionStore.clear(); - // console.log('inside of CREATE_SESSION, before releasing the session'); - // this._inlineChatSessionService.releaseSession(this._activeSession); - // console.log('inside of CREATE_SESSION, before calling pause'); - // // Doesn't appear to be properly awaited? - // await this[State.PAUSE](); - // } - console.log('inside of CREATE_SESSION, this._activeSession after the cleaning : ', this._activeSession); assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); @@ -245,7 +230,6 @@ export class InlineChatController implements IEditorContribution { if (!session) { const createSessionCts = new CancellationTokenSource(); const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of CREATE_SESSION, inside of the msgListener code of CREATE_SESSION'); this._log('state=_createSession) message received', m); if (m === Message.ACCEPT_INPUT) { // user accepted the input before having a session @@ -297,8 +281,6 @@ export class InlineChatController implements IEditorContribution { } private async [State.INIT_UI](options: InlineChatRunOptions): Promise { - console.log('inside of init ui'); - console.log('inside of INIT_UI, this._activeSession : ', this._activeSession); assertType(this._activeSession); // hide/cancel inline completions when invoking IE @@ -359,20 +341,16 @@ export class InlineChatController implements IEditorContribution { if (editIsOutsideOfWholeRange) { this._log('text changed outside of whole range, FINISH session'); - console.log('inside of INIT_UI, before calling finish existing session'); this.finishExistingSession(); } })); if (!this._activeSession.lastExchange) { - console.log('inside of INIT_UI,before waiting for input'); return State.WAIT_FOR_INPUT; } else if (options.isUnstashed) { delete options.isUnstashed; - console.log('inside of INIT_UI,before apply response'); return State.APPLY_RESPONSE; } else { - console.log('inside of INIT_UI,before show response'); return State.SHOW_RESPONSE; } } @@ -391,9 +369,7 @@ export class InlineChatController implements IEditorContribution { } - private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { - console.log('inside of wait for input'); - + private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { assertType(this._activeSession); assertType(this._strategy); @@ -414,7 +390,6 @@ export class InlineChatController implements IEditorContribution { } else { const barrier = new Barrier(); const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of WAIT_FOR_INPUT, inside of msgListener'); this._log('state=_waitForInput) message received', m); message = m; barrier.open(); @@ -426,17 +401,11 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.selectAll(); if (message & (Message.CANCEL_INPUT | Message.CANCEL_SESSION)) { - console.log('inside of WAIT_FOR_INPUT,entered into the case when message cancel session'); - await this[State.CANCEL](); - return; - // return State.CANCEL; + return State.CANCEL; } if (message & Message.ACCEPT_SESSION) { - console.log('inside of WAIT_FOR_INPUT,entered into the case when message accept'); - await this[State.ACCEPT](); - return; - // return State.ACCEPT; + return State.ACCEPT; } if (message & Message.PAUSE_SESSION) { @@ -488,7 +457,6 @@ export class InlineChatController implements IEditorContribution { let message = Message.NONE; const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of MAKE_REQUEST, inside of msgListener of MAKE REQUEST'); this._log('state=_makeRequest) message received', m); message = m; requestCts.cancel(); @@ -543,12 +511,10 @@ export class InlineChatController implements IEditorContribution { this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); if (message & Message.CANCEL_SESSION) { - console.log('inside of MAKE_REQUEST, cancelling the session'); return State.CANCEL; } else if (message & Message.PAUSE_SESSION) { return State.PAUSE; } else if (message & Message.ACCEPT_SESSION) { - console.log('inside of MAKE_REQUEST, accepting'); return State.ACCEPT; } else { return State.APPLY_RESPONSE; @@ -664,8 +630,6 @@ export class InlineChatController implements IEditorContribution { private async [State.PAUSE]() { - console.log('entered into pause state'); - this._ctxDidEdit.reset(); this._ctxUserDidEdit.reset(); this._ctxLastResponseType.reset(); @@ -685,35 +649,24 @@ export class InlineChatController implements IEditorContribution { } private async [State.ACCEPT]() { - console.log('inside of State.ACCEPT'); assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); - console.log('inside of State.ACCEPT, after assert type'); try { - console.log('inside of State.ACCEPT, before strategy apply'); await this._strategy.apply(); - console.log('inside of State.ACCEPT, after strategy apply'); - // TODO: ASK WHY DESPITE AWAIT, AFTER STRATEFY NOT PRINTED BEFORE CREATE SESSION } catch (err) { - console.log('inside of State.ACCEPT, when error obtained'); this._dialogService.error(localize('err.apply', "Failed to apply changes.", toErrorMessage(err))); this._log('FAILED to apply changes'); this._log(err); } - console.log('inside of State.ACCEPT, before release session'); + this._inlineChatSessionService.releaseSession(this._activeSession); - await this._inlineChatSessionService.releaseSession(this._activeSession); - - console.log('inside of State.ACCEPT, before state pause'); - await this[State.PAUSE](); + this[State.PAUSE](); } private async [State.CANCEL]() { - console.log('inside of cancel the session'); - assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); @@ -728,14 +681,13 @@ export class InlineChatController implements IEditorContribution { this._log(err); } - await this[State.PAUSE](); + this[State.PAUSE](); this._stashedSession.clear(); if (!mySession.isUnstashed && mySession.lastExchange) { // only stash sessions that had edits this._stashedSession.value = this._instaService.createInstance(StashedSession, this._editor, mySession); } else { - console.log('before releasing the session of cancel'); this._inlineChatSessionService.releaseSession(mySession); } } @@ -815,13 +767,10 @@ export class InlineChatController implements IEditorContribution { } acceptSession(): void { - console.log('inside of acceptSession method'); - // Will fire a message which will be picked up by the controller and some other code will be run this._messages.fire(Message.ACCEPT_SESSION); } cancelSession() { - console.log('inside of cancelSession'); let result: string | undefined; if (this._strategy && this._activeSession) { const changedText = this._activeSession.asChangedText(); @@ -836,21 +785,15 @@ export class InlineChatController implements IEditorContribution { } finishExistingSession(): void { - console.log('inside of finish existing session'); - console.log(this._activeSession); if (this._activeSession) { if (this._activeSession.editMode === EditMode.Preview) { this._log('finishing existing session, using CANCEL', this._activeSession.editMode); - console.log('before cancelling inside of finish existing session'); this.cancelSession(); } else { this._log('finishing existing session, using APPLY', this._activeSession.editMode); - console.log('before accepting inside of finish existing session'); this.acceptSession(); } - } - console.log('at the end of finish existing session'); } unstashLastSession(): Session | undefined { @@ -879,7 +822,6 @@ class StashedSession { this._ctxHasStashedSession.set(true); this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { this._session = undefined; - console.log('before release session of constructor'); this._sessionService.releaseSession(session); this._ctxHasStashedSession.reset(); }); @@ -889,7 +831,6 @@ class StashedSession { this._listener.dispose(); this._ctxHasStashedSession.reset(); if (this._session) { - console.log('before release session of dispose'); this._sessionService.releaseSession(this._session); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 019a4ecfd58..36e64ebf09a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -86,7 +86,7 @@ export class PreviewStrategy extends EditModeStrategy { } async apply() { - console.log('at the beginning of the apply method of preview strategy'); + if (!(this._session.lastExchange?.response instanceof EditResponse)) { return; } @@ -107,7 +107,6 @@ export class PreviewStrategy extends EditModeStrategy { modelN.pushStackElement(); } } - console.log('at the end of the apply method of preview strategy'); } async cancel(): Promise { @@ -280,7 +279,6 @@ export class LiveStrategy extends EditModeStrategy { } async apply() { - console.log('inside of apply of live strategy'); if (this._editCount > 0) { this._editor.pushUndoStop(); } @@ -288,7 +286,6 @@ export class LiveStrategy extends EditModeStrategy { await this._bulkEditService.apply(this._lastResponse.workspaceEdits); this._instaService.invokeFunction(showSingleCreateFile, this._lastResponse); } - console.log('at the end of apply for live strategy'); } async cancel() { From ae5d6a86630f4801aec3125cec08d7afc9c43f14 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 13 Jul 2023 08:21:03 -0700 Subject: [PATCH 104/826] use widget that we have already --- .../workbench/contrib/chat/browser/chat.contribution.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index defbcbd581d..8677a3e6ffd 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -132,13 +132,8 @@ class ChatAccessibleViewContribution extends Disposable { let focusedItem: ChatTreeItem | undefined = widget?.getFocus(); let focusedInput = false; const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); - if (editor) { - // focus is in the input box, so we need to focus the last widget first - const editorUri = editor.getModel()?.uri; - if (!editorUri) { - return false; - } - widgetService.getWidgetByInputUri(editorUri)?.focusLastMessage(); + if (editor && widget) { + widget.focusLastMessage(); widget = widgetService.lastFocusedWidget; focusedItem = widget?.getFocus(); focusedInput = true; From d7bdb0a08a3716a37ba958762be0091d519c01bc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 13 Jul 2023 08:55:59 -0700 Subject: [PATCH 105/826] clean up --- .../contrib/chat/browser/chat.contribution.ts | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 8677a3e6ffd..aef372dee92 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -22,7 +22,7 @@ import { registerChatExecuteActions } from 'vs/workbench/contrib/chat/browser/ac import { registerChatQuickQuestionActions } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; import { registerChatTitleActions } from 'vs/workbench/contrib/chat/browser/actions/chatTitleActions'; import { registerChatExportActions } from 'vs/workbench/contrib/chat/browser/actions/chatImportExport'; -import { ChatTreeItem, IChatAccessibilityService, IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatAccessibilityService, IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatContributionService } from 'vs/workbench/contrib/chat/browser/chatContributionServiceImpl'; import { ChatEditor, IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput, ChatEditorInputSerializer } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; @@ -128,32 +128,42 @@ class ChatAccessibleViewContribution extends Disposable { const widgetService = accessor.get(IChatWidgetService); const codeEditorService = accessor.get(ICodeEditorService); - let widget: IChatWidget | undefined = widgetService.lastFocusedWidget; - let focusedItem: ChatTreeItem | undefined = widget?.getFocus(); - let focusedInput = false; - const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); - if (editor && widget) { - widget.focusLastMessage(); - widget = widgetService.lastFocusedWidget; - focusedItem = widget?.getFocus(); - focusedInput = true; - } - if (!widget || !focusedItem || !isResponseVM(focusedItem)) { + let widget = widgetService.lastFocusedWidget; + if (!widget) { return false; } - const responseContent = focusedItem?.response.value; + const chatInputFocused = !!(codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor()); + + if (chatInputFocused) { + widget.focusLastMessage(); + widget = widgetService.lastFocusedWidget; + } + + if (!widget) { + return false; + } + + const verifiedWidget: IChatWidget = widget; + const focusedItem = verifiedWidget.getFocus(); + + if (!focusedItem) { + return false; + } + + const responseContent = isResponseVM(focusedItem) ? focusedItem.response.value : undefined; if (!responseContent) { return false; } + accessibleViewService.show({ verbositySettingKey: 'panelChat', provideContent(): string { return responseContent; }, onClose() { - if (focusedInput) { - widget?.focusInput(); + if (chatInputFocused) { + verifiedWidget.focusInput(); } else { - widget!.focus(focusedItem!); + verifiedWidget.focus(focusedItem); } }, options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View } From 634a9ce37b77891d3c03045126ffb8c8e87c89b5 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 13 Jul 2023 08:56:49 -0700 Subject: [PATCH 106/826] Dispose of test instantiation service (#187848) * Dispose of test instantiation service * Feedback --- .../browser/parts/editor/editorGroupModel.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts index 4b790fac167..3467cdcd507 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts @@ -28,8 +28,18 @@ import { isEqual } from 'vs/base/common/resources'; suite('EditorGroupModel', () => { + let testInstService: TestInstantiationService | undefined; + + suiteTeardown(() => { + testInstService?.dispose(); + testInstService = undefined; + }); + function inst(): IInstantiationService { - const inst = new TestInstantiationService(); + if (!testInstService) { + testInstService = new TestInstantiationService(); + } + const inst = testInstService; inst.stub(IStorageService, new TestStorageService()); inst.stub(ILifecycleService, new TestLifecycleService()); inst.stub(IWorkspaceContextService, new TestContextService()); From 68213224210f346ea86e87e60c4eac45896d45e6 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 13 Jul 2023 09:31:16 -0700 Subject: [PATCH 107/826] Share code for chat slash command blocks --- src/vs/workbench/contrib/chat/browser/chat.ts | 1 - .../browser/chatSlashCommandContentWidget.css | 11 +++ .../browser/chatSlashCommandContentWidget.ts | 69 +++++++++++++++++ .../contrib/chat/browser/chatWidget.ts | 4 - .../browser/contrib/chatInputEditorContrib.ts | 75 +++---------------- .../contrib/inlineChat/browser/inlineChat.css | 7 -- .../inlineChat/browser/inlineChatWidget.ts | 57 +------------- 7 files changed, 95 insertions(+), 129 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.css create mode 100644 src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 8c51aaa2175..b42cad92d7e 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -60,7 +60,6 @@ export interface IChatWidget { acceptInput(query?: string): void; focusLastMessage(): void; focusInput(): void; - getSlashCommandsSync(): ISlashCommand[] | undefined; getSlashCommands(): Promise; getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined; getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[]; diff --git a/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.css b/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.css new file mode 100644 index 00000000000..33532a3ee8a --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.css @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.chat-slash-command-content-widget { + padding: 0 0.4em; + border-radius: 3px; + background-color: var(--vscode-textCodeBlock-background); + color: var(--vscode-textLink-foreground); +} diff --git a/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts b/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts new file mode 100644 index 00000000000..a2fa834d905 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./chatSlashCommandContentWidget'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { Range } from 'vs/editor/common/core/range'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget } from 'vs/editor/browser/editorBrowser'; +import { KeyCode } from 'vs/base/common/keyCodes'; + +export class SlashCommandContentWidget extends Disposable implements IContentWidget { + private _domNode = document.createElement('div'); + private _lastSlashCommandText: string | undefined; + + constructor(private _editor: ICodeEditor) { + super(); + + this._domNode.toggleAttribute('hidden', true); + this._domNode.classList.add('chat-slash-command-content-widget'); + + // If backspace at a slash command boundary, remove the slash command + this._register(this._editor.onKeyUp((e) => this._handleKeyUp(e))); + } + + override dispose() { + this._editor.removeContentWidget(this); + super.dispose(); + } + + show() { + this._domNode.toggleAttribute('hidden', false); + this._editor.addContentWidget(this); + } + + hide() { + this._domNode.toggleAttribute('hidden', true); + this._editor.removeContentWidget(this); + } + + setCommandText(slashCommand: string) { + this._domNode.innerText = `${slashCommand} `; + this._lastSlashCommandText = slashCommand; + } + + getId() { return 'chat-slash-command-content-widget'; } + getDomNode() { return this._domNode; } + getPosition() { return { position: { lineNumber: 1, column: 1 }, preference: [ContentWidgetPositionPreference.EXACT] }; } + + private _handleKeyUp(e: IKeyboardEvent) { + if (e.keyCode !== KeyCode.Backspace) { + return; + } + + const firstLine = this._editor.getModel()?.getLineContent(1); + const selection = this._editor.getSelection(); + const withSlash = `/${this._lastSlashCommandText}`; + if (!firstLine?.startsWith(withSlash) || !selection?.isEmpty() || selection?.startLineNumber !== 1 || selection?.startColumn !== withSlash.length + 1) { + return; + } + + // Allow to undo the backspace + this._editor.executeEdits('inline-chat-slash-command', [{ + range: new Range(1, 1, 1, selection.startColumn), + text: null + }]); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 9fe7dc95836..fa29aac0229 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -219,10 +219,6 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - getSlashCommandsSync(): ISlashCommand[] | undefined { - return this.lastSlashCommands; - } - async getSlashCommands(): Promise { if (!this.viewModel) { return; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 830d85d35b6..46b61d5efa3 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -15,27 +15,22 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { editorForeground, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { ContentWidgetPositionPreference, IContentWidget } from 'vs/editor/browser/editorBrowser'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { Selection } from 'vs/editor/common/core/selection'; +import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; const decorationDescription = 'chat'; const slashCommandPlaceholderDecorationType = 'chat-session-detail'; const slashCommandTextDecorationType = 'chat-session-text'; -const slashCommandContentWidgetId = 'chat-session-content-widget'; class InputEditorDecorations extends Disposable { - private _slashCommandDomNode = document.createElement('div'); - private _slashCommandContentWidget: IContentWidget | undefined; + private _slashCommandContentWidget: SlashCommandContentWidget | undefined; private _previouslyUsedSlashCommands = new Set(); constructor( @@ -66,7 +61,7 @@ class InputEditorDecorations extends Disposable { private updateRegisteredDecorationTypes() { this.codeEditorService.removeDecorationType(slashCommandTextDecorationType); - this.updateInputEditorContentWidgets({ hide: true }); + this._slashCommandContentWidget?.hide(); this.codeEditorService.registerDecorationType(decorationDescription, slashCommandTextDecorationType, { opacity: '0', after: { @@ -109,7 +104,7 @@ class InputEditorDecorations extends Disposable { } ]; this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandPlaceholderDecorationType, decoration); - this.updateInputEditorContentWidgets({ hide: true }); + this._slashCommandContentWidget?.hide(); return; } @@ -142,9 +137,14 @@ class InputEditorDecorations extends Disposable { } if (command && inputValue.startsWith(`/${command.command} `)) { - this.updateInputEditorContentWidgets({ command: command.command }); + if (!this._slashCommandContentWidget) { + this._slashCommandContentWidget = new SlashCommandContentWidget(this.widget.inputEditor); + this._store.add(this._slashCommandContentWidget); + } + this._slashCommandContentWidget.setCommandText(command.command); + this._slashCommandContentWidget.show(); } else { - this.updateInputEditorContentWidgets({ hide: true }); + this._slashCommandContentWidget?.hide(); } if (command && command.detail) { @@ -163,40 +163,6 @@ class InputEditorDecorations extends Disposable { this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandTextDecorationType, []); } } - - private async updateInputEditorContentWidgets(arg: { command: string } | { hide: true }) { - const domNode = this._slashCommandDomNode; - - if (this._slashCommandContentWidget && 'hide' in arg) { - domNode.toggleAttribute('hidden', true); - this.widget.inputEditor.removeContentWidget(this._slashCommandContentWidget); - return; - } else if ('command' in arg) { - const theme = this.themeService.getColorTheme(); - domNode.style.padding = '0 0.4em'; - domNode.style.borderRadius = '3px'; - domNode.style.backgroundColor = theme.getColor(textCodeBlockBackground)?.toString() ?? ''; - domNode.style.color = theme.getColor(textLinkForeground)?.toString() ?? ''; - domNode.innerText = `${arg.command} `; - domNode.toggleAttribute('hidden', false); - - this._slashCommandContentWidget = { - getId() { return slashCommandContentWidgetId; }, - getDomNode() { return domNode; }, - getPosition() { - return { - position: { - lineNumber: 1, - column: 1 - }, - preference: [ContentWidgetPositionPreference.EXACT] - }; - }, - }; - - this.widget.inputEditor.addContentWidget(this._slashCommandContentWidget); - } - } } class InputEditorSlashCommandFollowups extends Disposable { @@ -206,7 +172,6 @@ class InputEditorSlashCommandFollowups extends Disposable { ) { super(); this._register(this.chatService.onDidSubmitSlashCommand(({ slashCommand, sessionId }) => this.repopulateSlashCommand(slashCommand, sessionId))); - this._register(this.widget.inputEditor.onKeyUp((e) => this.handleKeyUp(e))); } private async repopulateSlashCommand(slashCommand: string, sessionId: string) { @@ -227,22 +192,6 @@ class InputEditorSlashCommandFollowups extends Disposable { } } - - private handleKeyUp(e: IKeyboardEvent) { - if (e.keyCode !== KeyCode.Backspace) { - return; - } - - const value = this.widget.inputEditor.getValue().split(' ')[0]; - const currentSelection = this.widget.inputEditor.getSelection(); - if (!value.startsWith('/') || !currentSelection?.isEmpty() || currentSelection?.startLineNumber !== 1 || currentSelection?.startColumn !== value.length + 1) { - return; - } - - if (this.widget.getSlashCommandsSync()?.find((command) => `/${command.command}` === value)) { - this.widget.inputEditor.executeEdits('chat-input-editor-slash-commands', [{ range: new Range(1, 1, 1, currentSelection.startColumn), text: null }], [new Selection(1, 1, 1, 1)]); - } - } } ChatWidget.CONTRIBS.push(InputEditorDecorations, InputEditorSlashCommandFollowups); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index 5beab56e7f2..efdacad53e2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -285,13 +285,6 @@ opacity: 0.5; } -.monaco-editor .inline-chat-slash-command-content-widget { - padding: 0 0.4em; - border-radius: 3px; - background-color: var(--vscode-textCodeBlock-background); - color: var(--vscode-textLink-foreground); -} - /* diff zone */ .monaco-editor .inline-chat-diff-widget { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index ac29d7cf5c1..66927d16722 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./inlineChat'; -import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; +import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { localize } from 'vs/nls'; @@ -49,7 +49,7 @@ import { ExpansionState } from 'vs/workbench/contrib/inlineChat/browser/inlineCh import { IdleValue } from 'vs/base/common/async'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IMenuWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -713,57 +713,6 @@ export class InlineChatWidget { } } -class SlashCommandContentWidget extends Disposable implements IContentWidget { - private _domNode = document.createElement('div'); - private _lastSlashCommandText: string | undefined; - - constructor(private _editor: ICodeEditor) { - super(); - - this._domNode.toggleAttribute('hidden', true); - this._domNode.classList.add('inline-chat-slash-command-content-widget'); - - // If backspace at a slash command boundary, remove the slash command - this._register(this._editor.onKeyUp((e) => { - if (e.keyCode !== KeyCode.Backspace) { - return; - } - - const firstLine = this._editor.getModel()?.getLineContent(1); - const selection = this._editor.getSelection(); - const withSlash = `/${this._lastSlashCommandText}`; - if (!firstLine?.startsWith(withSlash) || !selection?.isEmpty() || selection?.startLineNumber !== 1 || selection?.startColumn !== withSlash.length + 1) { - return; - } - - // Allow to undo the backspace - this._editor.executeEdits('inline-chat-slash-command', [{ - range: new Range(1, 1, 1, selection.startColumn), - text: null - }]); - })); - } - - show() { - this._domNode.toggleAttribute('hidden', false); - this._editor.addContentWidget(this); - } - - hide() { - this._domNode.toggleAttribute('hidden', true); - this._editor.removeContentWidget(this); - } - - setCommandText(slashCommand: string) { - this._domNode.innerText = `${slashCommand} `; - this._lastSlashCommandText = slashCommand; - } - - getId() { return 'inline-chat-slash-command-content-widget'; } - getDomNode() { return this._domNode; } - getPosition() { return { position: { lineNumber: 1, column: 1 }, preference: [ContentWidgetPositionPreference.EXACT] }; } -} - export class InlineChatZoneWidget extends ZoneWidget { readonly widget: InlineChatWidget; From fe22b24f8edb86fd50f6f87697718059a6703633 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Jul 2023 18:52:45 +0200 Subject: [PATCH 108/826] finetune message and fix quickpick styling (#187855) * finetune message and fix quickpick styling * update desc --- .../quickinput/browser/media/quickInput.css | 1 + .../platform/quickinput/browser/quickInput.ts | 9 ++++++--- .../browser/userDataProfile.ts | 19 +++---------------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/quickinput/browser/media/quickInput.css b/src/vs/platform/quickinput/browser/media/quickInput.css index f3fbd726e10..de8df301e3c 100644 --- a/src/vs/platform/quickinput/browser/media/quickInput.css +++ b/src/vs/platform/quickinput/browser/media/quickInput.css @@ -55,6 +55,7 @@ .quick-input-header .quick-input-description { margin: 4px 2px; + flex: 1; } .quick-input-header { diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 213a80ca190..7992328ec1f 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -98,6 +98,7 @@ interface QuickInputUI { widget: HTMLElement; rightActionBar: ActionBar; checkAll: HTMLInputElement; + inputContainer: HTMLElement; filterContainer: HTMLElement; inputBox: QuickInputBox; visibleCountContainer: HTMLElement; @@ -1331,8 +1332,8 @@ export class QuickInputController extends Disposable { })); const description2 = dom.append(headerContainer, $('.quick-input-description')); - const extraContainer = dom.append(headerContainer, $('.quick-input-and-message')); - const filterContainer = dom.append(extraContainer, $('.quick-input-filter')); + const inputContainer = dom.append(headerContainer, $('.quick-input-and-message')); + const filterContainer = dom.append(inputContainer, $('.quick-input-filter')); const inputBox = this._register(new QuickInputBox(filterContainer, this.styles.inputBox, this.styles.toggle)); inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`); @@ -1360,7 +1361,7 @@ export class QuickInputController extends Disposable { this.onDidCustomEmitter.fire(); })); - const message = dom.append(extraContainer, $(`#${this.idPrefix}message.quick-input-message`)); + const message = dom.append(inputContainer, $(`#${this.idPrefix}message.quick-input-message`)); const progressBar = new ProgressBar(container, this.styles.progressBar); progressBar.getContainer().classList.add('quick-input-progress'); @@ -1481,6 +1482,7 @@ export class QuickInputController extends Disposable { widget, rightActionBar, checkAll, + inputContainer, filterContainer, inputBox, visibleCountContainer, @@ -1749,6 +1751,7 @@ export class QuickInputController extends Disposable { ui.description1.style.display = visibilities.description && (visibilities.inputBox || visibilities.checkAll) ? '' : 'none'; ui.description2.style.display = visibilities.description && !(visibilities.inputBox || visibilities.checkAll) ? '' : 'none'; ui.checkAll.style.display = visibilities.checkAll ? '' : 'none'; + ui.inputContainer.style.display = visibilities.inputBox ? '' : 'none'; ui.filterContainer.style.display = visibilities.inputBox ? '' : 'none'; ui.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none'; ui.countContainer.style.display = visibilities.count ? '' : 'none'; diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index ced5b63ed9b..5ea18399b20 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -507,27 +507,14 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements quickPick.hideCheckAll = true; quickPick.ignoreFocusOut = true; quickPick.customLabel = localize('create', "Create Profile"); - quickPick.description = localize('customise the profile', "Unselect to link to the Default Profile"); + quickPick.description = localize('customise the profile', "Choose what you want to configure in the profile. Unselected items are shared from the default profile."); + quickPick.items = resources; + quickPick.selectedItems = resources.filter(item => item.picked); const disposables = new DisposableStore(); - const update = () => { - quickPick.items = resources; - quickPick.selectedItems = resources.filter(item => item.picked); - }; - update(); - disposables.add(quickPick.onDidChangeSelection(items => { - let needUpdate = false; for (const resource of resources) { resource.picked = items.includes(resource); - const description = resource.picked ? undefined : localize('use default profile', "Default Profile"); - if (resource.description !== description) { - resource.description = description; - needUpdate = true; - } - } - if (needUpdate) { - update(); } })); From c9d8ab642c47188aab5c954e968dffa46e86753b Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 13 Jul 2023 10:14:05 -0700 Subject: [PATCH 109/826] Add two alternative UX's for the Quick Question experience (#187780) * Add two alternative UX's for the Quick Question experience Main changes: * Changes to the chat to have an alterative rendering of the input on the top... needs polish but decent for now * The inclusion of a setting to choose which mode you would like to use for the UX * 2 new modes: `InputOnBottomChat` and `InputOnTopChat` which do what you expect. * Move where the enum is to hopefully fix tests --- .../browser/actions/chatQuickInputActions.ts | 280 +----------------- .../multipleByScrollQuickQuestionAction.ts | 267 +++++++++++++++++ .../quickQuestionAction.ts | 61 ++++ .../singleQuickQuestionAction.ts | 261 ++++++++++++++++ .../contrib/chat/browser/chat.contribution.ts | 8 + src/vs/workbench/contrib/chat/browser/chat.ts | 2 +- .../contrib/chat/browser/chatInputPart.ts | 11 +- .../contrib/chat/browser/chatWidget.ts | 19 +- .../contrib/chat/browser/media/chat.css | 2 + .../browser/commandsQuickAccess.ts | 2 +- 10 files changed, 627 insertions(+), 286 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts create mode 100644 src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts create mode 100644 src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index e982980b2e0..e03b39894f3 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -3,284 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from 'vs/base/browser/dom'; -import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Codicon } from 'vs/base/common/codicons'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { asCssVariable, editorBackground, foreground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ChatListItemRenderer } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; -import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { ChatViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { AskQuickQuestionAction } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction'; -export const ASK_QUICK_QUESTION_ACTION_ID = 'chat.action.askQuickQuestion'; +import 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction'; +import 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction'; export function registerChatQuickQuestionActions() { registerAction2(AskQuickQuestionAction); } - -class AskQuickQuestionAction extends Action2 { - - private _currentSession: InteractiveQuickPickSession | undefined; - private _currentQuery: string | undefined; - private _lastAcceptedQuery: string | undefined; - private _currentTimer: any | undefined; - private _input: IQuickPick | undefined; - - constructor() { - super({ - id: ASK_QUICK_QUESTION_ACTION_ID, - title: { value: localize('askQuickQuestion', "Ask Quick Question"), original: 'Ask Quick Question' }, - precondition: CONTEXT_PROVIDER_EXISTS, - f1: false, - category: CHAT_CATEGORY, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, - linux: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyI - } - } - }); - } - - run(accessor: ServicesAccessor, query: string): void { - const quickInputService = accessor.get(IQuickInputService); - const chatService = accessor.get(IChatService); - const instantiationService = accessor.get(IInstantiationService); - - // First things first, clear the existing timer that will dispose the session - clearTimeout(this._currentTimer); - this._currentTimer = undefined; - - // If the input is already shown, hide it. This provides a toggle behavior of the quick pick - if (this._input !== undefined) { - this._input.hide(); - return; - } - - // Check if any providers are available. If not, show nothing - const providerInfo = chatService.getProviderInfos()[0]; - if (!providerInfo) { - return; - } - - const disposableStore = new DisposableStore(); - - //#region Setup quick pick - - this._input = quickInputService.createQuickPick(); - disposableStore.add(this._input); - this._input.placeholder = localize('askabot', "Ask {0} a question...", providerInfo.displayName); - - // Setup toggle that will be used to open the chat view - const openInChat = new Toggle({ - title: 'Open in chat view', - icon: Codicon.commentDiscussion, - isChecked: false, - inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), - inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), - inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground) - }); - disposableStore.add(openInChat); - disposableStore.add(openInChat.onChange(async () => { - await this._currentSession?.openInChat(this._lastAcceptedQuery ?? this._input!.value); - this._currentQuery = undefined; - this._lastAcceptedQuery = undefined; - this._currentSession?.dispose(); - this._currentSession = undefined; - })); - this._input.toggles = [openInChat]; - - // Setup the widget that will be used to render the chat response - const containerList = dom.$('.interactive-list'); - const containerSession = dom.$('.interactive-session', undefined, containerList); - containerList.style.position = 'relative'; - this._input.widget = containerSession; - - //#endregion - - //#region quick pick events - - disposableStore.add(this._input.onDidChangeValue((value) => { - if (value !== this._currentQuery) { - this._currentQuery = value; - } - })); - disposableStore.add(this._input.onDidHide(() => { - disposableStore.dispose(); - this._input = undefined; - this._currentTimer = setTimeout(() => { - this._currentQuery = undefined; - this._lastAcceptedQuery = undefined; - this._currentSession?.dispose(); - this._currentSession = undefined; - }, 1000 * 30); // 30 seconds - })); - disposableStore.add(this._input.onDidAccept(async () => { - await this._currentSession?.accept(this._input!.value); - this._lastAcceptedQuery = this._input!.value; - })); - - //#endregion - - // If we were given a query (via executeCommand), then clear past state - if (query) { - this._currentSession?.dispose(); - this._currentSession = undefined; - } - this._currentSession ??= instantiationService.createInstance(InteractiveQuickPickSession); - this._input.show(); - // This line must come after showing the input so the offsetWidth is correct - this._currentSession.createList(containerList, containerList.offsetWidth); - - disposableStore.add(this._currentSession.onDidClickFollowup(async e => { - this._input!.focusOnInput(); - this._input!.value = e.message; - await this._currentSession?.accept(e.message); - })); - - // If we were given a query (via executeCommand), then accept it - if (query) { - this._input.value = query; - this._input.valueSelection = [0, this._input.value.length]; - this._currentQuery = query; - this._currentSession.accept(query); - } else if (this._currentQuery) { - this._input.value = this._currentQuery; - this._input.valueSelection = [0, this._input.value.length]; - } - } -} - -class InteractiveQuickPickSession extends Disposable { - - private _model: ChatModel; - private _viewModel: ChatViewModel; - - private readonly _onDidClickFollowup: Emitter = this._register(new Emitter()); - onDidClickFollowup: Event = this._onDidClickFollowup.event; - - private _listDisposable: DisposableStore | undefined; - - constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IChatService private readonly _chatService: IChatService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService - ) { - super(); - - const providerInfo = _chatService.getProviderInfos()[0]; - this._model = this._register(_chatService.startSession(providerInfo.id, CancellationToken.None)!); - this._viewModel = this._register(new ChatViewModel(this._model, _instantiationService)); - } - - createList(container: HTMLElement, offsetWidth: number) { - this._listDisposable?.dispose(); - this._listDisposable = new DisposableStore(); - const options = this._listDisposable.add(this._instantiationService.createInstance(ChatEditorOptions, 'quickpick-interactive', foreground, editorBackground, editorBackground)); - const list = this._listDisposable.add(this._instantiationService.createInstance( - ChatListItemRenderer, - options, - { - getListLength: () => { - return 1; - }, - getSlashCommands() { - return []; - }, - } - )); - - const template = list.renderTemplate(container); - list.layout(offsetWidth); - this._listDisposable.add(this._viewModel.onDidChange(() => { - const items = this._viewModel.getItems(); - const node = { - element: items[items.length - 1], - children: [], - collapsed: false, - collapsible: false, - depth: 0, - filterData: undefined, - visible: true, - visibleChildIndex: 0, - visibleChildrenCount: 1, - }; - list.disposeElement(node, 0, template); - list.renderElement(node, 0, template); - })); - - if (this._viewModel.getItems().length) { - const items = this._viewModel.getItems(); - const node = { - element: items[items.length - 1], - children: [], - collapsed: false, - collapsible: false, - depth: 0, - filterData: undefined, - visible: true, - visibleChildIndex: 0, - visibleChildrenCount: 1, - }; - list.disposeElement(node, 0, template); - list.renderElement(node, 0, template); - } - - this._listDisposable.add(list.onDidClickFollowup(e => { - this._onDidClickFollowup.fire(e); - })); - } - - get providerId() { - return this._model.providerId; - } - - get sessionId() { - return this._model.sessionId; - } - - async accept(query: string) { - await this._model.waitForInitialization(); - const requests = this._model.getRequests(); - const lastRequest = requests[requests.length - 1]; - if (lastRequest?.message && lastRequest?.message === query) { - return; - } - if (this._model.requestInProgress) { - this._chatService.cancelCurrentRequestForSession(this.sessionId); - } - await this._chatService.sendRequest(this.sessionId, query); - } - - async openInChat(value: string) { - const widget = await this._chatWidgetService.revealViewForProvider(this.providerId); - if (!widget?.viewModel) { - return; - } - - const requests = this._model.getRequests().reverse(); - const response = requests.find(r => r.response?.response.value !== undefined); - const message = response?.response?.response.value; - if (message) { - this._chatService.addCompleteRequest(widget.viewModel.sessionId, value, { message }); - } else if (value) { - this._chatService.sendRequest(widget.viewModel.sessionId, value); - } - widget.focusInput(); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts new file mode 100644 index 00000000000..095b8c2df8f --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts @@ -0,0 +1,267 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Codicon } from 'vs/base/common/codicons'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { localize } from 'vs/nls'; +import { IContextKeyService, IScopedContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { editorForeground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { AskQuickQuestionAction, IQuickQuestionMode, QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction'; +import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane'; +import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; + +class BaseChatQuickQuestionMode implements IQuickQuestionMode { + private _currentTimer: any | undefined; + private _input: IQuickPick | undefined; + private _currentChat: QuickChat | undefined; + + constructor( + private readonly renderInputOnTop: boolean + ) { } + + run(accessor: ServicesAccessor, query: string): void { + const quickInputService = accessor.get(IQuickInputService); + const chatService = accessor.get(IChatService); + const instantiationService = accessor.get(IInstantiationService); + + // First things first, clear the existing timer that will dispose the session + clearTimeout(this._currentTimer); + this._currentTimer = undefined; + + // If the input is already shown, hide it. This provides a toggle behavior of the quick pick + if (this._input !== undefined) { + this._input.hide(); + return; + } + + // Check if any providers are available. If not, show nothing + // This shouldn't be needed because of the precondition, but just in case + const providerInfo = chatService.getProviderInfos()[0]; + if (!providerInfo) { + return; + } + + const disposableStore = new DisposableStore(); + + //#region Setup quick pick + + this._input = quickInputService.createQuickPick(); + disposableStore.add(this._input); + this._input.hideInput = true; + + + const containerList = dom.$('.interactive-list'); + const containerSession = dom.$('.interactive-session', undefined, containerList); + containerSession.style.height = '500px'; + containerList.style.position = 'relative'; + this._input.widget = containerSession; + + const clearButton = { + iconClass: ThemeIcon.asClassName(Codicon.clearAll), + tooltip: localize('clear', "Clear"), + }; + this._input.buttons = [ + clearButton, + { + iconClass: ThemeIcon.asClassName(Codicon.commentDiscussion), + tooltip: localize('openInChat', "Open in chat view"), + } + ]; + this._input.title = providerInfo.displayName; + + disposableStore.add(this._input.onDidHide(() => { + disposableStore.dispose(); + this._input = undefined; + this._currentTimer = setTimeout(() => { + this._currentChat?.dispose(); + this._currentChat = undefined; + }, 1000 * 30); // 30 seconds + })); + + //#endregion + + this._currentChat ??= instantiationService.createInstance(QuickChat, { + providerId: providerInfo.id, + renderInputOnTop: this.renderInputOnTop, + }); + this._currentChat.render(containerSession); + + disposableStore.add(this._input.onDidAccept(() => { + this._currentChat?.acceptInput(); + })); + + disposableStore.add(this._input.onDidTriggerButton((e) => { + if (e === clearButton) { + this._currentChat?.clear(); + } else { + this._currentChat?.openChatView(); + } + })); + + this._input.show(); + this._currentChat.layout(); + this._currentChat.focus(); + + if (query) { + this._currentChat.setValue(query); + this._currentChat.acceptInput(); + } + } +} + +class QuickChat extends Disposable { + private widget!: ChatWidget; + private model: ChatModel | undefined; + private _currentQuery: string | undefined; + + private _scopedContextKeyService!: IScopedContextKeyService; + get scopedContextKeyService() { + return this._scopedContextKeyService; + } + + private _currentParentElement?: HTMLElement; + + constructor( + private readonly chatViewOptions: IChatViewOptions & { renderInputOnTop: boolean }, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IChatService private readonly chatService: IChatService, + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService + ) { + super(); + } + + clear() { + this.model?.dispose(); + this.model = undefined; + this.updateModel(); + this.widget.inputEditor.setValue(''); + } + + focus(): void { + if (this.widget) { + this.widget.focusInput(); + } + } + + render(parent: HTMLElement): void { + this.widget?.dispose(); + this._currentParentElement = parent; + this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); + const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); + this.widget = this._register( + scopedInstantiationService.createInstance( + ChatWidget, + { resource: true, renderInputOnTop: this.chatViewOptions.renderInputOnTop }, + { + listForeground: editorForeground, + listBackground: editorBackground, + inputEditorBackground: SIDE_BAR_BACKGROUND, + resultEditorBackground: SIDE_BAR_BACKGROUND + })); + this.widget.render(parent); + this.widget.setVisible(true); + this.updateModel(); + if (this._currentQuery) { + this.widget.inputEditor.setSelection({ + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: this._currentQuery.length + 1 + }); + } + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(dom.addDisposableListener(parent, dom.EventType.RESIZE, () => this.layout())); + this._register(this.widget.inputEditor.onDidChangeModelContent((e) => { + this._currentQuery = this.widget.inputEditor.getValue(); + })); + } + + async acceptInput(): Promise { + if (this.widget.inputEditor.getValue().trim() === '/clear') { + this.clear(); + } else { + await this.widget.acceptInput(); + } + this.layout(); + } + + async openChatView(): Promise { + const widget = await this._chatWidgetService.revealViewForProvider(this.chatViewOptions.providerId); + if (!widget?.viewModel || !this.model) { + return; + } + + for (const request of this.model.getRequests()) { + if (request.response?.response.value || request.response?.errorDetails) { + this.chatService.addCompleteRequest(widget.viewModel.sessionId, + request.message as string, + { + message: request.response.response.value, + errorDetails: request.response.errorDetails + }); + } else if (request.message) { + + } + } + + const value = this.widget.inputEditor.getValue(); + if (value) { + widget.inputEditor.setValue(value); + } + widget.focusInput(); + } + + setValue(value: string): void { + this.widget.inputEditor.setValue(value); + } + + layout(): void { + if (this._currentParentElement) { + this.widget.layout(500, this._currentParentElement.offsetWidth); + } + } + + private updateModel(): void { + this.model ??= this.chatService.startSession(this.chatViewOptions.providerId, CancellationToken.None); + if (!this.model) { + throw new Error('Could not start chat session'); + } + + this.widget.setModel(this.model, { inputValue: this._currentQuery }); + } +} + +AskQuickQuestionAction.registerMode( + QuickQuestionMode.InputOnTopChat, + class InputOnTopChatQuickQuestionMode extends BaseChatQuickQuestionMode { + constructor() { + super(true); + } + } +); + +AskQuickQuestionAction.registerMode( + QuickQuestionMode.InputOnBottomChat, + class InputOnBottomChatQuickQuestionMode extends BaseChatQuickQuestionMode { + constructor() { + super(false); + } + } +); diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts new file mode 100644 index 00000000000..55d1b8faab9 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Lazy } from 'vs/base/common/lazy'; +import { localize } from 'vs/nls'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; +import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; + +export const ASK_QUICK_QUESTION_ACTION_ID = 'chat.action.askQuickQuestion'; + +export const enum QuickQuestionMode { + SingleQuestion = 'singleQuestion', + InputOnTopChat = 'inputOnTopChat', + InputOnBottomChat = 'inputOnBottomChat', +} + +export interface IQuickQuestionMode { + run(accessor: ServicesAccessor, query: string): void; +} + +export class AskQuickQuestionAction extends Action2 { + + private static readonly modeRegistry: Map> = new Map(); + static registerMode(mode: QuickQuestionMode, modeAction: { new(): IQuickQuestionMode }) { + AskQuickQuestionAction.modeRegistry.set(mode, new Lazy(() => new modeAction())); + } + + constructor() { + super({ + id: ASK_QUICK_QUESTION_ACTION_ID, + title: { value: localize('askQuickQuestion', "Ask Quick Question"), original: 'Ask Quick Question' }, + precondition: CONTEXT_PROVIDER_EXISTS, + f1: false, + category: CHAT_CATEGORY, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, + linux: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyI + } + }, + }); + } + + override run(accessor: ServicesAccessor, query: string): void { + const configurationService = accessor.get(IConfigurationService); + + const mode = configurationService.getValue('chat.experimental.quickQuestion.mode'); + const modeAction = AskQuickQuestionAction.modeRegistry.get(mode); + if (modeAction) { + modeAction.value.run(accessor, query); + } + } +} diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts new file mode 100644 index 00000000000..b436449a51a --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Codicon } from 'vs/base/common/codicons'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { localize } from 'vs/nls'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { asCssVariable, editorBackground, foreground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { ChatListItemRenderer } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; +import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; +import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { AskQuickQuestionAction, IQuickQuestionMode, QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction'; + +class AskSingleQuickQuestionMode implements IQuickQuestionMode { + + private _currentSession: InteractiveQuickPickSession | undefined; + private _currentQuery: string | undefined; + private _lastAcceptedQuery: string | undefined; + private _currentTimer: any | undefined; + private _input: IQuickPick | undefined; + + run(accessor: ServicesAccessor, query: string): void { + const quickInputService = accessor.get(IQuickInputService); + const chatService = accessor.get(IChatService); + const instantiationService = accessor.get(IInstantiationService); + + // First things first, clear the existing timer that will dispose the session + clearTimeout(this._currentTimer); + this._currentTimer = undefined; + + // If the input is already shown, hide it. This provides a toggle behavior of the quick pick + if (this._input !== undefined) { + this._input.hide(); + return; + } + + // Check if any providers are available. If not, show nothing + const providerInfo = chatService.getProviderInfos()[0]; + if (!providerInfo) { + return; + } + + const disposableStore = new DisposableStore(); + + //#region Setup quick pick + + this._input = quickInputService.createQuickPick(); + disposableStore.add(this._input); + this._input.placeholder = localize('askabot', "Ask {0} a question...", providerInfo.displayName); + + // Setup toggle that will be used to open the chat view + const openInChat = new Toggle({ + title: 'Open in chat view', + icon: Codicon.commentDiscussion, + isChecked: false, + inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), + inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), + inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground) + }); + disposableStore.add(openInChat); + disposableStore.add(openInChat.onChange(async () => { + await this._currentSession?.openInChat(this._lastAcceptedQuery ?? this._input!.value); + this._currentQuery = undefined; + this._lastAcceptedQuery = undefined; + this._currentSession?.dispose(); + this._currentSession = undefined; + })); + this._input.toggles = [openInChat]; + + // Setup the widget that will be used to render the chat response + const containerList = dom.$('.interactive-list'); + const containerSession = dom.$('.interactive-session', undefined, containerList); + containerList.style.position = 'relative'; + this._input.widget = containerSession; + + //#endregion + + //#region quick pick events + + disposableStore.add(this._input.onDidChangeValue((value) => { + if (value !== this._currentQuery) { + this._currentQuery = value; + } + })); + disposableStore.add(this._input.onDidHide(() => { + disposableStore.dispose(); + this._input = undefined; + this._currentTimer = setTimeout(() => { + this._currentQuery = undefined; + this._lastAcceptedQuery = undefined; + this._currentSession?.dispose(); + this._currentSession = undefined; + }, 1000 * 30); // 30 seconds + })); + disposableStore.add(this._input.onDidAccept(async () => { + await this._currentSession?.accept(this._input!.value); + this._lastAcceptedQuery = this._input!.value; + })); + + //#endregion + + // If we were given a query (via executeCommand), then clear past state + if (query) { + this._currentSession?.dispose(); + this._currentSession = undefined; + } + this._currentSession ??= instantiationService.createInstance(InteractiveQuickPickSession); + this._input.show(); + // This line must come after showing the input so the offsetWidth is correct + this._currentSession.createList(containerList, containerList.offsetWidth); + + disposableStore.add(this._currentSession.onDidClickFollowup(async e => { + this._input!.focusOnInput(); + this._input!.value = e.message; + await this._currentSession?.accept(e.message); + })); + + // If we were given a query (via executeCommand), then accept it + if (query) { + this._input.value = query; + this._input.valueSelection = [0, this._input.value.length]; + this._currentQuery = query; + this._currentSession.accept(query); + } else if (this._currentQuery) { + this._input.value = this._currentQuery; + this._input.valueSelection = [0, this._input.value.length]; + } + } +} + +class InteractiveQuickPickSession extends Disposable { + + private _model: ChatModel; + private _viewModel: ChatViewModel; + + private readonly _onDidClickFollowup: Emitter = this._register(new Emitter()); + onDidClickFollowup: Event = this._onDidClickFollowup.event; + + private _listDisposable: DisposableStore | undefined; + + constructor( + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IChatService private readonly _chatService: IChatService, + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService + ) { + super(); + + const providerInfo = _chatService.getProviderInfos()[0]; + this._model = this._register(_chatService.startSession(providerInfo.id, CancellationToken.None)!); + this._viewModel = this._register(new ChatViewModel(this._model, _instantiationService)); + } + + createList(container: HTMLElement, offsetWidth: number) { + this._listDisposable?.dispose(); + this._listDisposable = new DisposableStore(); + const options = this._listDisposable.add(this._instantiationService.createInstance(ChatEditorOptions, 'quickpick-interactive', foreground, editorBackground, editorBackground)); + const list = this._listDisposable.add(this._instantiationService.createInstance( + ChatListItemRenderer, + options, + { + getListLength: () => { + return 1; + }, + getSlashCommands() { + return []; + }, + } + )); + + const template = list.renderTemplate(container); + list.layout(offsetWidth); + this._listDisposable.add(this._viewModel.onDidChange(() => { + const items = this._viewModel.getItems(); + const node = { + element: items[items.length - 1], + children: [], + collapsed: false, + collapsible: false, + depth: 0, + filterData: undefined, + visible: true, + visibleChildIndex: 0, + visibleChildrenCount: 1, + }; + list.disposeElement(node, 0, template); + list.renderElement(node, 0, template); + })); + + if (this._viewModel.getItems().length) { + const items = this._viewModel.getItems(); + const node = { + element: items[items.length - 1], + children: [], + collapsed: false, + collapsible: false, + depth: 0, + filterData: undefined, + visible: true, + visibleChildIndex: 0, + visibleChildrenCount: 1, + }; + list.disposeElement(node, 0, template); + list.renderElement(node, 0, template); + } + + this._listDisposable.add(list.onDidClickFollowup(e => { + this._onDidClickFollowup.fire(e); + })); + } + + get providerId() { + return this._model.providerId; + } + + get sessionId() { + return this._model.sessionId; + } + + async accept(query: string) { + await this._model.waitForInitialization(); + const requests = this._model.getRequests(); + const lastRequest = requests[requests.length - 1]; + if (lastRequest?.message && lastRequest?.message === query) { + return; + } + if (this._model.requestInProgress) { + this._chatService.cancelCurrentRequestForSession(this.sessionId); + } + await this._chatService.sendRequest(this.sessionId, query); + } + + async openInChat(value: string) { + const widget = await this._chatWidgetService.revealViewForProvider(this.providerId); + if (!widget?.viewModel) { + return; + } + + const requests = this._model.getRequests().reverse(); + const response = requests.find(r => r.response?.response.value !== undefined); + const message = response?.response?.response.value; + if (message) { + this._chatService.addCompleteRequest(widget.viewModel.sessionId, value, { message }); + } else if (value) { + this._chatService.sendRequest(widget.viewModel.sessionId, value); + } + widget.focusInput(); + } +} + +AskQuickQuestionAction.registerMode(QuickQuestionMode.SingleQuestion, AskSingleQuickQuestionMode); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index aef372dee92..656485ed70a 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -43,6 +43,7 @@ import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -76,6 +77,13 @@ configurationRegistry.registerConfiguration({ type: 'number', description: nls.localize('interactiveSession.editor.lineHeight', "Controls the line height in pixels in chat codeblocks. Use 0 to compute the line height from the font size."), default: 0 + }, + 'chat.experimental.quickQuestion.mode': { + type: 'string', + tags: ['experimental'], + enum: [QuickQuestionMode.SingleQuestion, QuickQuestionMode.InputOnTopChat, QuickQuestionMode.InputOnBottomChat], + description: nls.localize('interactiveSession.quickQuestion.mode', "Controls the mode of quick question chat experience."), + default: QuickQuestionMode.SingleQuestion, } } }); diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index b42cad92d7e..996a999286c 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -45,7 +45,7 @@ export interface IChatCodeBlockInfo { export type ChatTreeItem = IChatRequestViewModel | IChatResponseViewModel | IChatWelcomeMessageViewModel; -export type IChatWidgetViewContext = { viewId: string } | { resource: boolean }; +export type IChatWidgetViewContext = { viewId: string; renderInputOnTop?: false } | { resource: boolean; renderInputOnTop?: boolean }; export interface IChatWidget { readonly onDidChangeViewModel: Event; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index d41f490038d..92af40f0f16 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -77,6 +77,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge constructor( // private readonly editorOptions: ChatEditorOptions, // TODO this should be used + private readonly options: { renderFollowupsBelow: boolean }, @IChatWidgetHistoryService private readonly historyService: IChatWidgetHistoryService, @IModelService private readonly modelService: IModelService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -176,9 +177,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge render(container: HTMLElement, initialValue: string, widget: IChatWidget) { this.container = dom.append(container, $('.interactive-input-part')); - this.followupsContainer = dom.append(this.container, $('.interactive-input-followups')); - const inputContainer = dom.append(this.container, $('.interactive-input-and-toolbar')); + let inputContainer: HTMLElement; + if (this.options.renderFollowupsBelow) { + inputContainer = dom.append(this.container, $('.interactive-input-and-toolbar')); + this.followupsContainer = dom.append(this.container, $('.interactive-input-followups')); + } else { + this.followupsContainer = dom.append(this.container, $('.interactive-input-followups')); + inputContainer = dom.append(this.container, $('.interactive-input-and-toolbar')); + } const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(inputContainer)); CONTEXT_IN_CHAT_INPUT.bindTo(inputScopedContextKeyService).set(true); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index fa29aac0229..c40f97ce4dc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -136,13 +136,20 @@ export class ChatWidget extends Disposable implements IChatWidget { } render(parent: HTMLElement): void { - this.container = dom.append(parent, $('.interactive-session')); - this.listContainer = dom.append(this.container, $(`.interactive-list`)); - const viewId = 'viewId' in this.viewContext ? this.viewContext.viewId : undefined; this.editorOptions = this._register(this.instantiationService.createInstance(ChatEditorOptions, viewId, this.styles.listForeground, this.styles.inputEditorBackground, this.styles.resultEditorBackground)); + const renderInputOnTop = this.viewContext.renderInputOnTop ?? false; + + this.container = dom.append(parent, $('.interactive-session')); + if (renderInputOnTop) { + this.createInput(this.container, { renderFollowupsBelow: true }); + this.listContainer = dom.append(this.container, $(`.interactive-list`)); + } else { + this.listContainer = dom.append(this.container, $(`.interactive-list`)); + this.createInput(this.container); + } + this.createList(this.listContainer); - this.createInput(this.container); this._register(this.editorOptions.onDidChange(() => this.onDidStyleChange())); this.onDidStyleChange(); @@ -327,8 +334,8 @@ export class ChatWidget extends Disposable implements IChatWidget { this.previousTreeScrollHeight = this.tree.scrollHeight; } - private createInput(container: HTMLElement): void { - this.inputPart = this.instantiationService.createInstance(ChatInputPart); + private createInput(container: HTMLElement, options?: { renderFollowupsBelow: boolean }): void { + this.inputPart = this.instantiationService.createInstance(ChatInputPart, { renderFollowupsBelow: options?.renderFollowupsBelow ?? false }); this.inputPart.render(container, '', this); this._register(this.inputPart.onDidFocus(() => this._onDidFocus.fire())); diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 75a3d125a98..e0ec10e9d5e 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -190,6 +190,7 @@ border-radius: 4px; position: relative; padding-left: 8px; + margin-bottom: 4px; } .interactive-session .interactive-input-and-toolbar.focused { @@ -298,6 +299,7 @@ display: flex; flex-direction: column; border-top: solid 1px var(--vscode-chat-requestBorder); + border-bottom: solid 1px var(--vscode-chat-requestBorder); } .interactive-session-followups { diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index dd837a29ec7..328d2745024 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -35,7 +35,7 @@ import { isFirefox } from 'vs/base/browser/browser'; import { IProductService } from 'vs/platform/product/common/productService'; import { ISemanticSimilarityService } from 'vs/workbench/services/semanticSimilarity/common/semanticSimilarityService'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { ASK_QUICK_QUESTION_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; +import { ASK_QUICK_QUESTION_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction'; export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { From 29f02712936e3edcec5f053d7361a9b88b2b8570 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 13 Jul 2023 10:41:06 -0700 Subject: [PATCH 110/826] accessible view for notifications --- .../accessibility}/accessibleView.ts | 36 +++++++++++++++++-- .../parts/notifications/notificationsList.ts | 16 +++++++-- .../browser/accessibility.contribution.ts | 2 +- .../browser/actions/chatAccessibilityHelp.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 2 +- .../browser/accessibility/accessibility.ts | 2 +- .../codeEditor/browser/diffEditorHelper.ts | 2 +- .../browser/notebookAccessibilityHelp.ts | 2 +- .../terminal.accessibility.contribution.ts | 2 +- .../browser/terminalAccessibilityHelp.ts | 2 +- 10 files changed, 55 insertions(+), 13 deletions(-) rename src/vs/workbench/{contrib/accessibility/browser => browser/accessibility}/accessibleView.ts (90%) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/browser/accessibility/accessibleView.ts similarity index 90% rename from src/vs/workbench/contrib/accessibility/browser/accessibleView.ts rename to src/vs/workbench/browser/accessibility/accessibleView.ts index 8754d601121..3be0b4add75 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/browser/accessibility/accessibleView.ts @@ -22,9 +22,8 @@ import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/cont import { IContextViewDelegate, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; -import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { alert } from 'vs/base/browser/ui/aria/aria'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; const enum DEFAULT { WIDTH = 800, @@ -80,7 +79,7 @@ class AccessibleView extends Disposable { this._editorContainer = document.createElement('div'); this._editorContainer.classList.add('accessible-view'); const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { - contributions: EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, SelectionClipboardContributionID, 'editor.contrib.selectionAnchorController']) + contributions: EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, 'editor.contrib.selectionClipboard', 'editor.contrib.selectionAnchorController']) }; const editorOptions: IEditorConstructionOptions = { ...getSimpleEditorOptions(this._configurationService), @@ -226,3 +225,34 @@ export class AccessibleViewService extends Disposable implements IAccessibleView this._accessibleView.show(provider); } } +function getSimpleEditorOptions(configurationService: IConfigurationService): IEditorOptions { + return { + wordWrap: 'on', + overviewRulerLanes: 0, + glyphMargin: false, + lineNumbers: 'off', + folding: false, + selectOnLineNumbers: false, + hideCursorInOverviewRuler: true, + selectionHighlight: false, + scrollbar: { + horizontal: 'hidden' + }, + lineDecorationsWidth: 0, + overviewRulerBorder: false, + scrollBeyondLastLine: false, + renderLineHighlight: 'none', + fixedOverflowWidgets: true, + acceptSuggestionOnEnter: 'smart', + dragAndDrop: false, + revealHorizontalRightPadding: 5, + minimap: { + enabled: false + }, + guides: { + indentation: false + }, + accessibilitySupport: configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'), + cursorBlinking: configurationService.getValue<'blink' | 'smooth' | 'phase' | 'expand' | 'solid'>('editor.cursorBlinking') + }; +} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index f54137e9371..48584abc524 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -19,6 +19,7 @@ import { NotificationFocusedContext } from 'vs/workbench/common/contextkeys'; import { Disposable } from 'vs/base/common/lifecycle'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; export interface INotificationsListOptions extends IListOptions { readonly widgetAriaLabel?: string; @@ -36,7 +37,8 @@ export class NotificationsList extends Disposable { private readonly container: HTMLElement, private readonly options: INotificationsListOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IContextMenuService private readonly contextMenuService: IContextMenuService + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IAccessibleViewService private readonly accessibleViewService: IAccessibleViewService ) { super(); } @@ -161,7 +163,17 @@ export class NotificationsList extends Disposable { // Remember focus and relative top of that item const focusedIndex = list.getFocus()[0]; const focusedItem = this.viewModel[focusedIndex]; - + this.accessibleViewService.show({ + provideContent: () => { return focusedItem.message.original.toString() || ''; }, + onClose(): void { + list.setFocus([focusedIndex]); + }, + verbositySettingKey: 'notifications', + options: { + ariaLabel: localize('notification', "Notification Accessible View"), + type: AccessibleViewType.View + } + }); let focusRelativeTop: number | null = null; if (typeof focusedIndex === 'number') { focusRelativeTop = list.getRelativeTop(focusedIndex); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index d391b388ac6..e4b747f5e02 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -13,7 +13,7 @@ import { localize } from 'vs/nls'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilityHelpAction, AccessibleViewAction, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; -import { AccessibleViewService, AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewService, AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; import * as strings from 'vs/base/common/strings'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 10a34e44a6a..83892d5fb48 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -10,7 +10,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 656485ed70a..4f362ccf834 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -38,7 +38,7 @@ import '../common/chatColors'; import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; import { registerClearActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 903406a2947..b39ed1353f8 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -9,7 +9,7 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { accessibilityHelpIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { accessibilityHelpIsShown } from 'vs/workbench/browser/accessibility/accessibleView'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { alert } from 'vs/base/browser/ui/aria/aria'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 7414c5b26a5..5da8615c51c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -20,7 +20,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const enum WidgetState { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts index 717b6167614..5e45789ccb1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts @@ -8,7 +8,7 @@ import { format } from 'vs/base/common/strings'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index 26042ec1cb1..baf38d2541b 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -13,7 +13,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { terminalTabFocusModeContextKey } from 'vs/platform/terminal/common/terminal'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; -import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts index d5d73c308a1..318b912982d 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts @@ -10,7 +10,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ShellIntegrationStatus, WindowsShellType } from 'vs/platform/terminal/common/terminal'; -import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/browser/accessibility/accessibleView'; import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal } from 'xterm'; From 092a43cfaa667e3739e06e5c52519d43ec658172 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 13 Jul 2023 19:54:37 +0200 Subject: [PATCH 111/826] Merge profile name and configuration pickers (#187862) --- .../quickinput/browser/media/quickInput.css | 2 +- .../platform/quickinput/browser/quickInput.ts | 14 +++++- .../platform/quickinput/common/quickInput.ts | 7 +++ .../browser/userDataProfile.ts | 49 +++++++++++++------ 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/quickinput/browser/media/quickInput.css b/src/vs/platform/quickinput/browser/media/quickInput.css index de8df301e3c..9bb877f9991 100644 --- a/src/vs/platform/quickinput/browser/media/quickInput.css +++ b/src/vs/platform/quickinput/browser/media/quickInput.css @@ -50,7 +50,7 @@ } .quick-input-description { - margin: 6px; + margin: 6px 6px 6px 11px; } .quick-input-header .quick-input-description { diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 7992328ec1f..f9829e3d98f 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -537,6 +537,7 @@ class QuickPick extends QuickInput implements IQuickPi private _customButtonHover: string | undefined; private _quickNavigate: IQuickNavigateConfiguration | undefined; private _hideInput: boolean | undefined; + private _hideCountBadge: boolean | undefined; private _hideCheckAll: boolean | undefined; get quickNavigate() { @@ -796,6 +797,15 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + get hideCountBadge() { + return !!this._hideCountBadge; + } + + set hideCountBadge(hideCountBadge: boolean) { + this._hideCountBadge = hideCountBadge; + this.update(); + } + get hideCheckAll() { return !!this._hideCheckAll; } @@ -1038,7 +1048,7 @@ class QuickPick extends QuickInput implements IQuickPi inputBox: !this._hideInput, progressBar: !this._hideInput || hasDescription, visibleCount: true, - count: this.canSelectMany, + count: this.canSelectMany && !this._hideCountBadge, ok: this.ok === 'default' ? this.canSelectMany : this.ok, list: true, message: !!this.validationMessage, @@ -1315,8 +1325,8 @@ export class QuickInputController extends Disposable { const rightActionBar = this._register(new ActionBar(titleBar)); rightActionBar.domNode.classList.add('quick-input-right-action-bar'); - const description1 = dom.append(container, $('.quick-input-description')); const headerContainer = dom.append(container, $('.quick-input-header')); + const description1 = dom.append(container, $('.quick-input-description')); const checkAll = dom.append(headerContainer, $('input.quick-input-check-all')); checkAll.type = 'checkbox'; diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 00c4a4d28b5..8106d170870 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -351,6 +351,8 @@ export interface IQuickPick extends IQuickInput { validationMessage: string | undefined; + severity: Severity; + inputHasFocus(): boolean; focusOnInput(): void; @@ -362,6 +364,11 @@ export interface IQuickPick extends IQuickInput { */ hideInput: boolean; + /** + * Allows to control if the count for the items should be shown + */ + hideCountBadge: boolean; + hideCheckAll: boolean; /** diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 5ea18399b20..9b2002bdfe2 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -30,6 +30,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; +import Severity from 'vs/base/common/severity'; const CREATE_EMPTY_PROFILE_ACTION_ID = 'workbench.profiles.actions.createEmptyProfile'; const CREATE_EMPTY_PROFILE_ACTION_TITLE = { @@ -500,14 +501,19 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const quickPick = this.quickInputService.createQuickPick(); quickPick.title = title; - quickPick.hideInput = true; + quickPick.placeholder = localize('name placeholder', "Name the new profile"); quickPick.canSelectMany = true; + quickPick.matchOnDescription = false; + quickPick.matchOnDetail = false; + quickPick.matchOnLabel = false; + quickPick.sortByLabel = false; + quickPick.hideCountBadge = true; quickPick.ok = false; quickPick.customButton = true; quickPick.hideCheckAll = true; quickPick.ignoreFocusOut = true; quickPick.customLabel = localize('create', "Create Profile"); - quickPick.description = localize('customise the profile', "Choose what you want to configure in the profile. Unselected items are shared from the default profile."); + quickPick.description = localize('customise the profile', "Choose what to configure in the profile. Unselected items are shared from the default profile."); quickPick.items = resources; quickPick.selectedItems = resources.filter(item => item.picked); @@ -518,12 +524,29 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } })); - let result: ReadonlyArray | undefined; + disposables.add(quickPick.onDidChangeValue(value => { + if (this.userDataProfilesService.profiles.some(p => p.name === value)) { + quickPick.validationMessage = localize('profileExists', "Profile with name {0} already exists.", value); + quickPick.severity = Severity.Error; + } else { + quickPick.severity = Severity.Ignore; + quickPick.validationMessage = undefined; + } + })); + + let result: { name: string; items: ReadonlyArray } | undefined; disposables.add(quickPick.onDidCustom(async () => { + if (!quickPick.value) { + quickPick.validationMessage = localize('name required', "Provide a name for the new profile"); + quickPick.severity = Severity.Error; + return; + } if (resources.some(resource => quickPick.selectedItems.includes(resource))) { - result = quickPick.selectedItems; + result = { name: quickPick.value, items: quickPick.selectedItems }; quickPick.hide(); } + quickPick.severity = Severity.Ignore; + quickPick.validationMessage = undefined; })); quickPick.show(); @@ -538,18 +561,14 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements return; } - const name = await this.getNameForProfile(title); - if (!name) { - return; - } - + const { name, items } = result; try { - const useDefaultFlags: UseDefaultProfileFlags | undefined = result.length !== resources.length ? { - settings: !result.includes(settings), - keybindings: !result.includes(keybindings), - snippets: !result.includes(snippets), - tasks: !result.includes(tasks), - extensions: !result.includes(extensions) + const useDefaultFlags: UseDefaultProfileFlags | undefined = items.length !== resources.length ? { + settings: !items.includes(settings), + keybindings: !items.includes(keybindings), + snippets: !items.includes(snippets), + tasks: !items.includes(tasks), + extensions: !items.includes(extensions) } : undefined; await this.userDataProfileManagementService.createAndEnterProfile(name, { useDefaultFlags }); } catch (error) { From 7b17343101821bd36e9939557e2200edc8122882 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 13 Jul 2023 12:46:42 -0700 Subject: [PATCH 112/826] Always create terminal on empty terminal view show Part of #187772 --- .../contrib/terminal/browser/terminalView.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index c5b3308b7d4..f91f800c2ad 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -127,9 +127,17 @@ export class TerminalViewPane extends ViewPane { return (decorationsEnabled === 'both' || decorationsEnabled === 'gutter') && this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled); } - private _initializeTerminal() { - if (this.isBodyVisible() && this._terminalService.isProcessSupportRegistered && this._terminalService.connectionState === TerminalConnectionState.Connected && this._terminalService.restoredGroupCount === 0 && this._terminalGroupService.groups.length === 0) { - this._terminalService.createTerminal({ location: TerminalLocation.Panel }); + private _initializeTerminal(checkRestoredTerminals: boolean) { + if (this.isBodyVisible() && this._terminalService.isProcessSupportRegistered && this._terminalService.connectionState === TerminalConnectionState.Connected) { + let shouldCreate = this._terminalGroupService.groups.length === 0; + // When triggered just after reconnection, also check there are no groups that could be + // getting restored currently + if (checkRestoredTerminals) { + shouldCreate &&= this._terminalService.restoredGroupCount === 0; + } + if (shouldCreate) { + this._terminalService.createTerminal({ location: TerminalLocation.Panel }); + } } } @@ -169,7 +177,7 @@ export class TerminalViewPane extends ViewPane { if (!this._terminalService.isProcessSupportRegistered) { this._onDidChangeViewWelcomeState.fire(); } - this._initializeTerminal(); + this._initializeTerminal(false); // we don't know here whether or not it should be focused, so // defer focusing the panel to the focus() call // to prevent overriding preserveFocus for extensions @@ -181,7 +189,7 @@ export class TerminalViewPane extends ViewPane { } this._terminalGroupService.updateVisibility(); })); - this._register(this._terminalService.onDidChangeConnectionState(() => this._initializeTerminal())); + this._register(this._terminalService.onDidChangeConnectionState(() => this._initializeTerminal(true))); this.layoutBody(this._parentDomElement.offsetHeight, this._parentDomElement.offsetWidth); } From 02447a5b258275c43c6f72f70424f380a2a44837 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 13 Jul 2023 14:19:15 -0700 Subject: [PATCH 113/826] fix #187114 --- .../browser/accessibility/accessibleView.ts | 13 +++--- .../notifications/notificationsCommands.ts | 32 +++++++------- .../parts/notifications/notificationsList.ts | 16 +------ .../browser/accessibility.contribution.ts | 43 +++++++++++++++++++ 4 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/browser/accessibility/accessibleView.ts b/src/vs/workbench/browser/accessibility/accessibleView.ts index 3be0b4add75..14b3d401f80 100644 --- a/src/vs/workbench/browser/accessibility/accessibleView.ts +++ b/src/vs/workbench/browser/accessibility/accessibleView.ts @@ -170,11 +170,7 @@ class AccessibleView extends Disposable { }); const disposableStore = new DisposableStore(); disposableStore.add(this._editorWidget.onKeyUp((e) => { - if (e.keyCode === KeyCode.Escape) { - this._contextViewService.hideContextView(); - // Delay to allow the context view to hide #186514 - setTimeout(() => provider.onClose(), 100); - } else if (e.keyCode === KeyCode.KeyD && this._configurationService.getValue(settingKey)) { + if (e.keyCode === KeyCode.KeyD && this._configurationService.getValue(settingKey)) { alert(localize('disableAccessibilityHelp', '{0} accessibility verbosity is now disabled', provider.verbositySettingKey)); this._configurationService.updateValue(settingKey, false); } @@ -182,7 +178,12 @@ class AccessibleView extends Disposable { provider.onKeyDown?.(e); })); disposableStore.add(this._editorWidget.onKeyDown((e) => { - if (e.keyCode === KeyCode.KeyH && provider.options.readMoreUrl) { + if (e.keyCode === KeyCode.Escape) { + e.stopPropagation(); + this._contextViewService.hideContextView(); + // Delay to allow the context view to hide #186514 + setTimeout(() => provider.onClose(), 100); + } else if (e.keyCode === KeyCode.KeyH && provider.options.readMoreUrl) { const url: string = provider.options.readMoreUrl!; alert(AccessibilityHelpNLS.openingDocs); this._openerService.open(URI.parse(url)); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index ffe7592d9f1..71df63ebd3d 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -61,24 +61,24 @@ export interface INotificationsToastController { hide(): void; } -export function registerNotificationCommands(center: INotificationsCenterController, toasts: INotificationsToastController, model: NotificationsModel): void { - - function getNotificationFromContext(listService: IListService, context?: unknown): INotificationViewItem | undefined { - if (isNotificationViewItem(context)) { - return context; - } - - const list = listService.lastFocusedList; - if (list instanceof WorkbenchList) { - const focusedElement = list.getFocusedElements()[0]; - if (isNotificationViewItem(focusedElement)) { - return focusedElement; - } - } - - return undefined; +export function getNotificationFromContext(listService: IListService, context?: unknown): INotificationViewItem | undefined { + if (isNotificationViewItem(context)) { + return context; } + const list = listService.lastFocusedList; + if (list instanceof WorkbenchList) { + const focusedElement = list.getFocusedElements()[0]; + if (isNotificationViewItem(focusedElement)) { + return focusedElement; + } + } + + return undefined; +} + +export function registerNotificationCommands(center: INotificationsCenterController, toasts: INotificationsToastController, model: NotificationsModel): void { + // Show Notifications Cneter CommandsRegistry.registerCommand(SHOW_NOTIFICATIONS_CENTER, () => { toasts.hide(); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index 48584abc524..f54137e9371 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -19,7 +19,6 @@ import { NotificationFocusedContext } from 'vs/workbench/common/contextkeys'; import { Disposable } from 'vs/base/common/lifecycle'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; export interface INotificationsListOptions extends IListOptions { readonly widgetAriaLabel?: string; @@ -37,8 +36,7 @@ export class NotificationsList extends Disposable { private readonly container: HTMLElement, private readonly options: INotificationsListOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IAccessibleViewService private readonly accessibleViewService: IAccessibleViewService + @IContextMenuService private readonly contextMenuService: IContextMenuService ) { super(); } @@ -163,17 +161,7 @@ export class NotificationsList extends Disposable { // Remember focus and relative top of that item const focusedIndex = list.getFocus()[0]; const focusedItem = this.viewModel[focusedIndex]; - this.accessibleViewService.show({ - provideContent: () => { return focusedItem.message.original.toString() || ''; }, - onClose(): void { - list.setFocus([focusedIndex]); - }, - verbositySettingKey: 'notifications', - options: { - ariaLabel: localize('notification', "Notification Accessible View"), - type: AccessibleViewType.View - } - }); + let focusRelativeTop: number | null = null; if (typeof focusedIndex === 'number') { focusRelativeTop = list.getRelativeTop(focusedIndex); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index e4b747f5e02..0ceff52d257 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -24,6 +24,9 @@ import { NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover'; import { withNullAsUndefined } from 'vs/base/common/types'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { getNotificationFromContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; +import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; +import { NotificationFocusedContext } from 'vs/workbench/common/contextkeys'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); @@ -133,3 +136,43 @@ class HoverAccessibleViewContribution extends Disposable { const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(HoverAccessibleViewContribution, LifecyclePhase.Eventually); + +class NotificationAccessibleViewContribution extends Disposable { + static ID: 'notificationAccessibleViewContribution'; + constructor() { + super(); + this._register(AccessibleViewAction.addImplementation(90, 'notifications', accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + const listService = accessor.get(IListService); + const notification = getNotificationFromContext(listService); + if (!notification) { + return false; + } + let notificationIndex: number | undefined; + const list = listService.lastFocusedList; + if (list instanceof WorkbenchList) { + notificationIndex = list.indexOf(notification); + } + if (notificationIndex === undefined) { + return false; + } + accessibleViewService.show({ + provideContent: () => { return notification.message.original.toString() || ''; }, + onClose(): void { + if (list && notificationIndex !== undefined) { + list.domFocus(); + list.setFocus([notificationIndex]); + } + }, + verbositySettingKey: 'notifications', + options: { + ariaLabel: localize('notification', "Notification Accessible View"), + type: AccessibleViewType.View + } + }); + return true; + }, NotificationFocusedContext)); + } +} + +workbenchContributionsRegistry.registerWorkbenchContribution(NotificationAccessibleViewContribution, LifecyclePhase.Eventually); From 4284deab4203b23f2390641bc1596c2599635fbe Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 13 Jul 2023 14:59:50 -0700 Subject: [PATCH 114/826] get it close to working to navigate bw them --- .../browser/accessibility/accessibleView.ts | 2 +- .../browser/accessibility.contribution.ts | 68 +++++++++++-------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/browser/accessibility/accessibleView.ts b/src/vs/workbench/browser/accessibility/accessibleView.ts index 14b3d401f80..deb54ed4628 100644 --- a/src/vs/workbench/browser/accessibility/accessibleView.ts +++ b/src/vs/workbench/browser/accessibility/accessibleView.ts @@ -174,8 +174,8 @@ class AccessibleView extends Disposable { alert(localize('disableAccessibilityHelp', '{0} accessibility verbosity is now disabled', provider.verbositySettingKey)); this._configurationService.updateValue(settingKey, false); } - e.stopPropagation(); provider.onKeyDown?.(e); + e.stopPropagation(); })); disposableStore.add(this._editorWidget.onKeyDown((e) => { if (e.keyCode === KeyCode.Escape) { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 0ceff52d257..55244e09957 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -11,7 +11,7 @@ import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; import { localize } from 'vs/nls'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilityHelpAction, AccessibleViewAction, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { AccessibleViewService, AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; import * as strings from 'vs/base/common/strings'; @@ -27,6 +27,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { getNotificationFromContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; import { NotificationFocusedContext } from 'vs/workbench/common/contextkeys'; +import { KeyCode } from 'vs/base/common/keyCodes'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); @@ -144,33 +145,46 @@ class NotificationAccessibleViewContribution extends Disposable { this._register(AccessibleViewAction.addImplementation(90, 'notifications', accessor => { const accessibleViewService = accessor.get(IAccessibleViewService); const listService = accessor.get(IListService); - const notification = getNotificationFromContext(listService); - if (!notification) { - return false; - } - let notificationIndex: number | undefined; - const list = listService.lastFocusedList; - if (list instanceof WorkbenchList) { - notificationIndex = list.indexOf(notification); - } - if (notificationIndex === undefined) { - return false; - } - accessibleViewService.show({ - provideContent: () => { return notification.message.original.toString() || ''; }, - onClose(): void { - if (list && notificationIndex !== undefined) { - list.domFocus(); - list.setFocus([notificationIndex]); - } - }, - verbositySettingKey: 'notifications', - options: { - ariaLabel: localize('notification', "Notification Accessible View"), - type: AccessibleViewType.View + + function show(): boolean { + const notification = getNotificationFromContext(listService); + if (!notification) { + return false; } - }); - return true; + let notificationIndex: number | undefined; + const list = listService.lastFocusedList; + if (list instanceof WorkbenchList) { + notificationIndex = list.indexOf(notification); + } + if (notificationIndex === undefined) { + return false; + } + accessibleViewService.show({ + provideContent: () => { return notification.message.original.toString() || ''; }, + onClose(): void { + if (list && notificationIndex !== undefined) { + list.domFocus(); + list.setFocus([notificationIndex]); + } + }, + onKeyDown(e: IKeyboardEvent): void { + if (e.keyCode === KeyCode.DownArrow && e.altKey && e.ctrlKey) { + list?.focusNext(); + show(); + } else if (e.keyCode === KeyCode.UpArrow && e.altKey && e.ctrlKey) { + list?.focusPrevious(); + show(); + } + }, + verbositySettingKey: 'notifications', + options: { + ariaLabel: localize('notification', "Notification Accessible View"), + type: AccessibleViewType.View + } + }); + return true; + } + return show(); }, NotificationFocusedContext)); } } From 0a6f58c6bf576b85699a6316e8c7afa850d578b0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 13 Jul 2023 15:08:28 -0700 Subject: [PATCH 115/826] revert file move that did not help --- .../browser/accessibility.contribution.ts | 2 +- .../accessibility/browser}/accessibleView.ts | 36 ++----------------- 2 files changed, 4 insertions(+), 34 deletions(-) rename src/vs/workbench/{browser/accessibility => contrib/accessibility/browser}/accessibleView.ts (90%) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 55244e09957..14e0c135ccb 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -13,7 +13,6 @@ import { localize } from 'vs/nls'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilityHelpAction, AccessibleViewAction, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; -import { AccessibleViewService, AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; import * as strings from 'vs/base/common/strings'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; @@ -28,6 +27,7 @@ import { getNotificationFromContext } from 'vs/workbench/browser/parts/notificat import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; import { NotificationFocusedContext } from 'vs/workbench/common/contextkeys'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { IAccessibleViewService, AccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); diff --git a/src/vs/workbench/browser/accessibility/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts similarity index 90% rename from src/vs/workbench/browser/accessibility/accessibleView.ts rename to src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index deb54ed4628..7f67ff99e97 100644 --- a/src/vs/workbench/browser/accessibility/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -23,7 +23,8 @@ import { IContextViewDelegate, IContextViewService } from 'vs/platform/contextvi import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; +import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; const enum DEFAULT { WIDTH = 800, @@ -79,7 +80,7 @@ class AccessibleView extends Disposable { this._editorContainer = document.createElement('div'); this._editorContainer.classList.add('accessible-view'); const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { - contributions: EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, 'editor.contrib.selectionClipboard', 'editor.contrib.selectionAnchorController']) + contributions: EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, SelectionClipboardContributionID, 'editor.contrib.selectionAnchorController']) }; const editorOptions: IEditorConstructionOptions = { ...getSimpleEditorOptions(this._configurationService), @@ -226,34 +227,3 @@ export class AccessibleViewService extends Disposable implements IAccessibleView this._accessibleView.show(provider); } } -function getSimpleEditorOptions(configurationService: IConfigurationService): IEditorOptions { - return { - wordWrap: 'on', - overviewRulerLanes: 0, - glyphMargin: false, - lineNumbers: 'off', - folding: false, - selectOnLineNumbers: false, - hideCursorInOverviewRuler: true, - selectionHighlight: false, - scrollbar: { - horizontal: 'hidden' - }, - lineDecorationsWidth: 0, - overviewRulerBorder: false, - scrollBeyondLastLine: false, - renderLineHighlight: 'none', - fixedOverflowWidgets: true, - acceptSuggestionOnEnter: 'smart', - dragAndDrop: false, - revealHorizontalRightPadding: 5, - minimap: { - enabled: false - }, - guides: { - indentation: false - }, - accessibilitySupport: configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'), - cursorBlinking: configurationService.getValue<'blink' | 'smooth' | 'phase' | 'expand' | 'solid'>('editor.cursorBlinking') - }; -} From 0dd3429cbd9d0ccc19715ad2582b9e557a6ec019 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 14 Jul 2023 00:58:58 +0200 Subject: [PATCH 116/826] Revert "revert the feature (#187816)" (#187871) This reverts commit 7895f9bc23f85ffe6db5e3d27cae984f9fffab1c. --- .../browser/preferencesRenderers.ts | 10 +- .../preferences/browser/settingsTree.ts | 49 ++++++++- .../configuration/browser/configuration.ts | 10 +- .../browser/configurationService.ts | 56 ++++++++++- .../configuration/common/configuration.ts | 2 + .../test/browser/configurationService.test.ts | 99 ++++++++++++++++++- 6 files changed, 218 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 3983b46e191..c80d1d641c8 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -44,7 +44,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { isEqual } from 'vs/base/common/resources'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IStringDictionary } from 'vs/base/common/collections'; -import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; export interface IPreferencesRenderer extends IDisposable { render(): void; @@ -616,6 +616,14 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc if (configuration.scope === ConfigurationScope.APPLICATION) { // If we're in a profile setting file, and the setting is application-scoped, fade it out. markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); + } else if (this.configurationService.isSettingAppliedForAllProfiles(setting.key)) { + // If we're in the non-default profile setting file, and the setting can be applied in all profiles, fade it out. + markerData.push({ + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + ...setting.range, + message: nls.localize('allProfileSettingWhileInNonDefaultProfileSetting', "This setting cannot be applied because it is configured to be applied in all profiles using setting {0}. Value from the default profile will be used instead.", APPLY_ALL_PROFILES_SETTING) + }); } } } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 9124e87d359..ff00480a0ee 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -23,6 +23,7 @@ import { IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; import { ITreeFilter, ITreeModel, ITreeNode, ITreeRenderer, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { Action, IAction, Separator } from 'vs/base/common/actions'; +import { distinct } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -63,7 +64,7 @@ import { ISettingsEditorViewState, SettingsTreeElement, SettingsTreeGroupChild, import { ExcludeSettingWidget, IListDataItem, IObjectDataItem, IObjectEnumOption, IObjectKeySuggester, IObjectValueSuggester, ISettingListChangeEvent, IncludeSettingWidget, ListSettingWidget, ObjectSettingCheckboxWidget, ObjectSettingDropdownWidget, ObjectValue } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { LANGUAGE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; -import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; @@ -901,6 +902,11 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre } template.indicatorsLabel.updateScopeOverrides(element, this._onDidClickOverrideElement, this._onApplyFilter); + template.elementDisposables.add(this._configService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(APPLY_ALL_PROFILES_SETTING)) { + template.indicatorsLabel.updateScopeOverrides(element, this._onDidClickOverrideElement, this._onApplyFilter); + } + })); const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, @@ -1959,6 +1965,7 @@ export class SettingTreeRenderers { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IContextViewService private readonly _contextViewService: IContextViewService, + @IUserDataProfilesService private readonly _userDataProfilesService: IUserDataProfilesService, @IUserDataSyncEnablementService private readonly _userDataSyncEnablementService: IUserDataSyncEnablementService, ) { this.settingActions = [ @@ -2017,6 +2024,9 @@ export class SettingTreeRenderers { private getActionsForSetting(setting: ISetting, settingTarget: SettingsTarget): IAction[] { const actions: IAction[] = []; + if (this._userDataProfilesService.isEnabled() && setting.scope !== ConfigurationScope.APPLICATION && settingTarget === ConfigurationTarget.USER_LOCAL) { + actions.push(this._instantiationService.createInstance(ApplySettingToAllProfilesAction, setting)); + } if (this._userDataSyncEnablementService.isEnabled() && !setting.disallowSyncIgnore) { actions.push(this._instantiationService.createInstance(SyncSettingAction, setting)); } @@ -2486,3 +2496,40 @@ class SyncSettingAction extends Action { } } + +class ApplySettingToAllProfilesAction extends Action { + static readonly ID = 'settings.applyToAllProfiles'; + static readonly LABEL = localize('applyToAllProfiles', "Apply Setting to all Profiles"); + + constructor( + private readonly setting: ISetting, + @IWorkbenchConfigurationService private readonly configService: IWorkbenchConfigurationService, + ) { + super(ApplySettingToAllProfilesAction.ID, ApplySettingToAllProfilesAction.LABEL); + this._register(Event.filter(configService.onDidChangeConfiguration, e => e.affectsConfiguration(APPLY_ALL_PROFILES_SETTING))(() => this.update())); + this.update(); + } + + update() { + const allProfilesSettings = this.configService.getValue(APPLY_ALL_PROFILES_SETTING); + this.checked = allProfilesSettings.includes(this.setting.key); + } + + override async run(): Promise { + // first remove the current setting completely from ignored settings + const value = this.configService.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; + + if (this.checked) { + value.splice(value.indexOf(this.setting.key), 1); + } else { + value.push(this.setting.key); + } + + const newValue = distinct(value); + await this.configService.updateValue(APPLY_ALL_PROFILES_SETTING, newValue.length ? newValue : undefined, ConfigurationTarget.USER_LOCAL); + if (!this.checked) { + await this.configService.updateValue(this.setting.key, this.configService.inspect(this.setting.key).userLocal?.value, ConfigurationTarget.USER_LOCAL); + } + } + +} diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 70f1fcd1e93..8f942efdd0e 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -11,7 +11,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult, FileOperation, FileOperationEvent } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; -import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; +import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES, APPLY_ALL_PROFILES_SETTING } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { WorkbenchState, IWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { ConfigurationScope, Extensions, IConfigurationRegistry, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; @@ -140,6 +140,14 @@ export class ApplicationConfiguration extends UserSettings { return this.loadConfiguration(); } + override async loadConfiguration(): Promise { + const model = await super.loadConfiguration(); + const value = model.getValue(APPLY_ALL_PROFILES_SETTING); + const allProfilesSettings = Array.isArray(value) ? value : []; + return this.parseOptions.include || allProfilesSettings.length + ? this.reparse({ ...this.parseOptions, include: allProfilesSettings }) + : model; + } } export class UserConfiguration extends Disposable { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index b02fbc29e61..9a920e59af0 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -15,7 +15,7 @@ import { ConfigurationModel, ConfigurationChangeEvent, mergeChanges } from 'vs/p import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService, IConfigurationUpdateOptions } from 'vs/platform/configuration/common/configuration'; import { IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId, APPLY_ALL_PROFILES_SETTING } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId } from 'vs/platform/configuration/common/configurationRegistry'; import { IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, getStoredWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; @@ -44,6 +44,7 @@ import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/pol import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined { return userDataProfile.isDefault @@ -489,7 +490,11 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } isSettingAppliedForAllProfiles(key: string): boolean { - return this.configurationRegistry.getConfigurationProperties()[key]?.scope === ConfigurationScope.APPLICATION; + if (this.configurationRegistry.getConfigurationProperties()[key]?.scope === ConfigurationScope.APPLICATION) { + return true; + } + const allProfilesSettings = this.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; + return Array.isArray(allProfilesSettings) && allProfilesSettings.includes(key); } private async createWorkspace(arg: IAnyWorkspaceIdentifier): Promise { @@ -601,6 +606,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat const initUserConfiguration = async () => { mark('code/willInitUserConfiguration'); const result = await Promise.all([this.localUserConfiguration.initialize(), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())]); + if (this.applicationConfiguration) { + const applicationConfigurationModel = await initApplicationConfigurationPromise; + result[0] = this.localUserConfiguration.reparse({ exclude: applicationConfigurationModel.getValue(APPLY_ALL_PROFILES_SETTING) }); + } mark('code/didInitUserConfiguration'); return result; }; @@ -714,8 +723,12 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat promises.push(this.reloadApplicationConfiguration(true)); } } - const [localUser, application] = await Promise.all(promises); - await this.loadConfiguration(application ?? this._configuration.applicationConfiguration, localUser, this._configuration.remoteUserConfiguration, true); + let [localUser, application] = await Promise.all(promises); + application = application ?? this._configuration.applicationConfiguration; + if (this.applicationConfiguration) { + localUser = this.localUserConfiguration.reparse({ exclude: application.getValue(APPLY_ALL_PROFILES_SETTING) }); + } + await this.loadConfiguration(application, localUser, this._configuration.remoteUserConfiguration, true); })()); } @@ -758,15 +771,35 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private onApplicationConfigurationChanged(applicationConfiguration: ConfigurationModel): void { const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const previousAllProfilesSettings = this._configuration.applicationConfiguration.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; const change = this._configuration.compareAndUpdateApplicationConfiguration(applicationConfiguration); + const currentAllProfilesSettings = this.getValue(APPLY_ALL_PROFILES_SETTING) ?? []; const configurationProperties = this.configurationRegistry.getConfigurationProperties(); const changedKeys: string[] = []; for (const changedKey of change.keys) { if (configurationProperties[changedKey]?.scope === ConfigurationScope.APPLICATION) { changedKeys.push(changedKey); + if (changedKey === APPLY_ALL_PROFILES_SETTING) { + for (const previousAllProfileSetting of previousAllProfilesSettings) { + if (!currentAllProfilesSettings.includes(previousAllProfileSetting)) { + changedKeys.push(previousAllProfileSetting); + } + } + for (const currentAllProfileSetting of currentAllProfilesSettings) { + if (!previousAllProfilesSettings.includes(currentAllProfileSetting)) { + changedKeys.push(currentAllProfileSetting); + } + } + } + } + else if (currentAllProfilesSettings.includes(changedKey)) { + changedKeys.push(changedKey); } } change.keys = changedKeys; + if (change.keys.includes(APPLY_ALL_PROFILES_SETTING)) { + this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reparse({ exclude: currentAllProfilesSettings })); + } this.triggerConfigurationChange(change, previous, ConfigurationTarget.USER); } @@ -1318,3 +1351,18 @@ const workbenchContributionsRegistry = Registry.as(Extensions.Configuration); +configurationRegistry.registerConfiguration({ + ...workbenchConfigurationNodeBase, + properties: { + [APPLY_ALL_PROFILES_SETTING]: { + 'type': 'array', + description: localize('setting description', "Configure settings to be applied for all profiles."), + 'default': [], + 'scope': ConfigurationScope.APPLICATION, + additionalProperties: true, + uniqueItems: true, + } + } +}); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 227c827eb2a..043add6bd72 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -93,3 +93,5 @@ export interface IWorkbenchConfigurationService extends IConfigurationService { } export const TASKS_DEFAULT = '{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": []\n}'; + +export const APPLY_ALL_PROFILES_SETTING = 'workbench.settings.applyToAllProfiles'; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index b336cb2fec3..e0613cac0c7 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -28,7 +28,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; +import { APPLY_ALL_PROFILES_SETTING, IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; import { SignService } from 'vs/platform/sign/browser/signService'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; @@ -1529,6 +1529,11 @@ suite('WorkspaceConfigurationService - Profiles', () => { 'id': '_test', 'type': 'object', 'properties': { + [APPLY_ALL_PROFILES_SETTING]: { + 'type': 'array', + 'default': [], + 'scope': ConfigurationScope.APPLICATION, + }, 'configurationService.profiles.applicationSetting': { 'type': 'string', 'default': 'isSet', @@ -1682,9 +1687,61 @@ suite('WorkspaceConfigurationService - Profiles', () => { assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting3'), 'defaultProfile'); })); + test('initialize with custom all profiles settings', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + + await testObject.initialize(convertToWorkspacePayload(joinPath(ROOT, 'a'))); + + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); + })); + + test('update all profiles settings', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + + const changeEvent = await promise; + assert.deepStrictEqual([...changeEvent.affectedKeys], [APPLY_ALL_PROFILES_SETTING, 'configurationService.profiles.testSetting2']); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); + })); + + test('setting applied to all profiles is registered later', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await fileService.writeFile(instantiationService.get(IUserDataProfilesService).defaultProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting4": "userValue" }')); + await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting4": "profileValue" }')); + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting4'], ConfigurationTarget.USER_LOCAL); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting4'), 'userValue'); + + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.profiles.testSetting4': { + 'type': 'string', + 'default': 'isSet', + } + } + }); + + await testObject.reloadConfiguration(); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting4'), 'userValue'); + })); + + test('update setting that is applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await testObject.updateValue('configurationService.profiles.testSetting2', 'updatedValue', ConfigurationTarget.USER_LOCAL); + + const changeEvent = await promise; + assert.deepStrictEqual([...changeEvent.affectedKeys], ['configurationService.profiles.testSetting2']); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'updatedValue'); + })); + test('test isSettingAppliedToAllProfiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { assert.strictEqual(testObject.isSettingAppliedForAllProfiles('configurationService.profiles.applicationSetting2'), true); assert.strictEqual(testObject.isSettingAppliedForAllProfiles('configurationService.profiles.testSetting2'), false); + + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + assert.strictEqual(testObject.isSettingAppliedForAllProfiles('configurationService.profiles.testSetting2'), true); })); test('switch to default profile', () => runWithFakedTimers({ useFakeTimers: true }, async () => { @@ -1730,6 +1787,46 @@ suite('WorkspaceConfigurationService - Profiles', () => { assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'isSet'); })); + test('switch to default profile with settings applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + + await userDataProfileService.updateCurrentProfile(instantiationService.get(IUserDataProfilesService).defaultProfile); + + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); + })); + + test('switch to non default profile with settings applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + + const profile = toUserDataProfile('custom2', 'custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2'), joinPath(environmentService.cacheHome, 'profilesCache')); + await fileService.writeFile(profile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting": "profileValue", "configurationService.profiles.testSetting2": "profileValue2" }')); + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await userDataProfileService.updateCurrentProfile(profile); + + const changeEvent = await promise; + assert.deepStrictEqual([...changeEvent.affectedKeys], ['configurationService.profiles.testSetting']); + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'profileValue'); + })); + + test('switch to non default from default profile with settings applied to all profiles', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + await testObject.updateValue(APPLY_ALL_PROFILES_SETTING, ['configurationService.profiles.testSetting2'], ConfigurationTarget.USER_LOCAL); + await userDataProfileService.updateCurrentProfile(instantiationService.get(IUserDataProfilesService).defaultProfile); + + const profile = toUserDataProfile('custom2', 'custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2'), joinPath(environmentService.cacheHome, 'profilesCache')); + await fileService.writeFile(profile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting": "profileValue", "configurationService.profiles.testSetting2": "profileValue2" }')); + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await userDataProfileService.updateCurrentProfile(profile); + + const changeEvent = await promise; + assert.deepStrictEqual([...changeEvent.affectedKeys], ['configurationService.profiles.testSetting']); + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'userValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'profileValue'); + })); + }); suite('WorkspaceConfigurationService-Multiroot', () => { From 86bb5239ebc10d7cf9db835a5c1242e9c8207e96 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 14 Jul 2023 01:01:49 +0200 Subject: [PATCH 117/826] Fix #187487 (#187872) --- .../abstractExtensionManagementService.ts | 12 ++--- .../common/extensionManagement.ts | 1 - .../userDataSync/common/extensionsSync.ts | 2 +- .../browser/extensions.contribution.ts | 2 +- .../extensions/browser/extensionsActions.ts | 54 +++++++------------ .../remoteExtensionManagementService.ts | 6 +-- 6 files changed, 26 insertions(+), 51 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index b9449b9df77..2ed53a4b0d7 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -118,7 +118,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl await Promise.allSettled(extensions.map(async ({ extension, options }) => { try { - const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion, false); + const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion); installableExtensions.push({ ...compatible, options }); } catch (error) { results.push({ identifier: extension.identifier, operation: InstallOperation.Install, source: extension, error }); @@ -434,7 +434,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const isDependency = dependecies.some(id => areSameExtensions({ id }, galleryExtension.identifier)); let compatible; try { - compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease, true); + compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease); } catch (error) { if (!isDependency) { this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id, getErrorMessage(error)); @@ -454,7 +454,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return allDependenciesAndPacks; } - private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean, fallbackToRelease: boolean): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> { + private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> { const extensionsControlManifest = await this.getExtensionsControlManifest(); if (extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier))) { throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious); @@ -466,11 +466,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } const compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease); - if (compatibleExtension) { - if (!fallbackToRelease && installPreRelease && !sameVersion && extension.hasPreReleaseVersion && !compatibleExtension.properties.isPreReleaseVersion) { - throw new ExtensionManagementError(nls.localize('notFoundCompatiblePrereleaseDependency', "Can't install pre-release version of '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.IncompatiblePreRelease); - } - } else { + if (!compatibleExtension) { /** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */ if (!installPreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) { throw new ExtensionManagementError(nls.localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 77f90157954..23df3ac6abd 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -407,7 +407,6 @@ export enum ExtensionManagementErrorCode { Deprecated = 'Deprecated', Malicious = 'Malicious', Incompatible = 'Incompatible', - IncompatiblePreRelease = 'IncompatiblePreRelease', IncompatibleTargetPlatform = 'IncompatibleTargetPlatform', ReleaseVersionNotFound = 'ReleaseVersionNotFound', Invalid = 'Invalid', diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index d898288a41e..aff6b642585 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -527,7 +527,7 @@ export class LocalExtensionsProvider { addToSkipped.push(e); this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension`, gallery.displayName || gallery.identifier.id); } - if (error instanceof ExtensionManagementError && [ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatiblePreRelease, ExtensionManagementErrorCode.IncompatibleTargetPlatform].includes(error.code)) { + if (error instanceof ExtensionManagementError && [ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform].includes(error.code)) { this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension because the compatible extension is not found.`, gallery.displayName || gallery.identifier.id); } else if (error) { this.logService.error(error); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f0f2c01fbbd..bd7165c5d12 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -678,7 +678,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi try { await this.extensionsWorkbenchService.install(extension, extension.local?.preRelease ? { installPreReleaseVersion: true } : undefined); } catch (err) { - runAction(this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Update, undefined, err)); + runAction(this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Update, err)); } })); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index ee6c57e9959..8ad613804b5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -78,7 +78,6 @@ export class PromptExtensionInstallFailureAction extends Action { private readonly extension: IExtension, private readonly version: string, private readonly installOperation: InstallOperation, - private readonly installOptions: InstallOptions | undefined, private readonly error: Error, @IProductService private readonly productService: IProductService, @IOpenerService private readonly openerService: IOpenerService, @@ -138,44 +137,29 @@ export class PromptExtensionInstallFailureAction extends Action { return; } - let operationMessage = this.installOperation === InstallOperation.Update ? localize('update operation', "Error while updating '{0}' extension.", this.extension.displayName || this.extension.identifier.id) + const operationMessage = this.installOperation === InstallOperation.Update ? localize('update operation', "Error while updating '{0}' extension.", this.extension.displayName || this.extension.identifier.id) : localize('install operation', "Error while installing '{0}' extension.", this.extension.displayName || this.extension.identifier.id); let additionalMessage; const promptChoices: IPromptChoice[] = []; - if (ExtensionManagementErrorCode.IncompatiblePreRelease === (this.error.name)) { - operationMessage = getErrorMessage(this.error); - additionalMessage = localize('install release version message', "Would you like to install the release version?"); + const downloadUrl = await this.getDownloadUrl(); + if (downloadUrl) { + additionalMessage = localize('check logs', "Please check the [log]({0}) for more details.", `command:${showWindowLogActionId}`); promptChoices.push({ - label: localize('install release version', "Install Release Version"), - run: () => { - const installAction = this.instantiationService.createInstance(InstallAction, { installPreReleaseVersion: !!this.installOptions?.installPreReleaseVersion }); - installAction.extension = this.extension; - return installAction.run(); - } + label: localize('download', "Try Downloading Manually..."), + run: () => this.openerService.open(downloadUrl).then(() => { + this.notificationService.prompt( + Severity.Info, + localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', this.extension.identifier.id), + [{ + label: localize('installVSIX', "Install from VSIX..."), + run: () => this.commandService.executeCommand(SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID) + }] + ); + }) }); } - else { - const downloadUrl = await this.getDownloadUrl(); - if (downloadUrl) { - additionalMessage = localize('check logs', "Please check the [log]({0}) for more details.", `command:${showWindowLogActionId}`); - promptChoices.push({ - label: localize('download', "Try Downloading Manually..."), - run: () => this.openerService.open(downloadUrl).then(() => { - this.notificationService.prompt( - Severity.Info, - localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', this.extension.identifier.id), - [{ - label: localize('installVSIX', "Install from VSIX..."), - run: () => this.commandService.executeCommand(SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID) - }] - ); - }) - }); - } - } - const message = `${operationMessage}${additionalMessage ? ` ${additionalMessage}` : ''}`; this.notificationService.prompt(Severity.Error, message, promptChoices); } @@ -469,7 +453,7 @@ export class InstallAction extends ExtensionAction { try { return await this.extensionsWorkbenchService.install(extension, this.options); } catch (error) { - await this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Install, this.options, error).run(); + await this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Install, error).run(); return undefined; } } @@ -837,7 +821,7 @@ export class UpdateAction extends AbstractUpdateAction { await this.extensionsWorkbenchService.install(extension, extension.local?.preRelease ? { installPreReleaseVersion: true } : undefined); alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", extension.displayName, extension.latestVersion)); } catch (err) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Update, undefined, err).run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Update, err).run(); } } } @@ -1304,7 +1288,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { await this.extensionsWorkbenchService.installVersion(this.extension!, pick.id, { installPreReleaseVersion: pick.isPreReleaseVersion }); } } catch (error) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension!, pick.latest ? this.extension!.latestVersion : pick.id, InstallOperation.Install, undefined, error).run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension!, pick.latest ? this.extension!.latestVersion : pick.id, InstallOperation.Install, error).run(); } } return null; @@ -1811,7 +1795,7 @@ export class InstallRecommendedExtensionAction extends Action { try { await this.extensionWorkbenchService.install(extension); } catch (err) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Install, undefined, err).run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Install, err).run(); } } } diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index 112c8e95c5d..30e29d6784d 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -129,11 +129,7 @@ export class NativeRemoteExtensionManagementService extends RemoteExtensionManag compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform); } - if (compatibleExtension) { - if (includePreRelease && !compatibleExtension.properties.isPreReleaseVersion && extension.hasPreReleaseVersion) { - throw new ExtensionManagementError(localize('notFoundCompatiblePrereleaseDependency', "Can't install pre-release version of '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.IncompatiblePreRelease); - } - } else { + if (!compatibleExtension) { /** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */ if (!includePreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) { throw new ExtensionManagementError(localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound); From c0d560ed49aee29ce51deb0cecfa99db2d6b0313 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 13 Jul 2023 16:02:57 -0700 Subject: [PATCH 118/826] Don't parse `@link` inside `@example` JSDoc tags (#187877) Fixes #187768 --- .../languageFeatures/util/textRendering.ts | 13 +++++- .../src/test/unit/textRendering.test.ts | 46 +++++++++++++++---- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts b/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts index bc4b7ad1b5f..2918a7de54c 100644 --- a/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts +++ b/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts @@ -49,9 +49,13 @@ function getTagBodyText( return '```\n' + text + '\n```'; } - const text = convertLinkTags(tag.text, filePathConverter); + let text = convertLinkTags(tag.text, filePathConverter); switch (tag.name) { case 'example': { + // Example text does not support `{@link}` as it is considered code. + // TODO: should we support it if it appears outside of an explicit code block? + text = asPlainText(tag.text); + // check for caption tags, fix for #79704 const captionTagMatches = text.match(/(.*?)<\/caption>\s*(\r\n|\n)/); if (captionTagMatches && captionTagMatches.index === 0) { @@ -132,6 +136,13 @@ function getTagBody(tag: Proto.JSDocTagInfo, filePathConverter: IFilePathToResou return (convertLinkTags(tag.text, filePathConverter)).split(/^(\S+)\s*-?\s*/); } +function asPlainText(parts: readonly Proto.SymbolDisplayPart[] | string): string { + if (typeof parts === 'string') { + return parts; + } + return parts.map(part => part.text).join(''); +} + export function asPlainTextWithLinks( parts: readonly Proto.SymbolDisplayPart[] | string, filePathConverter: IFilePathToResourceConverter, diff --git a/extensions/typescript-language-features/src/test/unit/textRendering.test.ts b/extensions/typescript-language-features/src/test/unit/textRendering.test.ts index 2354bb7f589..b13f682f715 100644 --- a/extensions/typescript-language-features/src/test/unit/textRendering.test.ts +++ b/extensions/typescript-language-features/src/test/unit/textRendering.test.ts @@ -14,7 +14,7 @@ const noopToResource: IFilePathToResourceConverter = { }; suite('typescript.previewer', () => { - test('Should ignore hyphens after a param tag', async () => { + test('Should ignore hyphens after a param tag', () => { assert.strictEqual( tagsToMarkdown([ { @@ -25,7 +25,7 @@ suite('typescript.previewer', () => { '*@param* `a` — b'); }); - test('Should parse url jsdoc @link', async () => { + test('Should parse url jsdoc @link', () => { assert.strictEqual( documentationToMarkdown( 'x {@link http://www.example.com/foo} y {@link https://api.jquery.com/bind/#bind-eventType-eventData-handler} z', @@ -35,7 +35,7 @@ suite('typescript.previewer', () => { 'x [http://www.example.com/foo](http://www.example.com/foo) y [https://api.jquery.com/bind/#bind-eventType-eventData-handler](https://api.jquery.com/bind/#bind-eventType-eventData-handler) z'); }); - test('Should parse url jsdoc @link with text', async () => { + test('Should parse url jsdoc @link with text', () => { assert.strictEqual( documentationToMarkdown( 'x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z', @@ -45,7 +45,7 @@ suite('typescript.previewer', () => { 'x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z'); }); - test('Should treat @linkcode jsdocs links as monospace', async () => { + test('Should treat @linkcode jsdocs links as monospace', () => { assert.strictEqual( documentationToMarkdown( 'x {@linkcode http://www.example.com/foo} y {@linkplain http://www.example.com/bar} z', @@ -55,7 +55,7 @@ suite('typescript.previewer', () => { 'x [`http://www.example.com/foo`](http://www.example.com/foo) y [http://www.example.com/bar](http://www.example.com/bar) z'); }); - test('Should parse url jsdoc @link in param tag', async () => { + test('Should parse url jsdoc @link in param tag', () => { assert.strictEqual( tagsToMarkdown([ { @@ -66,7 +66,7 @@ suite('typescript.previewer', () => { '*@param* `a` — x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z'); }); - test('Should ignore unclosed jsdocs @link', async () => { + test('Should ignore unclosed jsdocs @link', () => { assert.strictEqual( documentationToMarkdown( 'x {@link http://www.example.com/foo y {@link http://www.example.com/bar bar} z', @@ -76,7 +76,7 @@ suite('typescript.previewer', () => { 'x {@link http://www.example.com/foo y [bar](http://www.example.com/bar) z'); }); - test('Should support non-ascii characters in parameter name (#90108)', async () => { + test('Should support non-ascii characters in parameter name (#90108)', () => { assert.strictEqual( tagsToMarkdown([ { @@ -135,7 +135,35 @@ suite('typescript.previewer', () => { ); }); - test('Should render @linkcode symbol name as code', async () => { + test('Should not render @link inside of @example #187768', () => { + assert.strictEqual( + tagsToMarkdown([ + { + "name": "example", + "text": [ + { + "text": "1 + 1 ", + "kind": "text" + }, + { + "text": "{@link ", + "kind": "link" + }, + { + "text": "foo", + "kind": "linkName" + }, + { + "text": "}", + "kind": "link" + } + ] + } + ], noopToResource), + '*@example* \n```\n1 + 1 {@link foo}\n```'); + }); + + test('Should render @linkcode symbol name as code', () => { assert.strictEqual( asPlainTextWithLinks([ { "text": "a ", "kind": "text" }, @@ -155,7 +183,7 @@ suite('typescript.previewer', () => { 'a [`dog`](command:_typescript.openJsDocLink?%5B%7B%22file%22%3A%7B%22path%22%3A%22%2Fpath%2Ffile.ts%22%2C%22scheme%22%3A%22file%22%7D%2C%22position%22%3A%7B%22line%22%3A6%2C%22character%22%3A4%7D%7D%5D) b'); }); - test('Should render @linkcode text as code', async () => { + test('Should render @linkcode text as code', () => { assert.strictEqual( asPlainTextWithLinks([ { "text": "a ", "kind": "text" }, From 52d462293dba671b3d2417a299d3b61dfd4c8124 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 14 Jul 2023 01:04:15 +0200 Subject: [PATCH 119/826] Avoid creating services in APIs used by the monaco-editor repo itself (#187881) Fixes #169268: Avoid creating services in APIs used by the monaco-editor repo itself --- .../standalone/browser/standaloneLanguages.ts | 40 ++++++++++--------- .../standalone/browser/standaloneServices.ts | 22 ++++++++++ 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 78d683d2fe5..939d3391034 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -54,16 +54,18 @@ export function getEncodedLanguageId(languageId: string): number { * @event */ export function onLanguage(languageId: string, callback: () => void): IDisposable { - const languageService = StandaloneServices.get(ILanguageService); - const disposable = languageService.onDidRequestRichLanguageFeatures((encounteredLanguageId) => { - if (encounteredLanguageId === languageId) { - // stop listening - disposable.dispose(); - // invoke actual listener - callback(); - } + return StandaloneServices.withServices(() => { + const languageService = StandaloneServices.get(ILanguageService); + const disposable = languageService.onDidRequestRichLanguageFeatures((encounteredLanguageId) => { + if (encounteredLanguageId === languageId) { + // stop listening + disposable.dispose(); + // invoke actual listener + callback(); + } + }); + return disposable; }); - return disposable; } /** @@ -72,16 +74,18 @@ export function onLanguage(languageId: string, callback: () => void): IDisposabl * @event */ export function onLanguageEncountered(languageId: string, callback: () => void): IDisposable { - const languageService = StandaloneServices.get(ILanguageService); - const disposable = languageService.onDidRequestBasicLanguageFeatures((encounteredLanguageId) => { - if (encounteredLanguageId === languageId) { - // stop listening - disposable.dispose(); - // invoke actual listener - callback(); - } + return StandaloneServices.withServices(() => { + const languageService = StandaloneServices.get(ILanguageService); + const disposable = languageService.onDidRequestBasicLanguageFeatures((encounteredLanguageId) => { + if (encounteredLanguageId === languageId) { + // stop listening + disposable.dispose(); + // invoke actual listener + callback(); + } + }); + return disposable; }); - return disposable; } /** diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index cc2ede5219c..0546f5fc282 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -1128,6 +1128,7 @@ export module StandaloneServices { } let initialized = false; + const onDidInitialize = new Emitter(); export function initialize(overrides: IEditorOverrideServices): IInstantiationService { if (initialized) { return instantiationService; @@ -1163,6 +1164,27 @@ export module StandaloneServices { } } + onDidInitialize.fire(); + return instantiationService; } + + /** + * Executes callback once services are initialized. + */ + export function withServices(callback: () => IDisposable): IDisposable { + if (initialized) { + return callback(); + } + + const disposable = new DisposableStore(); + + const listener = disposable.add(onDidInitialize.event(() => { + listener.dispose(); + disposable.add(callback()); + })); + + return disposable; + } + } From 14a1c7e5e3465e737298e86336076d1d07f2944b Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 13 Jul 2023 16:29:26 -0700 Subject: [PATCH 120/826] allow multiple callers for decorating notebook webview (#187743) --- .../contrib/find/findMatchDecorationModel.ts | 11 +- .../browser/contrib/find/findModel.ts | 2 +- .../notebook/browser/notebookBrowser.ts | 8 +- .../notebook/browser/notebookEditorWidget.ts | 22 ++- .../view/renderers/backLayerWebView.ts | 23 +-- .../browser/view/renderers/webviewMessages.ts | 23 +-- .../browser/view/renderers/webviewPreloads.ts | 163 +++++++++++++----- .../test/browser/testNotebookEditor.ts | 1 + .../contrib/search/browser/notebookSearch.ts | 2 +- .../search/browser/notebookSearchService.ts | 8 +- .../contrib/search/browser/searchModel.ts | 50 +++--- .../search/test/browser/searchActions.test.ts | 2 +- .../search/test/browser/searchModel.test.ts | 2 +- .../browser/searchNotebookHelpers.test.ts | 2 +- .../search/test/browser/searchResult.test.ts | 28 +-- .../search/test/browser/searchTestCommon.ts | 8 +- .../search/test/browser/searchViewlet.test.ts | 4 +- 17 files changed, 227 insertions(+), 132 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts index 5e272f641c3..6fa5a4fea6c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts @@ -17,7 +17,8 @@ export class FindMatchDecorationModel extends Disposable { private _currentMatchDecorations: { kind: 'input'; decorations: ICellModelDecorations[] } | { kind: 'output'; index: number } | null = null; constructor( - private readonly _notebookEditor: INotebookEditor + private readonly _notebookEditor: INotebookEditor, + private readonly ownerID: string, ) { super(); } @@ -26,7 +27,7 @@ export class FindMatchDecorationModel extends Disposable { return this._currentMatchDecorations; } - public clearDecorations() { + private clearDecorations() { this.clearCurrentFindMatchDecoration(); this.setAllFindMatchesDecorations([]); } @@ -75,7 +76,7 @@ export class FindMatchDecorationModel extends Disposable { this.clearCurrentFindMatchDecoration(); - const offset = await this._notebookEditor.highlightFind(index); + const offset = await this._notebookEditor.findHighlightCurrent(index, this.ownerID); this._currentMatchDecorations = { kind: 'output', index: index }; this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, [{ @@ -101,7 +102,7 @@ export class FindMatchDecorationModel extends Disposable { this._currentMatchDecorations = null; }); } else if (this._currentMatchDecorations?.kind === 'output') { - this._notebookEditor.unHighlightFind(this._currentMatchDecorations.index); + this._notebookEditor.findUnHighlightCurrent(this._currentMatchDecorations.index, this.ownerID); } this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, []); @@ -145,7 +146,7 @@ export class FindMatchDecorationModel extends Disposable { } stopWebviewFind() { - this._notebookEditor.findStop(); + this._notebookEditor.findStop(this.ownerID); } override dispose() { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index e08a00a42cb..1d955dbbcdd 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -111,7 +111,7 @@ export class FindModel extends Disposable { this._registerModelListener(this._notebookEditor.textModel); } - this._findMatchDecorationModel = new FindMatchDecorationModel(this._notebookEditor); + this._findMatchDecorationModel = new FindMatchDecorationModel(this._notebookEditor, this._notebookEditor.getId()); } private _updateCellStates(e: FindReplaceStateChangedEvent) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 2959ca1b13a..5166e317991 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -673,10 +673,10 @@ export interface INotebookEditor { getCellIndex(cell: ICellViewModel): number | undefined; getNextVisibleCellIndex(index: number): number | undefined; getPreviousVisibleCellIndex(index: number): number | undefined; - find(query: string, options: INotebookSearchOptions, token: CancellationToken): Promise; - highlightFind(matchIndex: number): Promise; - unHighlightFind(matchIndex: number): Promise; - findStop(): void; + find(query: string, options: INotebookSearchOptions, token: CancellationToken, skipWarmup?: boolean, shouldGetSearchPreviewInfo?: boolean, ownerID?: string): Promise; + findHighlightCurrent(matchIndex: number, ownerID?: string): Promise; + findUnHighlightCurrent(matchIndex: number, ownerID?: string): Promise; + findStop(ownerID?: string): void; showProgress(): void; hideProgress(): void; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 801f7d93f61..92fb5866d8b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2430,15 +2430,19 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return Promise.all(requests); } - async find(query: string, options: INotebookSearchOptions, token: CancellationToken, skipWarmup: boolean = false, shouldGetSearchPreviewInfo = false): Promise { + async find(query: string, options: INotebookSearchOptions, token: CancellationToken, skipWarmup: boolean = false, shouldGetSearchPreviewInfo = false, ownerID?: string): Promise { if (!this._notebookViewModel) { return []; } + if (!ownerID) { + ownerID = this.getId(); + } + const findMatches = this._notebookViewModel.find(query, options).filter(match => match.length > 0); if (!options.includeMarkupPreview && !options.includeOutput) { - this._webview?.findStop(); + this._webview?.findStop(ownerID); return findMatches; } @@ -2461,7 +2465,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return []; } - const webviewMatches = await this._webview.find(query, { caseSensitive: options.caseSensitive, wholeWord: options.wholeWord, includeMarkup: !!options.includeMarkupPreview, includeOutput: !!options.includeOutput, shouldGetSearchPreviewInfo }); + const webviewMatches = await this._webview.find(query, { caseSensitive: options.caseSensitive, wholeWord: options.wholeWord, includeMarkup: !!options.includeMarkupPreview, includeOutput: !!options.includeOutput, shouldGetSearchPreviewInfo, ownerID }); if (token.isCancellationRequested) { return []; @@ -2517,24 +2521,24 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return ret; } - async highlightFind(matchIndex: number): Promise { + async findHighlightCurrent(matchIndex: number, ownerID?: string): Promise { if (!this._webview) { return 0; } - return this._webview?.findHighlight(matchIndex); + return this._webview?.findHighlightCurrent(matchIndex, ownerID ?? this.getId()); } - async unHighlightFind(matchIndex: number): Promise { + async findUnHighlightCurrent(matchIndex: number, ownerID?: string): Promise { if (!this._webview) { return; } - return this._webview?.findUnHighlight(matchIndex); + return this._webview?.findUnHighlightCurrent(matchIndex, ownerID ?? this.getId()); } - findStop() { - this._webview?.findStop(); + findStop(ownerID?: string) { + this._webview?.findStop(ownerID ?? this.getId()); } //#endregion 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 06a4054a668..4a3c05451ea 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -1608,7 +1608,7 @@ export class BackLayerWebView extends Themable { }); } - async find(query: string, options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean; shouldGetSearchPreviewInfo: boolean }): Promise { + async find(query: string, options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean; shouldGetSearchPreviewInfo: boolean; ownerID: string }): Promise { if (query === '') { return []; } @@ -1632,16 +1632,17 @@ export class BackLayerWebView extends Themable { return ret; } - findStop() { + findStop(ownerID: string) { this._sendMessageToWebview({ - type: 'findStop' + type: 'findStop', + ownerID }); } - async findHighlight(index: number): Promise { + async findHighlightCurrent(index: number, ownerID: string): Promise { const p = new Promise(resolve => { const sub = this.webview?.onMessage(e => { - if (e.message.type === 'didFindHighlight') { + if (e.message.type === 'didFindHighlightCurrent') { resolve(e.message.offset); sub?.dispose(); } @@ -1649,18 +1650,20 @@ export class BackLayerWebView extends Themable { }); this._sendMessageToWebview({ - type: 'findHighlight', - index + type: 'findHighlightCurrent', + index, + ownerID }); const ret = await p; return ret; } - async findUnHighlight(index: number): Promise { + async findUnHighlightCurrent(index: number, ownerID: string): Promise { this._sendMessageToWebview({ - type: 'findUnHighlight', - index + type: 'findUnHighlightCurrent', + index, + ownerID }); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index 15fc1f19790..1b21208b7b4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -397,22 +397,25 @@ export interface ITokenizedStylesChangedMessage { export interface IFindMessage { readonly type: 'find'; readonly query: string; - readonly options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean; shouldGetSearchPreviewInfo: boolean }; + readonly options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean; shouldGetSearchPreviewInfo: boolean; ownerID: string }; } -export interface IFindHighlightMessage { - readonly type: 'findHighlight'; +export interface IFindHighlightCurrentMessage { + readonly type: 'findHighlightCurrent'; readonly index: number; + readonly ownerID: string; } -export interface IFindUnHighlightMessage { - readonly type: 'findUnHighlight'; +export interface IFindUnHighlightCurrentMessage { + readonly type: 'findUnHighlightCurrent'; readonly index: number; + readonly ownerID: string; } export interface IFindStopMessage { readonly type: 'findStop'; + readonly ownerID: string; } export interface ISearchPreviewInfo { @@ -436,8 +439,8 @@ export interface IDidFindMessage extends BaseToWebviewMessage { readonly matches: IFindMatch[]; } -export interface IDidFindHighlightMessage extends BaseToWebviewMessage { - readonly type: 'didFindHighlight'; +export interface IDidFindHighlightCurrentMessage extends BaseToWebviewMessage { + readonly type: 'didFindHighlightCurrent'; readonly offset: number; } @@ -502,7 +505,7 @@ export type FromWebviewMessage = WebviewInitialized | IRenderedMarkupMessage | IRenderedCellOutputMessage | IDidFindMessage | - IDidFindHighlightMessage | + IDidFindHighlightCurrentMessage | IOutputResizedMessage | IGetOutputItemMessage | ILogRendererDebugMessage | @@ -534,8 +537,8 @@ export type ToWebviewMessage = IClearMessage | ITokenizedCodeBlockMessage | ITokenizedStylesChangedMessage | IFindMessage | - IFindHighlightMessage | - IFindUnHighlightMessage | + IFindHighlightCurrentMessage | + IFindUnHighlightCurrentMessage | IFindStopMessage | IReturnOutputItemMessage; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 25dab44de71..649d3421949 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -908,21 +908,30 @@ async function webviewPreloads(ctx: PreloadContext) { } interface IHighlighter { - highlightCurrentMatch(index: number): void; - unHighlightCurrentMatch(index: number): void; + addHighlights(matches: IFindMatch[], ownerID: string): void; + removeHighlights(ownerID: string): void; + highlightCurrentMatch(index: number, ownerID: string): void; + unHighlightCurrentMatch(index: number, ownerID: string): void; dispose(): void; } - let _highlighter: IHighlighter | null = null; + interface IHighlightInfo { + matches: IFindMatch[]; + currentMatchIndex: number; + } + const matchColor = window.getComputedStyle(document.getElementById('_defaultColorPalatte')!).color; const currentMatchColor = window.getComputedStyle(document.getElementById('_defaultColorPalatte')!).backgroundColor; class JSHighlighter implements IHighlighter { - private _findMatchIndex = -1; + private _activeHighlightInfo: Map; constructor( - readonly matches: IFindMatch[], ) { + this._activeHighlightInfo = new Map(); + } + + addHighlights(matches: IFindMatch[], ownerID: string): void { for (let i = matches.length - 1; i >= 0; i--) { const match = matches[i]; const ret = highlightRange(match.originalRange, true, 'mark', match.isShadow ? { @@ -932,14 +941,32 @@ async function webviewPreloads(ctx: PreloadContext) { }); match.highlightResult = ret; } + + const highlightInfo: IHighlightInfo = { + matches, + currentMatchIndex: -1 + }; + this._activeHighlightInfo.set(ownerID, highlightInfo); } - highlightCurrentMatch(index: number) { - const oldMatch = this.matches[this._findMatchIndex]; + removeHighlights(ownerID: string): void { + this._activeHighlightInfo.get(ownerID)?.matches.forEach(match => { + match.highlightResult?.dispose(); + }); + this._activeHighlightInfo.delete(ownerID); + } + + highlightCurrentMatch(index: number, ownerID: string) { + const highlightInfo = this._activeHighlightInfo.get(ownerID); + if (!highlightInfo) { + console.error('Modified current highlight match before adding highlight list.'); + return; + } + const oldMatch = highlightInfo.matches[highlightInfo.currentMatchIndex]; oldMatch?.highlightResult?.update(matchColor, oldMatch.isShadow ? undefined : 'find-match'); - const match = this.matches[index]; - this._findMatchIndex = index; + const match = highlightInfo.matches[index]; + highlightInfo.currentMatchIndex = index; const sel = window.getSelection(); if (!!match && !!sel && match.highlightResult) { let offset = 0; @@ -961,14 +988,18 @@ async function webviewPreloads(ctx: PreloadContext) { match.highlightResult?.update(currentMatchColor, match.isShadow ? undefined : 'current-find-match'); document.getSelection()?.removeAllRanges(); - postNotebookMessage('didFindHighlight', { + postNotebookMessage('didFindHighlightCurrent', { offset }); } } - unHighlightCurrentMatch(index: number) { - const oldMatch = this.matches[index]; + unHighlightCurrentMatch(index: number, ownerID: string) { + const highlightInfo = this._activeHighlightInfo.get(ownerID); + if (!highlightInfo) { + return; + } + const oldMatch = highlightInfo.matches[index]; if (oldMatch && oldMatch.highlightResult) { oldMatch.highlightResult.update(matchColor, oldMatch.isShadow ? undefined : 'find-match'); } @@ -976,37 +1007,76 @@ async function webviewPreloads(ctx: PreloadContext) { dispose() { document.getSelection()?.removeAllRanges(); - - this.matches.forEach(match => { - match.highlightResult?.dispose(); + this._activeHighlightInfo.forEach(highlightInfo => { + highlightInfo.matches.forEach(match => { + match.highlightResult?.dispose(); + }); }); } } class CSSHighlighter implements IHighlighter { + private _activeHighlightInfo: Map; private _matchesHighlight: Highlight; private _currentMatchesHighlight: Highlight; - private _findMatchIndex = -1; - constructor( - readonly matches: IFindMatch[], - ) { + constructor() { + this._activeHighlightInfo = new Map(); this._matchesHighlight = new Highlight(); this._matchesHighlight.priority = 1; this._currentMatchesHighlight = new Highlight(); this._currentMatchesHighlight.priority = 2; + CSS.highlights?.set(`find-highlight`, this._matchesHighlight); + CSS.highlights?.set(`current-find-highlight`, this._currentMatchesHighlight); + } + + _refreshRegistry(updateMatchesHighlight = true) { + // for performance reasons, only update the full list of highlights when we need to + if (updateMatchesHighlight) { + this._matchesHighlight.clear(); + } + + this._currentMatchesHighlight.clear(); + + this._activeHighlightInfo.forEach((highlightInfo) => { + + if (updateMatchesHighlight) { + for (let i = 0; i < highlightInfo.matches.length; i++) { + this._matchesHighlight.add(highlightInfo.matches[i].originalRange); + } + } + if (highlightInfo.currentMatchIndex < highlightInfo.matches.length && highlightInfo.currentMatchIndex >= 0) { + this._currentMatchesHighlight.add(highlightInfo.matches[highlightInfo.currentMatchIndex].originalRange); + } + }); + } + + addHighlights( + matches: IFindMatch[], + ownerID: string + ) { for (let i = 0; i < matches.length; i++) { this._matchesHighlight.add(matches[i].originalRange); } - CSS.highlights?.set('find-highlight', this._matchesHighlight); - CSS.highlights?.set('current-find-highlight', this._currentMatchesHighlight); + + const newEntry: IHighlightInfo = { + matches, + currentMatchIndex: -1, + }; + + this._activeHighlightInfo.set(ownerID, newEntry); } - highlightCurrentMatch(index: number): void { - this._findMatchIndex = index; - const match = this.matches[this._findMatchIndex]; - const range = match.originalRange; + highlightCurrentMatch(index: number, ownerID: string): void { + const highlightInfo = this._activeHighlightInfo.get(ownerID); + if (!highlightInfo) { + console.error('Modified current highlight match before adding highlight list.'); + return; + } + + highlightInfo.currentMatchIndex = index; + const match = highlightInfo.matches[index]; if (match) { let offset = 0; @@ -1015,20 +1085,28 @@ async function webviewPreloads(ctx: PreloadContext) { match.originalRange.startContainer.parentElement?.scrollIntoView({ behavior: 'auto', block: 'end', inline: 'nearest' }); const rangeOffset = match.originalRange.getBoundingClientRect().top; offset = rangeOffset - outputOffset; - postNotebookMessage('didFindHighlight', { + postNotebookMessage('didFindHighlightCurrent', { offset }); } catch (e) { console.error(e); } } - - this._currentMatchesHighlight.clear(); - this._currentMatchesHighlight.add(range); + this._refreshRegistry(false); } - unHighlightCurrentMatch(index: number): void { - this._currentMatchesHighlight.clear(); + unHighlightCurrentMatch(index: number, ownerID: string): void { + const highlightInfo = this._activeHighlightInfo.get(ownerID); + if (!highlightInfo) { + return; + } + + highlightInfo.currentMatchIndex = -1; + } + + removeHighlights(ownerID: string) { + this._activeHighlightInfo.delete(ownerID); + this._refreshRegistry(); } dispose(): void { @@ -1038,6 +1116,8 @@ async function webviewPreloads(ctx: PreloadContext) { } } + const _highlighter = (CSS.highlights) ? new CSSHighlighter() : new JSHighlighter(); + function extractSelectionLine(selection: Selection): ISearchPreviewInfo { const range = selection.getRangeAt(0); @@ -1126,7 +1206,7 @@ async function webviewPreloads(ctx: PreloadContext) { return offset + getSelectionOffsetRelativeTo(parentElement, currentNode.parentNode); } - const find = (query: string, options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean; shouldGetSearchPreviewInfo: boolean }) => { + const find = (query: string, options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean; shouldGetSearchPreviewInfo: boolean; ownerID: string }) => { let find = true; const matches: IFindMatch[] = []; @@ -1254,12 +1334,7 @@ async function webviewPreloads(ctx: PreloadContext) { console.log(e); } - if (matches.length && CSS.highlights) { - _highlighter = new CSSHighlighter(matches); - } else { - _highlighter = new JSHighlighter(matches); - } - + _highlighter.addHighlights(matches, options.ownerID); document.getSelection()?.removeAllRanges(); viewModel.toggleDragDropEnabled(currentOptions.dragAndDropEnabled); @@ -1448,20 +1523,20 @@ async function webviewPreloads(ctx: PreloadContext) { break; } case 'find': { - _highlighter?.dispose(); + _highlighter.removeHighlights(event.data.options.ownerID); find(event.data.query, event.data.options); break; } - case 'findHighlight': { - _highlighter?.highlightCurrentMatch(event.data.index); + case 'findHighlightCurrent': { + _highlighter?.highlightCurrentMatch(event.data.index, event.data.ownerID); break; } - case 'findUnHighlight': { - _highlighter?.unHighlightCurrentMatch(event.data.index); + case 'findUnHighlightCurrent': { + _highlighter?.unHighlightCurrentMatch(event.data.index, event.data.ownerID); break; } case 'findStop': { - _highlighter?.dispose(); + _highlighter.removeHighlights(event.data.ownerID); break; } case 'returnOutputItem': { diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index c8ac2befe3a..fd3cf5fdcb9 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -286,6 +286,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic override deltaCellDecorations() { return []; } override onDidChangeVisibleRanges = Event.None; override visibleRanges: ICellRange[] = [{ start: 0, end: 100 }]; + override getId(): string { return ''; } }; return { editor: notebookEditor, viewModel }; diff --git a/src/vs/workbench/contrib/search/browser/notebookSearch.ts b/src/vs/workbench/contrib/search/browser/notebookSearch.ts index 4a040c661a1..2fb52658219 100644 --- a/src/vs/workbench/contrib/search/browser/notebookSearch.ts +++ b/src/vs/workbench/contrib/search/browser/notebookSearch.ts @@ -14,5 +14,5 @@ export interface INotebookSearchService { readonly _serviceBrand: undefined; - notebookSearch(query: ITextQuery, token: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise<{ completeData: ISearchComplete; scannedFiles: ResourceSet }>; + notebookSearch(query: ITextQuery, token: CancellationToken, searchInstanceID: string, onProgress?: (result: ISearchProgressItem) => void): Promise<{ completeData: ISearchComplete; scannedFiles: ResourceSet }>; } diff --git a/src/vs/workbench/contrib/search/browser/notebookSearchService.ts b/src/vs/workbench/contrib/search/browser/notebookSearchService.ts index 711932ae636..b6209cf8de6 100644 --- a/src/vs/workbench/contrib/search/browser/notebookSearchService.ts +++ b/src/vs/workbench/contrib/search/browser/notebookSearchService.ts @@ -123,7 +123,7 @@ export class NotebookSearchService implements INotebookSearchService { return Array.from(uris.keys()); } - async notebookSearch(query: ITextQuery, token: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise<{ completeData: ISearchComplete; scannedFiles: ResourceSet }> { + async notebookSearch(query: ITextQuery, token: CancellationToken, searchInstanceID: string, onProgress?: (result: ISearchProgressItem) => void): Promise<{ completeData: ISearchComplete; scannedFiles: ResourceSet }> { if (query.type !== QueryType.Text) { return { @@ -139,7 +139,7 @@ export class NotebookSearchService implements INotebookSearchService { const localNotebookWidgets = this.getLocalNotebookWidgets(); const localNotebookFiles = localNotebookWidgets.map(widget => widget.viewModel!.uri); - const localResultPromise = this.getLocalNotebookResults(query, token, localNotebookWidgets); + const localResultPromise = this.getLocalNotebookResults(query, token, localNotebookWidgets, searchInstanceID); const searchLocalEnd = Date.now(); const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental?.closedNotebookRichContentResults ?? false; @@ -248,7 +248,7 @@ export class NotebookSearchService implements INotebookSearchService { }; } - private async getLocalNotebookResults(query: ITextQuery, token: CancellationToken, widgets: Array): Promise { + private async getLocalNotebookResults(query: ITextQuery, token: CancellationToken, widgets: Array, searchID: string): Promise { const localResults = new ResourceMap(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); let limitHit = false; @@ -266,7 +266,7 @@ export class NotebookSearchService implements INotebookSearchService { includeMarkupPreview: query.contentPattern.notebookInfo?.isInNotebookMarkdownPreview, includeCodeInput: query.contentPattern.notebookInfo?.isInNotebookCellInput, includeOutput: query.contentPattern.notebookInfo?.isInNotebookCellOutput, - }, token, false, true); + }, token, false, true, searchID); if (matches.length) { diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 76b513ea17b..46c2d29079f 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -397,6 +397,7 @@ export class FileMatch extends Disposable implements IFileMatch { private _parent: FolderMatch, private rawMatch: IFileMatch, private _closestRoot: FolderMatchWorkspaceRoot | null, + private readonly searchInstanceID: string, @IModelService private readonly modelService: IModelService, @IReplaceService private readonly replaceService: IReplaceService, @ILabelService readonly labelService: ILabelService, @@ -763,7 +764,7 @@ export class FileMatch extends Disposable implements IFileMatch { } this._findMatchDecorationModel?.stopWebviewFind(); this._findMatchDecorationModel?.dispose(); - this._findMatchDecorationModel = new FindMatchDecorationModel(this._notebookEditorWidget); + this._findMatchDecorationModel = new FindMatchDecorationModel(this._notebookEditorWidget, this.searchInstanceID); } private _removeNotebookHighlights(): void { @@ -851,7 +852,7 @@ export class FileMatch extends Disposable implements IFileMatch { includeMarkupPreview: this._query.notebookInfo?.isInNotebookMarkdownPreview, includeCodeInput: this._query.notebookInfo?.isInNotebookCellInput, includeOutput: this._query.notebookInfo?.isInNotebookCellOutput, - }, CancellationToken.None, false, true); + }, CancellationToken.None, false, true, this.searchInstanceID); this.updateNotebookMatches(allMatches, true); } @@ -1117,7 +1118,7 @@ export class FolderMatch extends Disposable { return this._query; } - addFileMatch(raw: IFileMatch[], silent: boolean): void { + addFileMatch(raw: IFileMatch[], silent: boolean, searchInstanceID: string): void { // when adding a fileMatch that has intermediate directories const added: FileMatch[] = []; const updated: FileMatch[] = []; @@ -1151,7 +1152,7 @@ export class FolderMatch extends Disposable { existingFileMatch.addContext(rawFileMatch.results); } else { if (this instanceof FolderMatchWorkspaceRoot || this instanceof FolderMatchNoRoot) { - const fileMatch = this.createAndConfigureFileMatch(rawFileMatch); + const fileMatch = this.createAndConfigureFileMatch(rawFileMatch, searchInstanceID); added.push(fileMatch); } } @@ -1337,7 +1338,7 @@ export class FolderMatchWorkspaceRoot extends FolderMatchWithResource { return this.uriIdentityService.extUri.isEqual(uri1, ur2); } - private createFileMatch(query: IPatternInfo, previewOptions: ITextSearchPreviewOptions | undefined, maxResults: number | undefined, parent: FolderMatch, rawFileMatch: IFileMatch, closestRoot: FolderMatchWorkspaceRoot | null,): FileMatch { + private createFileMatch(query: IPatternInfo, previewOptions: ITextSearchPreviewOptions | undefined, maxResults: number | undefined, parent: FolderMatch, rawFileMatch: IFileMatch, closestRoot: FolderMatchWorkspaceRoot | null, searchInstanceID: string): FileMatch { const fileMatch = this.instantiationService.createInstance( FileMatch, @@ -1346,7 +1347,8 @@ export class FolderMatchWorkspaceRoot extends FolderMatchWithResource { maxResults, parent, rawFileMatch, - closestRoot + closestRoot, + searchInstanceID ); parent.doAddFile(fileMatch); const disposable = fileMatch.onChange(({ didRemove }) => parent.onFileChange(fileMatch, didRemove)); @@ -1354,7 +1356,7 @@ export class FolderMatchWorkspaceRoot extends FolderMatchWithResource { return fileMatch; } - createAndConfigureFileMatch(rawFileMatch: IFileMatch): FileMatch { + createAndConfigureFileMatch(rawFileMatch: IFileMatch, searchInstanceID: string): FileMatch { if (!this.uriHasParent(this.resource, rawFileMatch.resource)) { throw Error(`${rawFileMatch.resource} is not a descendant of ${this.resource}`); @@ -1382,7 +1384,7 @@ export class FolderMatchWorkspaceRoot extends FolderMatchWithResource { parent = folderMatch; } - return this.createFileMatch(this._query.contentPattern, this._query.previewOptions, this._query.maxResults, parent, rawFileMatch, root); + return this.createFileMatch(this._query.contentPattern, this._query.previewOptions, this._query.maxResults, parent, rawFileMatch, root, searchInstanceID); } } @@ -1401,14 +1403,15 @@ export class FolderMatchNoRoot extends FolderMatch { super(null, _id, _index, _query, _parent, _searchModel, null, replaceService, instantiationService, labelService, uriIdentityService); } - createAndConfigureFileMatch(rawFileMatch: IFileMatch): FileMatch { + createAndConfigureFileMatch(rawFileMatch: IFileMatch, searchInstanceID: string): FileMatch { const fileMatch = this.instantiationService.createInstance( FileMatch, this._query.contentPattern, this._query.previewOptions, this._query.maxResults, this, rawFileMatch, - null); + null, + searchInstanceID); this.doAddFile(fileMatch); const disposable = fileMatch.onChange(({ didRemove }) => this.onFileChange(fileMatch, didRemove)); fileMatch.onDispose(() => disposable.dispose()); @@ -1740,7 +1743,7 @@ export class SearchResult extends Disposable { return this._searchModel; } - add(allRaw: IFileMatch[], silent: boolean = false): void { + add(allRaw: IFileMatch[], searchInstanceID: string, silent: boolean = false): void { // Split up raw into a list per folder so we can do a batch add per folder. const { byFolder, other } = this.groupFilesByFolder(allRaw); @@ -1750,10 +1753,10 @@ export class SearchResult extends Disposable { } const folderMatch = this.getFolderMatch(raw[0].resource); - folderMatch?.addFileMatch(raw, silent); + folderMatch?.addFileMatch(raw, silent, searchInstanceID); }); - this._otherFilesMatch?.addFileMatch(other, silent); + this._otherFilesMatch?.addFileMatch(other, silent, searchInstanceID); this.disposePastResults(); } @@ -1991,16 +1994,16 @@ export class SearchModel extends Disposable { return this._searchResult; } - private async doSearch(query: ITextQuery, progressEmitter: Emitter, searchQuery: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { + private async doSearch(query: ITextQuery, progressEmitter: Emitter, searchQuery: ITextQuery, searchInstanceID: string, onProgress?: (result: ISearchProgressItem) => void): Promise { const searchStart = Date.now(); const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource(); const onProgressCall = (p: ISearchProgressItem) => { progressEmitter.fire(); - this.onSearchProgress(p); + this.onSearchProgress(p, searchInstanceID); onProgress?.(p); }; - const notebookResult = await this.notebookSearchService.notebookSearch(query, this.currentCancelTokenSource.token, onProgressCall); + const notebookResult = await this.notebookSearchService.notebookSearch(query, this.currentCancelTokenSource.token, searchInstanceID, onProgressCall); const currentResult = await this.searchService.textSearch( searchQuery, this.currentCancelTokenSource.token, onProgressCall, @@ -2019,6 +2022,7 @@ export class SearchModel extends Disposable { if (!this.searchConfig.searchOnType) { this.searchResult.clear(); } + const searchInstanceID = Date.now().toString(); this._searchResult.query = this._searchQuery; @@ -2028,7 +2032,7 @@ export class SearchModel extends Disposable { // In search on type case, delay the streaming of results just a bit, so that we don't flash the only "local results" fast path this._startStreamDelay = new Promise(resolve => setTimeout(resolve, this.searchConfig.searchOnType ? 150 : 0)); - const currentRequest = this.doSearch(query, progressEmitter, this._searchQuery, onProgress); + const currentRequest = this.doSearch(query, progressEmitter, this._searchQuery, searchInstanceID, onProgress); const start = Date.now(); @@ -2043,7 +2047,7 @@ export class SearchModel extends Disposable { }); currentRequest.then( - value => this.onSearchCompleted(value, Date.now() - start), + value => this.onSearchCompleted(value, Date.now() - start, searchInstanceID), e => this.onSearchError(e, Date.now() - start)); try { @@ -2059,12 +2063,12 @@ export class SearchModel extends Disposable { } } - private onSearchCompleted(completed: ISearchComplete | null, duration: number): ISearchComplete | null { + private onSearchCompleted(completed: ISearchComplete | null, duration: number, searchInstanceID: string): ISearchComplete | null { if (!this._searchQuery) { throw new Error('onSearchCompleted must be called after a search is started'); } - this._searchResult.add(this._resultQueue); + this._searchResult.add(this._resultQueue, searchInstanceID); this._resultQueue.length = 0; const options: IPatternInfo = Object.assign({}, this._searchQuery.contentPattern); @@ -2108,17 +2112,17 @@ export class SearchModel extends Disposable { this.searchCancelledForNewSearch ? { exit: SearchCompletionExitCode.NewSearchStarted, results: [], messages: [] } : null, - duration); + duration, ''); this.searchCancelledForNewSearch = false; } } - private async onSearchProgress(p: ISearchProgressItem) { + private async onSearchProgress(p: ISearchProgressItem, searchInstanceID: string) { if ((p).resource) { this._resultQueue.push(p); await this._startStreamDelay; if (this._resultQueue.length) { - this._searchResult.add(this._resultQueue, true); + this._searchResult.add(this._resultQueue, searchInstanceID, true); this._resultQueue.length = 0; } } diff --git a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index fd518ce666c..7d47ec0a8bf 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -120,7 +120,7 @@ suite('Search Actions', () => { }, searchModel.searchResult, searchModel, null); return instantiationService.createInstance(FileMatch, { pattern: '' - }, undefined, undefined, folderMatch, rawMatch, null); + }, undefined, undefined, folderMatch, rawMatch, null, ''); } function aMatch(fileMatch: FileMatch): Match { diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index a3995b4ccc8..6a561cb325e 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -160,7 +160,7 @@ suite('SearchModel', () => { function notebookSearchServiceWithInfo(results: IFileMatchWithCells[], tokenSource: CancellationTokenSource | undefined): INotebookSearchService { return { _serviceBrand: undefined, - notebookSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise<{ completeData: ISearchComplete; scannedFiles: ResourceSet }> { + notebookSearch(query: ISearchQuery, token: CancellationToken, searchInstanceID: string, onProgress?: (result: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise<{ completeData: ISearchComplete; scannedFiles: ResourceSet }> { token?.onCancellationRequested(() => tokenSource?.cancel()); const localResults = new ResourceMap(uri => uri.path); diff --git a/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts index c8e419dd373..8f807a2a739 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts @@ -208,7 +208,7 @@ suite('searchNotebookHelpers', () => { }, searchModel.searchResult, searchModel, null); return instantiationService.createInstance(FileMatch, { pattern: '' - }, undefined, undefined, folderMatch, rawMatch, null); + }, undefined, undefined, folderMatch, rawMatch, null, ''); } }); }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index 49a7afe9c09..50500494d9a 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -31,7 +31,7 @@ import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/brows import { ICellMatch, IFileMatchWithCells } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { createFileUriFromPathFromRoot, getRootName } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; +import { addToSearchResult, createFileUriFromPathFromRoot, getRootName } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; const lineOneRange = new OneLineRange(1, 0, 1); @@ -176,7 +176,7 @@ suite('SearchResult', () => { new TextSearchMatch('preview 1', new OneLineRange(1, 4, 11)), new TextSearchMatch('preview 2', lineOneRange))]; - testObject.add(target); + addToSearchResult(testObject, target); assert.strictEqual(3, testObject.count()); @@ -206,7 +206,7 @@ suite('SearchResult', () => { aRawMatch('/2', new TextSearchMatch('preview 2', lineOneRange))]; - testObject.add(target); + addToSearchResult(testObject, target); assert.strictEqual(3, testObject.count()); @@ -261,7 +261,7 @@ suite('SearchResult', () => { }); const target = [fileMatch1, fileMatch2]; - testObject.add(target); + addToSearchResult(testObject, target); assert.strictEqual(6, testObject.count()); assert.deepStrictEqual(fileMatch1.cellResults[0].contentResults, (addFileMatch.getCall(0).args[0][0] as IFileMatchWithCells).cellResults[0].contentResults); assert.deepStrictEqual(fileMatch1.cellResults[0].webviewResults, (addFileMatch.getCall(0).args[0][0] as IFileMatchWithCells).cellResults[0].webviewResults); @@ -274,7 +274,7 @@ suite('SearchResult', () => { const target2 = sinon.spy(); const testObject = aSearchResult(); - testObject.add([ + addToSearchResult(testObject, [ aRawMatch('/1', new TextSearchMatch('preview 1', lineOneRange)), aRawMatch('/2', @@ -293,7 +293,7 @@ suite('SearchResult', () => { test('remove triggers change event', function () { const target = sinon.spy(); const testObject = aSearchResult(); - testObject.add([ + addToSearchResult(testObject, [ aRawMatch('/1', new TextSearchMatch('preview 1', lineOneRange))]); const objectToRemove = testObject.matches()[0]; @@ -308,7 +308,7 @@ suite('SearchResult', () => { test('remove array triggers change event', function () { const target = sinon.spy(); const testObject = aSearchResult(); - testObject.add([ + addToSearchResult(testObject, [ aRawMatch('/1', new TextSearchMatch('preview 1', lineOneRange)), aRawMatch('/2', @@ -324,7 +324,7 @@ suite('SearchResult', () => { test('Removing all line matches and adding back will add file back to result', function () { const testObject = aSearchResult(); - testObject.add([ + addToSearchResult(testObject, [ aRawMatch('/1', new TextSearchMatch('preview 1', lineOneRange))]); const target = testObject.matches()[0]; @@ -342,7 +342,7 @@ suite('SearchResult', () => { const voidPromise = Promise.resolve(null); instantiationService.stub(IReplaceService, 'replace', voidPromise); const testObject = aSearchResult(); - testObject.add([ + addToSearchResult(testObject, [ aRawMatch('/1', new TextSearchMatch('preview 1', lineOneRange))]); @@ -356,7 +356,7 @@ suite('SearchResult', () => { const voidPromise = Promise.resolve(null); instantiationService.stub(IReplaceService, 'replace', voidPromise); const testObject = aSearchResult(); - testObject.add([ + addToSearchResult(testObject, [ aRawMatch('/1', new TextSearchMatch('preview 1', lineOneRange))]); testObject.onChange(target); @@ -374,7 +374,7 @@ suite('SearchResult', () => { const voidPromise = Promise.resolve(null); instantiationService.stubPromise(IReplaceService, 'replace', voidPromise); const testObject = aSearchResult(); - testObject.add([ + addToSearchResult(testObject, [ aRawMatch('/1', new TextSearchMatch('preview 1', lineOneRange)), aRawMatch('/2', @@ -519,7 +519,7 @@ suite('SearchResult', () => { const root = searchResult?.folderMatches()[0]; return instantiationService.createInstance(FileMatch, { pattern: '' - }, undefined, undefined, root, rawMatch, null); + }, undefined, undefined, root, rawMatch, null, ''); } function aSearchResult(): SearchResult { @@ -569,7 +569,7 @@ suite('SearchResult', () => { ] }; - testObject.add([ + addToSearchResult(testObject, [ aRawMatch('/voo/foo.a', new TextSearchMatch('preview 1', lineOneRange), new TextSearchMatch('preview 2', lineOneRange)), aRawMatch('/with/path/bar.b', @@ -623,7 +623,7 @@ suite('SearchResult', () => { * |- eyy.y */ - testObject.add([ + addToSearchResult(testObject, [ aRawMatch('/voo/foo.a', new TextSearchMatch('preview 1', lineOneRange), new TextSearchMatch('preview 2', lineOneRange)), aRawMatch('/voo/beep/foo.c', diff --git a/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts b/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts index 0c2a6ce3eac..026df47ea9c 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts @@ -14,7 +14,9 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; +import { SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IFileMatch } from 'vs/workbench/services/search/common/search'; import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; export function createFileUriFromPathFromRoot(path?: string): URI { @@ -38,8 +40,6 @@ export function getRootName(): string { } } - - export function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IThemeService, new TestThemeService()); const config = new TestConfigurationService(); @@ -52,3 +52,7 @@ export function stubNotebookEditorService(instantiationService: TestInstantiatio instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); return instantiationService.createInstance(NotebookEditorWidgetService); } + +export function addToSearchResult(searchResult: SearchResult, allRaw: IFileMatch[], searchInstanceID = '') { + searchResult.add(allRaw, searchInstanceID); +} diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index 1624ba20a46..014e7c143cc 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -78,7 +78,7 @@ suite('Search - Viewlet', () => { endColumn: 1 } }] - }]); + }], ''); const fileMatch = result.matches()[0]; const lineMatch = fileMatch.matches()[0]; @@ -181,7 +181,7 @@ suite('Search - Viewlet', () => { }; return instantiation.createInstance(FileMatch, { pattern: '' - }, undefined, undefined, parentFolder ?? aFolderMatch('', 0), rawMatch, null); + }, undefined, undefined, parentFolder ?? aFolderMatch('', 0), rawMatch, null, ''); } function aFolderMatch(path: string, index: number, parent?: SearchResult): FolderMatch { From d441153e31584a6e98322dae90933af7ca5b82b3 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 13 Jul 2023 17:14:37 -0700 Subject: [PATCH 121/826] Disable followup questions in the inputOnTop mode (#187884) For now, until we iron out what they should look like. --- .../contrib/chat/browser/chatInputPart.ts | 15 ++++++--------- .../workbench/contrib/chat/browser/chatWidget.ts | 6 +++--- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 92af40f0f16..eeef6b5f5f4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -77,7 +77,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge constructor( // private readonly editorOptions: ChatEditorOptions, // TODO this should be used - private readonly options: { renderFollowupsBelow: boolean }, + private readonly options: { renderFollowups: boolean }, @IChatWidgetHistoryService private readonly historyService: IChatWidgetHistoryService, @IModelService private readonly modelService: IModelService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -178,14 +178,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge render(container: HTMLElement, initialValue: string, widget: IChatWidget) { this.container = dom.append(container, $('.interactive-input-part')); - let inputContainer: HTMLElement; - if (this.options.renderFollowupsBelow) { - inputContainer = dom.append(this.container, $('.interactive-input-and-toolbar')); - this.followupsContainer = dom.append(this.container, $('.interactive-input-followups')); - } else { - this.followupsContainer = dom.append(this.container, $('.interactive-input-followups')); - inputContainer = dom.append(this.container, $('.interactive-input-and-toolbar')); - } + this.followupsContainer = dom.append(this.container, $('.interactive-input-followups')); + const inputContainer = dom.append(this.container, $('.interactive-input-and-toolbar')); const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(inputContainer)); CONTEXT_IN_CHAT_INPUT.bindTo(inputScopedContextKeyService).set(true); @@ -256,6 +250,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } async renderFollowups(items?: IChatReplyFollowup[]): Promise { + if (!this.options.renderFollowups) { + return; + } this.followupsDisposables.clear(); dom.clearNode(this.followupsContainer); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index c40f97ce4dc..bea6bc2f7a9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -142,7 +142,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.container = dom.append(parent, $('.interactive-session')); if (renderInputOnTop) { - this.createInput(this.container, { renderFollowupsBelow: true }); + this.createInput(this.container, { renderFollowups: false }); this.listContainer = dom.append(this.container, $(`.interactive-list`)); } else { this.listContainer = dom.append(this.container, $(`.interactive-list`)); @@ -334,8 +334,8 @@ export class ChatWidget extends Disposable implements IChatWidget { this.previousTreeScrollHeight = this.tree.scrollHeight; } - private createInput(container: HTMLElement, options?: { renderFollowupsBelow: boolean }): void { - this.inputPart = this.instantiationService.createInstance(ChatInputPart, { renderFollowupsBelow: options?.renderFollowupsBelow ?? false }); + private createInput(container: HTMLElement, options?: { renderFollowups: boolean }): void { + this.inputPart = this.instantiationService.createInstance(ChatInputPart, { renderFollowups: options?.renderFollowups ?? true }); this.inputPart.render(container, '', this); this._register(this.inputPart.onDidFocus(() => this._onDidFocus.fire())); From 7bd35446a584a38e41067a6c5b43ba1ddfbdc61e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 13 Jul 2023 18:04:19 -0700 Subject: [PATCH 122/826] cli: fix windows service-mode not working (#187883) It seems like we need to run the server (a batch file) with cmd explicitly when the server itself is not run from a command prompt. --- cli/src/tunnels/code_server.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cli/src/tunnels/code_server.rs b/cli/src/tunnels/code_server.rs index 1246e1c9441..7c044fc70cb 100644 --- a/cli/src/tunnels/code_server.rs +++ b/cli/src/tunnels/code_server.rs @@ -566,7 +566,17 @@ impl<'a> ServerBuilder<'a> { } fn get_base_command(&self) -> Command { + #[cfg(not(windows))] let mut cmd = Command::new(&self.server_paths.executable); + #[cfg(windows)] + let mut cmd = { + let mut cmd = Command::new("cmd"); + cmd.arg("/Q"); + cmd.arg("/C"); + cmd.arg(&self.server_paths.executable); + cmd + }; + cmd.stdin(std::process::Stdio::null()) .args(self.server_params.code_server_args.command_arguments()); cmd From e024a5c28063c64b3dd571bada6ec42d37c2f8a0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 13 Jul 2023 19:51:11 -0700 Subject: [PATCH 123/826] fix compile errors --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 4 +--- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 2 +- .../contrib/codeEditor/browser/accessibility/accessibility.ts | 2 +- .../workbench/contrib/codeEditor/browser/diffEditorHelper.ts | 2 +- .../contrib/notebook/browser/notebookAccessibilityHelp.ts | 2 +- .../browser/terminal.accessibility.contribution.ts | 2 +- .../accessibility/browser/terminalAccessibilityHelp.ts | 2 +- 7 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 83892d5fb48..fd147f172ea 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -10,10 +10,8 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; - - +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'panelChat' | 'inlineChat'): string { const keybindingService = accessor.get(IKeybindingService); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 4f362ccf834..656485ed70a 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -38,7 +38,7 @@ import '../common/chatColors'; import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; import { registerClearActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index b39ed1353f8..903406a2947 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -9,7 +9,7 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { accessibilityHelpIsShown } from 'vs/workbench/browser/accessibility/accessibleView'; +import { accessibilityHelpIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { alert } from 'vs/base/browser/ui/aria/aria'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 5da8615c51c..174fbd3ac00 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -20,8 +20,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; const enum WidgetState { Hidden, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts index 5e45789ccb1..717b6167614 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts @@ -8,7 +8,7 @@ import { format } from 'vs/base/common/strings'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index baf38d2541b..26042ec1cb1 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -13,7 +13,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { terminalTabFocusModeContextKey } from 'vs/platform/terminal/common/terminal'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; -import { IAccessibleViewService } from 'vs/workbench/browser/accessibility/accessibleView'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts index 318b912982d..d5d73c308a1 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts @@ -10,7 +10,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ShellIntegrationStatus, WindowsShellType } from 'vs/platform/terminal/common/terminal'; -import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/browser/accessibility/accessibleView'; +import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal } from 'xterm'; From 12340da1f10186fee3aa4df448911b5bce99347d Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 13 Jul 2023 20:23:15 -0700 Subject: [PATCH 124/826] cli: allow installation as a service from the UI (#187869) - When turning on remote tunnel access, a quickpick is now shown asking users whether it should be installed as a service or just run in the session. - Picking the service install will install the tunnel as a service on the machine, and start it. - Turning off remote tunnel access will uninstall the service only if we were the ones to install it. - This involved some refactoring to add extra state to the RemoteTunnelService. There's now a "mode" that includes the previous "session" and reflects the desired end state. - I also did a cleanup with a `StreamSplitter` to ensure output of the CLI gets read line-by-line. This was depended upon by the remote tunnel service code, but it's not actually guaranteed. - Changes in the CLI: allow setting the tunnel name while installing the service, and make both service un/installation and renames idempotent. Closes https://github.com/microsoft/vscode/issues/184663 --- cli/src/commands/args.rs | 4 + cli/src/commands/tunnels.rs | 21 +- cli/src/rpc.rs | 14 +- cli/src/tunnels/dev_tunnels.rs | 40 ++-- cli/src/tunnels/service_windows.rs | 9 +- src/vs/base/common/buffer.ts | 94 ++++---- src/vs/base/node/nodeStreams.ts | 62 ++++++ src/vs/base/test/node/nodeStreams.test.ts | 51 +++++ .../remoteTunnel/common/remoteTunnel.ts | 26 ++- .../remoteTunnel/node/remoteTunnelService.ts | 205 +++++++++++++----- .../remoteTunnel.contribution.ts | 164 ++++++++------ 11 files changed, 491 insertions(+), 199 deletions(-) create mode 100644 src/vs/base/node/nodeStreams.ts create mode 100644 src/vs/base/test/node/nodeStreams.test.ts diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index ad961496bcc..a97e1fc3870 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -649,6 +649,10 @@ pub struct TunnelServiceInstallArgs { /// If set, the user accepts the server license terms and the server will be started without a user prompt. #[clap(long)] pub accept_server_license_terms: bool, + + /// Sets the machine name for port forwarding service + #[clap(long)] + pub name: Option, } #[derive(Args, Debug, Clone)] diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index d11c15ef1e3..24e349bb720 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -135,10 +135,17 @@ pub async fn service( let manager = create_service_manager(ctx.log.clone(), &ctx.paths); match service_args { TunnelServiceSubCommands::Install(args) => { - // ensure logged in, otherwise subsequent serving will fail - Auth::new(&ctx.paths, ctx.log.clone()) - .get_credential() - .await?; + let auth = Auth::new(&ctx.paths, ctx.log.clone()); + + if let Some(name) = &args.name { + // ensure the name matches, and tunnel exists + dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths) + .rename_tunnel(name) + .await?; + } else { + // still ensure they're logged in, otherwise subsequent serving will fail + auth.get_credential().await?; + } // likewise for license consent legal::require_consent(&ctx.paths, args.accept_server_license_terms)?; @@ -203,20 +210,20 @@ pub async fn user(ctx: CommandContext, user_args: TunnelUserSubCommands) -> Resu Ok(0) } -/// Remove the tunnel used by this gateway, if any. +/// Remove the tunnel used by this tunnel, if any. pub async fn rename(ctx: CommandContext, rename_args: TunnelRenameArgs) -> Result { let auth = Auth::new(&ctx.paths, ctx.log.clone()); let mut dt = dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths); dt.rename_tunnel(&rename_args.name).await?; ctx.log.result(format!( - "Successfully renamed this gateway to {}", + "Successfully renamed this tunnel to {}", &rename_args.name )); Ok(0) } -/// Remove the tunnel used by this gateway, if any. +/// Remove the tunnel used by this tunnel, if any. pub async fn unregister(ctx: CommandContext) -> Result { let auth = Auth::new(&ctx.paths, ctx.log.clone()); let mut dt = dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths); diff --git a/cli/src/rpc.rs b/cli/src/rpc.rs index acd53dc38e0..a9a66153735 100644 --- a/cli/src/rpc.rs +++ b/cli/src/rpc.rs @@ -117,7 +117,7 @@ impl RpcMethodBuilder { Ok(p) => p, Err(err) => { return id.map(|id| { - serial.serialize(&ErrorResponse { + serial.serialize(ErrorResponse { id, error: ResponseError { code: 0, @@ -131,7 +131,7 @@ impl RpcMethodBuilder { match callback(param.params, &context) { Ok(result) => id.map(|id| serial.serialize(&SuccessResponse { id, result })), Err(err) => id.map(|id| { - serial.serialize(&ErrorResponse { + serial.serialize(ErrorResponse { id, error: ResponseError { code: -1, @@ -161,7 +161,7 @@ impl RpcMethodBuilder { Ok(p) => p, Err(err) => { return future::ready(id.map(|id| { - serial.serialize(&ErrorResponse { + serial.serialize(ErrorResponse { id, error: ResponseError { code: 0, @@ -182,7 +182,7 @@ impl RpcMethodBuilder { id.map(|id| serial.serialize(&SuccessResponse { id, result })) } Err(err) => id.map(|id| { - serial.serialize(&ErrorResponse { + serial.serialize(ErrorResponse { id, error: ResponseError { code: -1, @@ -222,7 +222,7 @@ impl RpcMethodBuilder { return ( None, future::ready(id.map(|id| { - serial.serialize(&ErrorResponse { + serial.serialize(ErrorResponse { id, error: ResponseError { code: 0, @@ -255,7 +255,7 @@ impl RpcMethodBuilder { match callback(servers, param.params, context).await { Ok(r) => id.map(|id| serial.serialize(&SuccessResponse { id, result: r })), Err(err) => id.map(|id| { - serial.serialize(&ErrorResponse { + serial.serialize(ErrorResponse { id, error: ResponseError { code: -1, @@ -427,7 +427,7 @@ impl RpcDispatcher { Some(Method::Async(callback)) => MaybeSync::Future(callback(id, body)), Some(Method::Duplex(callback)) => MaybeSync::Stream(callback(id, body)), None => MaybeSync::Sync(id.map(|id| { - self.serializer.serialize(&ErrorResponse { + self.serializer.serialize(ErrorResponse { id, error: ResponseError { code: -1, diff --git a/cli/src/tunnels/dev_tunnels.rs b/cli/src/tunnels/dev_tunnels.rs index 8476028a2f5..c4ca9741b88 100644 --- a/cli/src/tunnels/dev_tunnels.rs +++ b/cli/src/tunnels/dev_tunnels.rs @@ -275,7 +275,9 @@ impl DevTunnels { /// Renames the current tunnel to the new name. pub async fn rename_tunnel(&mut self, name: &str) -> Result<(), AnyError> { - self.update_tunnel_name(None, name).await.map(|_| ()) + self.update_tunnel_name(self.launcher_tunnel.load(), name) + .await + .map(|_| ()) } /// Updates the name of the existing persisted tunnel to the new name. @@ -286,28 +288,34 @@ impl DevTunnels { name: &str, ) -> Result<(Tunnel, PersistedTunnel), AnyError> { let name = name.to_ascii_lowercase(); - self.check_is_name_free(&name).await?; - - debug!(self.log, "Tunnel name changed, applying updates..."); let (mut full_tunnel, mut persisted, is_new) = match persisted { Some(persisted) => { + debug!( + self.log, + "Found a persisted tunnel, seeing if the name matches..." + ); self.get_or_create_tunnel(persisted, Some(&name), NO_REQUEST_OPTIONS) .await } - None => self - .create_tunnel(&name, NO_REQUEST_OPTIONS) - .await - .map(|(pt, t)| (t, pt, true)), + None => { + debug!(self.log, "Creating a new tunnel with the requested name"); + self.create_tunnel(&name, NO_REQUEST_OPTIONS) + .await + .map(|(pt, t)| (t, pt, true)) + } }?; - if is_new { + let desired_tags = self.get_tags(&name); + if is_new || vec_eq_as_set(&full_tunnel.tags, &desired_tags) { return Ok((full_tunnel, persisted)); } - full_tunnel.tags = self.get_tags(&name); + debug!(self.log, "Tunnel name changed, applying updates..."); - let new_tunnel = spanf!( + full_tunnel.tags = desired_tags; + + let updated_tunnel = spanf!( self.log, self.log.span("dev-tunnel.tag.update"), self.client.update_tunnel(&full_tunnel, NO_REQUEST_OPTIONS) @@ -317,7 +325,7 @@ impl DevTunnels { persisted.name = name; self.launcher_tunnel.save(Some(persisted.clone()))?; - Ok((new_tunnel, persisted)) + Ok((updated_tunnel, persisted)) } /// Gets the persisted tunnel from the service, or creates a new one. @@ -443,6 +451,8 @@ impl DevTunnels { ) -> Result<(PersistedTunnel, Tunnel), AnyError> { info!(self.log, "Creating tunnel with the name: {}", name); + self.check_is_name_free(name).await?; + let mut tried_recycle = false; let new_tunnel = Tunnel { @@ -527,7 +537,7 @@ impl DevTunnels { options: &TunnelRequestOptions, ) -> Result { let new_tags = self.get_tags(name); - if vec_eq_unsorted(&tunnel.tags, &new_tags) { + if vec_eq_as_set(&tunnel.tags, &new_tags) { return Ok(tunnel); } @@ -610,7 +620,7 @@ impl DevTunnels { } async fn check_is_name_free(&mut self, name: &str) -> Result<(), AnyError> { - let existing = spanf!( + let existing: Vec = spanf!( self.log, self.log.span("dev-tunnel.rename.search"), self.client.list_all_tunnels(&TunnelRequestOptions { @@ -998,7 +1008,7 @@ fn clean_hostname_for_tunnel(hostname: &str) -> String { } } -fn vec_eq_unsorted(a: &[String], b: &[String]) -> bool { +fn vec_eq_as_set(a: &[String], b: &[String]) -> bool { if a.len() != b.len() { return false; } diff --git a/cli/src/tunnels/service_windows.rs b/cli/src/tunnels/service_windows.rs index 427eddd620d..3d2dc9f0c55 100644 --- a/cli/src/tunnels/service_windows.rs +++ b/cli/src/tunnels/service_windows.rs @@ -78,6 +78,7 @@ impl CliServiceManager for WindowsService { cmd.stderr(Stdio::null()); cmd.stdout(Stdio::null()); cmd.stdin(Stdio::null()); + cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS); cmd.spawn() .map_err(|e| wrapdbg(e, "error starting service"))?; @@ -121,8 +122,12 @@ impl CliServiceManager for WindowsService { async fn unregister(&self) -> Result<(), AnyError> { let key = WindowsService::open_key()?; - key.delete_value(TUNNEL_ACTIVITY_NAME) - .map_err(|e| AnyError::from(wrap(e, "error deleting registry key")))?; + match key.delete_value(TUNNEL_ACTIVITY_NAME) { + Ok(_) => {} + Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} + Err(e) => return Err(wrap(e, "error deleting registry key").into()), + } + info!(self.log, "Tunnel service uninstalled"); let r = do_single_rpc_call::<_, ()>( diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index ff61eb5c9e2..08736ab8c0b 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -172,53 +172,59 @@ export class VSBuffer { writeUInt8(this.buffer, value, offset); } - indexOf(subarray: VSBuffer | Uint8Array) { - const needle = subarray instanceof VSBuffer ? subarray.buffer : subarray; - const needleLen = needle.byteLength; - const haystack = this.buffer; - const haystackLen = haystack.byteLength; - - if (needleLen === 0) { - return 0; - } - - if (needleLen === 1) { - return haystack.indexOf(needle[0]); - } - - if (needleLen > haystackLen) { - return -1; - } - - // find index of the subarray using boyer-moore-horspool algorithm - const table = indexOfTable.value; - table.fill(needle.length); - for (let i = 0; i < needle.length; i++) { - table[needle[i]] = needle.length - i - 1; - } - - let i = needle.length - 1; - let j = i; - let result = -1; - while (i < haystackLen) { - if (haystack[i] === needle[j]) { - if (j === 0) { - result = i; - break; - } - - i--; - j--; - } else { - i += Math.max(needle.length - j, table[haystack[i]]); - j = needle.length - 1; - } - } - - return result; + indexOf(subarray: VSBuffer | Uint8Array, offset = 0) { + return binaryIndexOf(this.buffer, subarray instanceof VSBuffer ? subarray.buffer : subarray, offset); } } +/** + * Like String.indexOf, but works on Uint8Arrays. + * Uses the boyer-moore-horspool algorithm to be reasonably speedy. + */ +export function binaryIndexOf(haystack: Uint8Array, needle: Uint8Array, offset = 0): number { + const needleLen = needle.byteLength; + const haystackLen = haystack.byteLength; + + if (needleLen === 0) { + return 0; + } + + if (needleLen === 1) { + return haystack.indexOf(needle[0]); + } + + if (needleLen > haystackLen - offset) { + return -1; + } + + // find index of the subarray using boyer-moore-horspool algorithm + const table = indexOfTable.value; + table.fill(needle.length); + for (let i = 0; i < needle.length; i++) { + table[needle[i]] = needle.length - i - 1; + } + + let i = offset + needle.length - 1; + let j = i; + let result = -1; + while (i < haystackLen) { + if (haystack[i] === needle[j]) { + if (j === 0) { + result = i; + break; + } + + i--; + j--; + } else { + i += Math.max(needle.length - j, table[haystack[i]]); + j = needle.length - 1; + } + } + + return result; +} + export function readUInt16LE(source: Uint8Array, offset: number): number { return ( ((source[offset + 0] << 0) >>> 0) | diff --git a/src/vs/base/node/nodeStreams.ts b/src/vs/base/node/nodeStreams.ts new file mode 100644 index 00000000000..0719bb46787 --- /dev/null +++ b/src/vs/base/node/nodeStreams.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Transform } from 'stream'; +import { binaryIndexOf } from 'vs/base/common/buffer'; + +/** + * A Transform stream that splits the input on the "splitter" substring. + * The resulting chunks will contain (and trail with) the splitter match. + * The last chunk when the stream ends will be emitted even if a splitter + * is not encountered. + */ +export class StreamSplitter extends Transform { + private buffer: Buffer | undefined; + private readonly splitter: Buffer | number; + private readonly spitterLen: number; + + constructor(splitter: string | number | Buffer) { + super(); + if (typeof splitter === 'number') { + this.splitter = splitter; + this.spitterLen = 1; + } else { + const buf = Buffer.isBuffer(splitter) ? splitter : Buffer.from(splitter); + this.splitter = buf.length === 1 ? buf[0] : buf; + this.spitterLen = buf.length; + } + } + + override _transform(chunk: Buffer, _encoding: string, callback: (error?: Error | null, data?: any) => void): void { + if (!this.buffer) { + this.buffer = chunk; + } else { + this.buffer = Buffer.concat([this.buffer, chunk]); + } + + let offset = 0; + while (offset < this.buffer.length) { + const index = typeof this.splitter === 'number' + ? this.buffer.indexOf(this.splitter, offset) + : binaryIndexOf(this.buffer, this.splitter, offset); + if (index === -1) { + break; + } + + this.push(this.buffer.slice(offset, index + this.spitterLen)); + offset = index + this.spitterLen; + } + + this.buffer = offset === this.buffer.length ? undefined : this.buffer.slice(offset); + callback(); + } + + override _flush(callback: (error?: Error | null, data?: any) => void): void { + if (this.buffer) { + this.push(this.buffer); + } + + callback(); + } +} diff --git a/src/vs/base/test/node/nodeStreams.test.ts b/src/vs/base/test/node/nodeStreams.test.ts new file mode 100644 index 00000000000..620f817dfc9 --- /dev/null +++ b/src/vs/base/test/node/nodeStreams.test.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { Writable } from 'stream'; +import * as assert from 'assert'; +import { StreamSplitter } from 'vs/base/node/nodeStreams'; + +suite('StreamSplitter', () => { + test('should split a stream on a single character splitter', (done) => { + const chunks: string[] = []; + const splitter = new StreamSplitter('\n'); + const writable = new Writable({ + write(chunk, _encoding, callback) { + chunks.push(chunk.toString()); + callback(); + }, + }); + + splitter.pipe(writable); + splitter.write('hello\nwor'); + splitter.write('ld\n'); + splitter.write('foo\nbar\nz'); + splitter.end(() => { + assert.deepStrictEqual(chunks, ['hello\n', 'world\n', 'foo\n', 'bar\n', 'z']); + done(); + }); + }); + + test('should split a stream on a multi-character splitter', (done) => { + const chunks: string[] = []; + const splitter = new StreamSplitter('---'); + const writable = new Writable({ + write(chunk, _encoding, callback) { + chunks.push(chunk.toString()); + callback(); + }, + }); + + splitter.pipe(writable); + splitter.write('hello---wor'); + splitter.write('ld---'); + splitter.write('foo---bar---z'); + splitter.end(() => { + assert.deepStrictEqual(chunks, ['hello---', 'world---', 'foo---', 'bar---', 'z']); + done(); + }); + }); +}); diff --git a/src/vs/platform/remoteTunnel/common/remoteTunnel.ts b/src/vs/platform/remoteTunnel/common/remoteTunnel.ts index 61654659195..d50077ee508 100644 --- a/src/vs/platform/remoteTunnel/common/remoteTunnel.ts +++ b/src/vs/platform/remoteTunnel/common/remoteTunnel.ts @@ -21,18 +21,33 @@ export interface IRemoteTunnelService { readonly onDidChangeTunnelStatus: Event; getTunnelStatus(): Promise; - getSession(): Promise; - readonly onDidChangeSession: Event; + getMode(): Promise; + readonly onDidChangeMode: Event; readonly onDidTokenFailed: Event; - initialize(session: IRemoteTunnelSession | undefined): Promise; + initialize(mode: TunnelMode): Promise; - startTunnel(session: IRemoteTunnelSession): Promise; + startTunnel(mode: ActiveTunnelMode): Promise; stopTunnel(): Promise; getTunnelName(): Promise; } +export interface ActiveTunnelMode { + readonly active: true; + readonly session: IRemoteTunnelSession; + readonly asService: boolean; +} + +export interface InactiveTunnelMode { + readonly active: false; +} + +export const INACTIVE_TUNNEL_MODE: InactiveTunnelMode = { active: false }; + +/** Saved mode for the tunnel. */ +export type TunnelMode = ActiveTunnelMode | InactiveTunnelMode; + export type TunnelStatus = TunnelStates.Connected | TunnelStates.Disconnected | TunnelStates.Connecting | TunnelStates.Uninitialized; export namespace TunnelStates { @@ -46,13 +61,14 @@ export namespace TunnelStates { export interface Connected { readonly type: 'connected'; readonly info: ConnectionInfo; + readonly serviceInstallFailed: boolean; } export interface Disconnected { readonly type: 'disconnected'; readonly onTokenFailed?: IRemoteTunnelSession; } export const disconnected = (onTokenFailed?: IRemoteTunnelSession): Disconnected => ({ type: 'disconnected', onTokenFailed }); - export const connected = (info: ConnectionInfo): Connected => ({ type: 'connected', info }); + export const connected = (info: ConnectionInfo, serviceInstallFailed: boolean): Connected => ({ type: 'connected', info, serviceInstallFailed }); export const connecting = (progress?: string): Connecting => ({ type: 'connecting', progress }); export const uninitialized: Uninitialized = { type: 'uninitialized' }; diff --git a/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts b/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts index 84ccef33653..08ba634bad0 100644 --- a/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts +++ b/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CONFIGURATION_KEY_HOST_NAME, CONFIGURATION_KEY_PREVENT_SLEEP, ConnectionInfo, IRemoteTunnelSession, IRemoteTunnelService, LOGGER_NAME, LOG_ID, TunnelStates, TunnelStatus } from 'vs/platform/remoteTunnel/common/remoteTunnel'; +import { CONFIGURATION_KEY_HOST_NAME, CONFIGURATION_KEY_PREVENT_SLEEP, ConnectionInfo, IRemoteTunnelSession, IRemoteTunnelService, LOGGER_NAME, LOG_ID, TunnelStates, TunnelStatus, TunnelMode, INACTIVE_TUNNEL_MODE, ActiveTunnelMode } from 'vs/platform/remoteTunnel/common/remoteTunnel'; import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -20,15 +20,18 @@ import { localize } from 'vs/nls'; import { hostname, homedir } from 'os'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { isString } from 'vs/base/common/types'; +import { StreamSplitter } from 'vs/base/node/nodeStreams'; type RemoteTunnelEnablementClassification = { owner: 'aeschli'; comment: 'Reporting when Remote Tunnel access is turned on or off'; enabled?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Flag indicating if Remote Tunnel Access is enabled or not' }; + service?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Flag indicating if Remote Tunnel Access is installed as a service' }; }; type RemoteTunnelEnablementEvent = { enabled: boolean; + service: boolean; }; const restartTunnelOnConfigurationChanges: readonly string[] = [ @@ -40,6 +43,8 @@ const restartTunnelOnConfigurationChanges: readonly string[] = [ // if set, the remote tunnel access is currently enabled. // if not set, the remote tunnel access is currently disabled. const TUNNEL_ACCESS_SESSION = 'remoteTunnelSession'; +// Boolean indicating whether the tunnel should be installed as a service. +const TUNNEL_ACCESS_IS_SERVICE = 'remoteTunnelIsService'; /** * This service runs on the shared service. It is running the `code-tunnel` command @@ -55,12 +60,19 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ private readonly _onDidChangeTunnelStatusEmitter = new Emitter(); public readonly onDidChangeTunnelStatus = this._onDidChangeTunnelStatusEmitter.event; - private readonly _onDidChangeSessionEmitter = new Emitter(); - public readonly onDidChangeSession = this._onDidChangeSessionEmitter.event; + private readonly _onDidChangeModeEmitter = new Emitter(); + public readonly onDidChangeMode = this._onDidChangeModeEmitter.event; private readonly _logger: ILogger; - private _session: IRemoteTunnelSession | undefined; + /** + * "Mode" in the terminal state we want to get to -- started, stopped, and + * the attributes associated with each. + * + * At any given time, work may be ongoing to get `_tunnelStatus` into a + * state that reflects the desired `mode`. + */ + private _mode: TunnelMode = INACTIVE_TUNNEL_MODE; private _tunnelProcess: CancelablePromise | undefined; @@ -98,7 +110,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ } })); - this._session = this._restoreSession(); + this._mode = this._restoreMode(); this._tunnelStatus = TunnelStates.uninitialized; } @@ -111,32 +123,34 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ this._onDidChangeTunnelStatusEmitter.fire(tunnelStatus); } - private setSession(session: IRemoteTunnelSession | undefined) { - if (!isSameSession(session, this._session)) { - this._session = session; - this._onDidChangeSessionEmitter.fire(session); - this._storeSession(session); - if (session) { - this._logger.info(`Session updated: ${session.accountLabel} (${session.providerId})`); - if (session.token) { - this._logger.info(`Session token updated: ${session.accountLabel} (${session.providerId})`); - } - } else { - this._logger.info(`Session reset`); + private setMode(mode: TunnelMode) { + if (isSameMode(this._mode, mode)) { + return; + } + + this._mode = mode; + this._storeMode(mode); + this._onDidChangeModeEmitter.fire(this._mode); + if (mode.active) { + this._logger.info(`Session updated: ${mode.session.accountLabel} (${mode.session.providerId}) (service=${mode.asService})`); + if (mode.session.token) { + this._logger.info(`Session token updated: ${mode.session.accountLabel} (${mode.session.providerId})`); } + } else { + this._logger.info(`Session reset`); } } - async getSession(): Promise { - return this._session; + getMode(): Promise { + return Promise.resolve(this._mode); } - async initialize(session: IRemoteTunnelSession | undefined): Promise { + async initialize(mode: TunnelMode): Promise { if (this._initialized) { return this._tunnelStatus; } this._initialized = true; - this.setSession(session); + this.setMode(mode); try { await this._startTunnelProcessDelayer.trigger(() => this.updateTunnelProcess()); } catch (e) { @@ -145,6 +159,14 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ return this._tunnelStatus; } + private readonly defaultOnOutput = (a: string, isErr: boolean) => { + if (isErr) { + this._logger.error(a); + } else { + this._logger.info(a); + } + }; + private getTunnelCommandLocation() { if (!this._tunnelCommand) { let binParentLocation; @@ -164,11 +186,12 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ return this._tunnelCommand; } - async startTunnel(session: IRemoteTunnelSession): Promise { - if (isSameSession(session, this._session) && this._tunnelStatus.type !== 'disconnected') { + async startTunnel(mode: ActiveTunnelMode): Promise { + if (isSameMode(this._mode, mode) && this._tunnelStatus.type !== 'disconnected') { return this._tunnelStatus; } - this.setSession(session); + + this.setMode(mode); try { await this._startTunnelProcessDelayer.trigger(() => this.updateTunnelProcess()); @@ -180,41 +203,49 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ async stopTunnel(): Promise { - this.setSession(undefined); - if (this._tunnelProcess) { this._tunnelProcess.cancel(); this._tunnelProcess = undefined; } - const onOutput = (a: string, isErr: boolean) => { - if (isErr) { - this._logger.error(a); - } else { - this._logger.info(a); - } - }; + if (!this._mode.active) { + return; + } + + // Be careful to only uninstall the service if we're the ones who installed it: + const needsServiceUninstall = this._mode.asService; + this.setMode(INACTIVE_TUNNEL_MODE); + try { - await this.runCodeTunnelCommand('stop', ['kill'], onOutput); + if (needsServiceUninstall) { + this.runCodeTunnelCommand('uninstallService', ['service', 'uninstall']); + } } catch (e) { this._logger.error(e); } - this.setTunnelStatus(TunnelStates.disconnected()); + try { + await this.runCodeTunnelCommand('stop', ['kill']); + } catch (e) { + this._logger.error(e); + } + + this.setTunnelStatus(TunnelStates.disconnected()); } private async updateTunnelProcess(): Promise { - this.telemetryService.publicLog2('remoteTunnel.enablement', { enabled: !!this._session }); - + this.telemetryService.publicLog2('remoteTunnel.enablement', { + enabled: this._mode.active, + service: this._mode.active && this._mode.asService, + }); if (this._tunnelProcess) { this._tunnelProcess.cancel(); this._tunnelProcess = undefined; } - let isAttached = false; let output = ''; - + let isServiceInstalled = false; const onOutput = (a: string, isErr: boolean) => { if (isErr) { this._logger.error(a); @@ -241,22 +272,26 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ tunnel: object | null; } = JSON.parse(output.trim().split('\n').find(l => l.startsWith('{'))!); - isAttached = !!status.tunnel; - this._logger.info(isAttached ? 'Other tunnel running, attaching...' : 'No other tunnel running'); - if (!isAttached && !this._session) { - this._tunnelProcess = undefined; + isServiceInstalled = status.service_installed; + this._logger.info(status.tunnel ? 'Other tunnel running, attaching...' : 'No other tunnel running'); + + // If a tunnel is running but the mode isn't "active", we'll still attach + // to the tunnel to show its state in the UI. If neither are true, disconnect + if (!status.tunnel && !this._mode.active) { this.setTunnelStatus(TunnelStates.disconnected()); return; } } catch (e) { this._logger.error(e); - this._tunnelProcess = undefined; this.setTunnelStatus(TunnelStates.disconnected()); return; + } finally { + if (this._tunnelProcess === statusProcess) { + this._tunnelProcess = undefined; + } } - const session = this._session; - + const session = this._mode.active ? this._mode.session : undefined; if (session && session.token) { const token = session.token; this.setTunnelStatus(TunnelStates.connecting(localize({ key: 'remoteTunnelService.authorizing', comment: ['{0} is a user account name, {1} a provider name (e.g. Github)'] }, 'Connecting as {0} ({1})', session.accountLabel, session.providerId))); @@ -286,25 +321,67 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ } else { this.setTunnelStatus(TunnelStates.connecting(localize('remoteTunnelService.openTunnel', 'Opening tunnel'))); } - const args = ['--parent-process-id', String(process.pid), '--accept-server-license-terms', '--log', LogLevelToString(this._logger.getLevel())]; + const args = ['--accept-server-license-terms', '--log', LogLevelToString(this._logger.getLevel())]; if (hostName) { args.push('--name', hostName); } else { args.push('--random-name'); } + + let serviceInstallFailed = false; + if (this._mode.active && this._mode.asService && !isServiceInstalled) { + // I thought about calling `code tunnel kill` here, but having multiple + // tunnel processes running is pretty much idempotent. If there's + // another tunnel process running, the service process will + // take over when it exits, no hard feelings. + serviceInstallFailed = await this.installTunnelService(args) === false; + } + + return this.serverOrAttachTunnel(session, args, serviceInstallFailed); + } + + private async installTunnelService(args: readonly string[]) { + let status: number; + try { + status = await this.runCodeTunnelCommand('serviceInstall', ['service', 'install', ...args]); + } catch (e) { + this._logger.error(e); + status = 1; + } + + if (status !== 0) { + const msg = localize('remoteTunnelService.serviceInstallFailed', 'Failed to install tunnel as a service, starting in session...'); + this._logger.warn(msg); + this.setTunnelStatus(TunnelStates.connecting(msg)); + return false; + } + + return true; + } + + private async serverOrAttachTunnel(session: IRemoteTunnelSession | undefined, args: string[], serviceInstallFailed: boolean) { + args.push('--parent-process-id', String(process.pid)); + if (this._preventSleep()) { args.push('--no-sleep'); } + + let isAttached = false; const serveCommand = this.runCodeTunnelCommand('tunnel', args, (message: string, isErr: boolean) => { if (isErr) { this._logger.error(message); } else { this._logger.info(message); } + + if (message.includes('Connected to an existing tunnel process')) { + isAttached = true; + } + const m = message.match(/Open this link in your browser (https:\/\/([^\/\s]+)\/([^\/\s]+)\/([^\/\s]+))/); if (m) { const info: ConnectionInfo = { link: m[1], domain: m[2], tunnelName: m[4], isAttached }; - this.setTunnelStatus(TunnelStates.connected(info)); + this.setTunnelStatus(TunnelStates.connected(info, serviceInstallFailed)); } else if (message.match(/error refreshing token/)) { serveCommand.cancel(); this._onDidTokenFailedEmitter.fire(session); @@ -317,14 +394,14 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ // process exited unexpectedly this._logger.info(`tunnel process terminated`); this._tunnelProcess = undefined; - this._session = undefined; + this._mode = INACTIVE_TUNNEL_MODE; this.setTunnelStatus(TunnelStates.disconnected()); } }); } - private runCodeTunnelCommand(logLabel: string, commandArgs: string[], onOutput: (message: string, isError: boolean) => void = () => { }): CancelablePromise { + private runCodeTunnelCommand(logLabel: string, commandArgs: string[], onOutput: (message: string, isError: boolean) => void = this.defaultOnOutput): CancelablePromise { return createCancelablePromise(token => { return new Promise((resolve, reject) => { if (token.isCancellationRequested) { @@ -350,13 +427,13 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ tunnelProcess = spawn(tunnelCommand, ['tunnel', ...commandArgs], { cwd: homedir(), stdio }); } - tunnelProcess.stdout!.on('data', data => { + tunnelProcess.stdout!.pipe(new StreamSplitter('\n')).on('data', data => { if (tunnelProcess) { const message = data.toString(); onOutput(message, false); } }); - tunnelProcess.stderr!.on('data', data => { + tunnelProcess.stderr!.pipe(new StreamSplitter('\n')).on('data', data => { if (tunnelProcess) { const message = data.toString(); onOutput(message, true); @@ -394,30 +471,33 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ return name || undefined; } - private _restoreSession(): IRemoteTunnelSession | undefined { + private _restoreMode(): TunnelMode { try { const tunnelAccessSession = this.storageService.get(TUNNEL_ACCESS_SESSION, StorageScope.APPLICATION); + const asService = this.storageService.getBoolean(TUNNEL_ACCESS_IS_SERVICE, StorageScope.APPLICATION, false); if (tunnelAccessSession) { const session = JSON.parse(tunnelAccessSession) as IRemoteTunnelSession; if (session && isString(session.accountLabel) && isString(session.sessionId) && isString(session.providerId)) { - return session; + return { active: true, session, asService }; } this._logger.error('Problems restoring session from storage, invalid format', session); } } catch (e) { this._logger.error('Problems restoring session from storage', e); } - return undefined; + return INACTIVE_TUNNEL_MODE; } - private _storeSession(session: IRemoteTunnelSession | undefined): void { - if (session) { + private _storeMode(mode: TunnelMode): void { + if (mode.active) { const sessionWithoutToken = { - providerId: session.providerId, sessionId: session.sessionId, accountLabel: session.accountLabel + providerId: mode.session.providerId, sessionId: mode.session.sessionId, accountLabel: mode.session.accountLabel }; this.storageService.store(TUNNEL_ACCESS_SESSION, JSON.stringify(sessionWithoutToken), StorageScope.APPLICATION, StorageTarget.MACHINE); + this.storageService.store(TUNNEL_ACCESS_IS_SERVICE, mode.asService, StorageScope.APPLICATION, StorageTarget.MACHINE); } else { this.storageService.remove(TUNNEL_ACCESS_SESSION, StorageScope.APPLICATION); + this.storageService.remove(TUNNEL_ACCESS_IS_SERVICE, StorageScope.APPLICATION); } } } @@ -429,3 +509,12 @@ function isSameSession(a1: IRemoteTunnelSession | undefined, a2: IRemoteTunnelSe return a1 === a2; } +const isSameMode = (a: TunnelMode, b: TunnelMode) => { + if (a.active !== b.active) { + return false; + } else if (a.active && b.active) { + return a.asService === b.asService && isSameSession(a.session, b.session); + } else { + return true; + } +}; diff --git a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts index 7c6b0fe8cf5..5885e400570 100644 --- a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts +++ b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts @@ -3,40 +3,39 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { CONFIGURATION_KEY_HOST_NAME, CONFIGURATION_KEY_PREFIX, CONFIGURATION_KEY_PREVENT_SLEEP, ConnectionInfo, IRemoteTunnelSession, IRemoteTunnelService, LOGGER_NAME, LOG_ID } from 'vs/platform/remoteTunnel/common/remoteTunnel'; -import { AuthenticationSession, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; -import { localize } from 'vs/nls'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ILocalizedString } from 'vs/platform/action/common/action'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ILogger, ILoggerService, ILogService } from 'vs/platform/log/common/log'; -import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IQuickInputService, IQuickPickItem, IQuickPickSeparator, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IOutputService } from 'vs/workbench/services/output/common/output'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { IProgress, IProgressService, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Action } from 'vs/base/common/actions'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IWorkspaceContextService, isUntitledWorkspace } from 'vs/platform/workspace/common/workspace'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; import { ITunnelApplicationConfig } from 'vs/base/common/product'; +import { joinPath } from 'vs/base/common/resources'; import { isNumber, isObject, isString } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IProgress, IProgressService, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { CONFIGURATION_KEY_HOST_NAME, CONFIGURATION_KEY_PREFIX, CONFIGURATION_KEY_PREVENT_SLEEP, ConnectionInfo, INACTIVE_TUNNEL_MODE, IRemoteTunnelService, IRemoteTunnelSession, LOGGER_NAME, LOG_ID, TunnelStatus } from 'vs/platform/remoteTunnel/common/remoteTunnel'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IWorkspaceContextService, isUntitledWorkspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { AuthenticationSession, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IOutputService } from 'vs/workbench/services/output/common/output'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export const REMOTE_TUNNEL_CATEGORY: ILocalizedString = { original: 'Remote-Tunnels', @@ -103,10 +102,8 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo @IProductService productService: IProductService, @IStorageService private readonly storageService: IStorageService, @ILoggerService loggerService: ILoggerService, - @ILogService logService: ILogService, @IQuickInputService private readonly quickInputService: IQuickInputService, @INativeEnvironmentService private environmentService: INativeEnvironmentService, - @IFileService fileService: IFileService, @IRemoteTunnelService private remoteTunnelService: IRemoteTunnelService, @ICommandService private commandService: ICommandService, @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, @@ -127,20 +124,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo } this.serverConfiguration = serverConfiguration; - this._register(this.remoteTunnelService.onDidChangeTunnelStatus(status => { - this.connectionInfo = undefined; - if (status.type === 'disconnected') { - if (status.onTokenFailed) { - this.expiredSessions.add(status.onTokenFailed.sessionId); - } - this.connectionStateContext.set('disconnected'); - } else if (status.type === 'connecting') { - this.connectionStateContext.set('connecting'); - } else if (status.type === 'connected') { - this.connectionInfo = status.info; - this.connectionStateContext.set('connected'); - } - })); + this._register(this.remoteTunnelService.onDidChangeTunnelStatus(s => this.handleTunnelStatusUpdate(s))); this.registerCommands(); @@ -149,6 +133,21 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo this.recommendRemoteExtensionIfNeeded(); } + private handleTunnelStatusUpdate(status: TunnelStatus) { + this.connectionInfo = undefined; + if (status.type === 'disconnected') { + if (status.onTokenFailed) { + this.expiredSessions.add(status.onTokenFailed.sessionId); + } + this.connectionStateContext.set('disconnected'); + } else if (status.type === 'connecting') { + this.connectionStateContext.set('connecting'); + } else if (status.type === 'connected') { + this.connectionInfo = status.info; + this.connectionStateContext.set('connected'); + } + } + private async recommendRemoteExtensionIfNeeded() { await this.extensionService.whenInstalledExtensionsRegistered(); @@ -228,10 +227,17 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo } private async initialize(): Promise { - const session = await this.remoteTunnelService.getSession(); - if (session && session.token) { + const [mode, status] = await Promise.all([ + this.remoteTunnelService.getMode(), + this.remoteTunnelService.getTunnelStatus(), + ]); + + this.handleTunnelStatusUpdate(status); + + if (mode.active && mode.session.token) { return; // already initialized, token available } + return await this.progressService.withProgress( { location: ProgressLocation.Window, @@ -248,13 +254,13 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo } }); let newSession: IRemoteTunnelSession | undefined; - if (session) { - const token = await this.getSessionToken(session); + if (mode.active) { + const token = await this.getSessionToken(mode.session); if (token) { - newSession = { ...session, token }; + newSession = { ...mode.session, token }; } } - const status = await this.remoteTunnelService.initialize(newSession); + const status = await this.remoteTunnelService.initialize(mode.active && newSession ? { ...mode, session: newSession } : INACTIVE_TUNNEL_MODE); listener.dispose(); if (status.type === 'connected') { @@ -267,7 +273,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo } - private async startTunnel(): Promise { + private async startTunnel(asService: boolean): Promise { if (this.connectionInfo) { return this.connectionInfo; } @@ -301,6 +307,19 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo listener.dispose(); completed = true; s(status.info); + if (status.serviceInstallFailed) { + this.notificationService.notify({ + severity: Severity.Warning, + message: localize( + { + key: 'remoteTunnel.serviceInstallFailed', + comment: ['{Locked="](command:{0})"}'] + }, + "Installation as a service failed, and we fell back to running the tunnel for this session. See the [error log](command:{0}) for details.", + RemoteTunnelCommandIds.showLog, + ), + }); + } break; case 'disconnected': listener.dispose(); @@ -312,7 +331,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo }); const token = authenticationSession.session.idToken ?? authenticationSession.session.accessToken; const account: IRemoteTunnelSession = { sessionId: authenticationSession.session.id, token, providerId: authenticationSession.providerId, accountLabel: authenticationSession.session.account.label }; - this.remoteTunnelService.startTunnel(account).then(status => { + this.remoteTunnelService.startTunnel({ active: true, asService, session: account }).then(status => { if (!completed && (status.type === 'connected' || status.type === 'disconnected')) { listener.dispose(); if (status.type === 'connected') { @@ -403,7 +422,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo private async getAllSessions(): Promise { const authenticationProviders = await this.getAuthenticationProviders(); const accounts = new Map(); - const currentAccount = await this.remoteTunnelService.getSession(); + const currentAccount = await this.remoteTunnelService.getMode(); let currentSession: ExistingSessionItem | undefined; for (const provider of authenticationProviders) { @@ -413,7 +432,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo if (!this.expiredSessions.has(session.id)) { const item = this.createExistingSessionItem(session, provider.id); accounts.set(item.session.account.id, item); - if (currentAccount && currentAccount.sessionId === session.id) { + if (currentAccount.active && currentAccount.session.sessionId === session.id) { currentSession = item; } } @@ -483,6 +502,8 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo const commandService = accessor.get(ICommandService); const storageService = accessor.get(IStorageService); const dialogService = accessor.get(IDialogService); + const quickInputService = accessor.get(IQuickInputService); + const productService = accessor.get(IProductService); const didNotifyPreview = storageService.getBoolean(REMOTE_TUNNEL_PROMPTED_PREVIEW_STORAGE_KEY, StorageScope.APPLICATION, false); if (!didNotifyPreview) { @@ -497,12 +518,33 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo storageService.store(REMOTE_TUNNEL_PROMPTED_PREVIEW_STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.USER); } - const connectionInfo = await that.startTunnel(); + const disposables = new DisposableStore(); + const quickPick = quickInputService.createQuickPick(); + quickPick.placeholder = localize('tunnel.enable.placeholder', 'Select how you want to enable access'); + quickPick.items = [ + { service: false, label: localize('tunnel.enable.session', 'Turn on for this session'), description: localize('tunnel.enable.session.description', 'Run whenever {0} is open', productService.nameShort) }, + { service: true, label: localize('tunnel.enable.service', 'Install as a service'), description: localize('tunnel.enable.service.description', 'Run whenever you\'re logged in') } + ]; + + const asService = await new Promise(resolve => { + disposables.add(quickPick.onDidAccept(() => resolve(quickPick.selectedItems[0]?.service))); + disposables.add(quickPick.onDidHide(() => resolve(undefined))); + quickPick.show(); + }); + + quickPick.dispose(); + + if (asService === undefined) { + return; // no-op + } + + const connectionInfo = await that.startTunnel(/* installAsService= */ asService); + if (connectionInfo) { const linkToOpen = that.getLinkToOpen(connectionInfo); const remoteExtension = that.serverConfiguration.extension; const linkToOpenForMarkdown = linkToOpen.toString(false).replace(/\)/g, '%29'); - await notificationService.notify({ + notificationService.notify({ severity: Severity.Info, message: localize( @@ -525,7 +567,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo const usedOnHostMessage: UsedOnHostMessage = { hostName: connectionInfo.tunnelName, timeStamp: new Date().getTime() }; storageService.store(REMOTE_TUNNEL_USED_STORAGE_KEY, JSON.stringify(usedOnHostMessage), StorageScope.APPLICATION, StorageTarget.USER); } else { - await notificationService.notify({ + notificationService.notify({ severity: Severity.Info, message: localize('progress.turnOn.failed', "Unable to turn on the remote tunnel access. Check the Remote Tunnel Service log for details."), @@ -699,7 +741,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo private async showManageOptions() { - const account = await this.remoteTunnelService.getSession(); + const account = await this.remoteTunnelService.getMode(); return new Promise((c, e) => { const disposables = new DisposableStore(); @@ -721,7 +763,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo items.push({ id: RemoteTunnelCommandIds.showLog, label: localize('manage.showLog', 'Show Log') }); items.push({ type: 'separator' }); items.push({ id: RemoteTunnelCommandIds.configure, label: localize('manage.tunnelName', 'Change Tunnel Name'), description: this.connectionInfo?.tunnelName }); - items.push({ id: RemoteTunnelCommandIds.turnOff, label: RemoteTunnelCommandLabels.turnOff, description: account ? `${account.accountLabel} (${account.providerId})` : undefined }); + items.push({ id: RemoteTunnelCommandIds.turnOff, label: RemoteTunnelCommandLabels.turnOff, description: account.active ? `${account.session.accountLabel} (${account.session.providerId})` : undefined }); quickPick.items = items; disposables.add(quickPick.onDidAccept(() => { From 43650e28b6700c43708084df436e769a6eb99426 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 13 Jul 2023 23:12:45 -0700 Subject: [PATCH 125/826] Re #187471. Dispose instantiationService on test finish. (#187900) --- .../browser/browserKeyboardMapper.test.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/keybinding/test/browser/browserKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/browser/browserKeyboardMapper.test.ts index 5b466a15fe1..3921c6f130e 100644 --- a/src/vs/workbench/services/keybinding/test/browser/browserKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/browser/browserKeyboardMapper.test.ts @@ -35,13 +35,22 @@ class TestKeyboardMapperFactory extends BrowserKeyboardMapperFactoryBase { } suite('keyboard layout loader', () => { - const instantiationService: TestInstantiationService = new TestInstantiationService(); - const notitifcationService = instantiationService.stub(INotificationService, new TestNotificationService()); - const storageService = instantiationService.stub(IStorageService, new TestStorageService()); - const configurationService = instantiationService.stub(IConfigurationService, new TestConfigurationService()); + let instantiationService: TestInstantiationService; + let instance: TestKeyboardMapperFactory; - const commandService = instantiationService.stub(ICommandService, {}); - const instance = new TestKeyboardMapperFactory(configurationService, notitifcationService, storageService, commandService); + setup(() => { + instantiationService = new TestInstantiationService(); + const notitifcationService = instantiationService.stub(INotificationService, new TestNotificationService()); + const storageService = instantiationService.stub(IStorageService, new TestStorageService()); + const configurationService = instantiationService.stub(IConfigurationService, new TestConfigurationService()); + + const commandService = instantiationService.stub(ICommandService, {}); + instance = new TestKeyboardMapperFactory(configurationService, notitifcationService, storageService, commandService); + }); + + teardown(() => { + instantiationService.dispose(); + }); test('load default US keyboard layout', () => { assert.notStrictEqual(instance.activeKeyboardLayout, null); From 670b39eb0acb9d67789514ffd7a41072b4f07f75 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 10:04:05 +0200 Subject: [PATCH 126/826] adding console logs to understand where the error comes from --- src/vs/editor/browser/widget/diffEditorWidget.ts | 3 +++ .../contrib/inlineChat/browser/inlineChatStrategies.ts | 1 + .../contrib/inlineChat/browser/inlineChatWidget.ts | 7 +++++++ .../inlineChat/test/browser/inlineChatController.test.ts | 1 + 4 files changed, 12 insertions(+) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index e21c1e9f3be..47afe24c196 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -709,6 +709,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } public override dispose(): void { + console.log('inside of the dispose method'); + this._codeEditorService.removeDiffEditor(this); if (this._beginUpdateDecorationsTimeout !== -1) { @@ -884,6 +886,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._onDidChangeModel.fire(); // Diff navigator + console.log('right before the final _register'); this._diffNavigator = this._register(this._instantiationService.createInstance(DiffNavigator, this, { alwaysRevealFirst: false, findResultLoop: this.getModifiedEditor().getOption(EditorOption.find).loop diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 36e64ebf09a..bbc2aabacb8 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -126,6 +126,7 @@ export class PreviewStrategy extends EditModeStrategy { const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); this._widget.showEditsPreview(this._session.textModel0, edits, this._session.lastTextModelChanges); } else { + console.log('inside of render changes'); this._widget.hideEditsPreview(); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index adb0b77635e..a7cc7801921 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -521,6 +521,7 @@ export class InlineChatWidget { this._elements.statusToolbar.classList.add('hidden'); this._elements.feedbackToolbar.classList.add('hidden'); this.hideCreatePreview(); + console.log('inside of reset'); this.hideEditsPreview(); this._onDidChangeHeight.fire(); } @@ -537,6 +538,7 @@ export class InlineChatWidget { showEditsPreview(textModelv0: ITextModel, edits: ISingleEditOperation[], changes: readonly LineRangeMapping[]) { if (changes.length === 0) { + console.log('inside of show edits preview'); this.hideEditsPreview(); return; } @@ -546,6 +548,7 @@ export class InlineChatWidget { const languageSelection: ILanguageSelection = { languageId: textModelv0.getLanguageId(), onDidChange: Event.None }; const modified = this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModelv0.createSnapshot()), languageSelection, undefined, true); modified.applyEdits(edits, false); + console.log('inside of show edits preview'); this._previewDiffEditor.value.setModel({ original: textModelv0, modified }); // joined ranges @@ -577,7 +580,10 @@ export class InlineChatWidget { } hideEditsPreview() { + // Error happens because this is called after the diff editor widget is already disposed. + console.log('inside of hide edits preview'); this._elements.previewDiff.classList.add('hidden'); + // TODO: error is happening here this._previewDiffEditor.value.setModel(null); this._previewDiffModel.clear(); this._onDidChangeHeight.fire(); @@ -834,6 +840,7 @@ export class InlineChatZoneWidget extends ZoneWidget { } override hide(): void { + console.log('inside of hide'); this.container!.classList.remove('inside-selection'); this._ctxVisible.reset(); this._ctxCursorPosition.reset(); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index f4ba0f5b182..7733da149ef 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -156,6 +156,7 @@ suite('InteractiveChatController', function () { await run; + console.log('ctrl.getWidgetPosition() : ', ctrl.getWidgetPosition()); assert.ok(ctrl.getWidgetPosition() === undefined); }); From 42210746536554290fb35092495f4dfbef9bbb37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 14 Jul 2023 10:14:19 +0200 Subject: [PATCH 127/826] fix windows servers, add top level folders (#187906) --- build/azure-pipelines/win32/product-build-win32.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 6356978d40b..d733ec41efd 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -263,7 +263,7 @@ steps: $ErrorActionPreference = "Stop" $ArchivePath = ".build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH).zip" New-Item -ItemType Directory -Path .build\win32-$(VSCODE_ARCH) -Force - exec { 7z.exe a -tzip $ArchivePath ..\vscode-server-win32-$(VSCODE_ARCH)\* -r } + exec { 7z.exe a -tzip $ArchivePath ..\vscode-server-win32-$(VSCODE_ARCH) -r } echo "##vso[task.setvariable variable=SERVER_PATH]$ArchivePath" condition: and(succeededOrFailed(), eq(variables['BUILT_SERVER'], 'true')) displayName: Package server @@ -273,7 +273,7 @@ steps: $ErrorActionPreference = "Stop" $ArchivePath = ".build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH)-web.zip" New-Item -ItemType Directory -Path .build\win32-$(VSCODE_ARCH) -Force - exec { 7z.exe a -tzip $ArchivePath ..\vscode-server-win32-$(VSCODE_ARCH)-web\* -r } + exec { 7z.exe a -tzip $ArchivePath ..\vscode-server-win32-$(VSCODE_ARCH)-web -r } echo "##vso[task.setvariable variable=WEB_PATH]$ArchivePath" condition: and(succeededOrFailed(), eq(variables['BUILT_WEB'], 'true')) displayName: Package server (web) From a0f904dd54d04a87c3df08d42382a4d77075174a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 14 Jul 2023 10:28:51 +0200 Subject: [PATCH 128/826] only show update release notes in stable (#187908) --- .../contrib/update/browser/update.ts | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 538bbbd5578..bfba80a99df 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -458,23 +458,25 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Updating) }); - CommandsRegistry.registerCommand('update.showUpdateReleaseNotes', () => { - if (this.updateService.state.type !== StateType.Ready) { - return; - } + if (this.productService.quality === 'stable') { + CommandsRegistry.registerCommand('update.showUpdateReleaseNotes', () => { + if (this.updateService.state.type !== StateType.Ready) { + return; + } - const version = this.updateService.state.update.version; - this.instantiationService.invokeFunction(accessor => showReleaseNotes(accessor, version)); - }); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '7_update', - order: 1, - command: { - id: 'update.showUpdateReleaseNotes', - title: nls.localize('showUpdateReleaseNotes', "Show Update Release Notes") - }, - when: ContextKeyExpr.and(CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready), MAJOR_MINOR_UPDATE_AVAILABLE) - }); + const version = this.updateService.state.update.version; + this.instantiationService.invokeFunction(accessor => showReleaseNotes(accessor, version)); + }); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '7_update', + order: 1, + command: { + id: 'update.showUpdateReleaseNotes', + title: nls.localize('showUpdateReleaseNotes', "Show Update Release Notes") + }, + when: ContextKeyExpr.and(CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready), MAJOR_MINOR_UPDATE_AVAILABLE) + }); + } CommandsRegistry.registerCommand('update.restart', () => this.updateService.quitAndInstall()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { From bd27bb08e5d3984ee288c4a9c4eba233625acd3c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 10:33:19 +0200 Subject: [PATCH 129/826] not sure what is causing the test errors --- .../browser/inlineChatController.ts | 4 +++ .../test/browser/inlineChatController.test.ts | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 39436818fa9..3306e796ce3 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -176,6 +176,7 @@ export class InlineChatController implements IEditorContribution { private _currentRun?: Promise; async run(options: InlineChatRunOptions | undefined = {}): Promise { + console.log('inside of run'); this.finishExistingSession(); if (this._currentRun) { await this._currentRun; @@ -667,6 +668,7 @@ export class InlineChatController implements IEditorContribution { } private async [State.CANCEL]() { + console.log('inside of cancel function'); assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); @@ -771,6 +773,7 @@ export class InlineChatController implements IEditorContribution { } cancelSession() { + console.log('inside of the cancel session method'); let result: string | undefined; if (this._strategy && this._activeSession) { const changedText = this._activeSession.asChangedText(); @@ -785,6 +788,7 @@ export class InlineChatController implements IEditorContribution { } finishExistingSession(): void { + console.log('inside of finish existing session'); if (this._activeSession) { if (this._activeSession.editMode === EditMode.Preview) { this._log('finishing existing session, using CANCEL', this._activeSession.editMode); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 7733da149ef..1ace231c30b 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -141,27 +141,39 @@ suite('InteractiveChatController', function () { }); test('creation, not showing anything', function () { + console.log('*** at the begining of creation'); ctrl = instaService.createInstance(TestController, editor); assert.ok(ctrl); assert.strictEqual(ctrl.getWidgetPosition(), undefined); + console.log('*** at the end of creation'); }); test('run (show/hide)', async function () { + console.log('*** at the beginning of run (show/hide)'); + ctrl = instaService.createInstance(TestController, editor); + console.log('right before run'); const run = ctrl.run({ message: 'Hello', autoSend: true }); + console.log('right before waitFor INIT_SEQUENCE_AUTO_SEND'); await ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); assert.ok(ctrl.getWidgetPosition() !== undefined); + + console.log('right before cancel session'); ctrl.cancelSession(); + console.log('right before run'); await run; console.log('ctrl.getWidgetPosition() : ', ctrl.getWidgetPosition()); assert.ok(ctrl.getWidgetPosition() === undefined); + console.log('*** at the end of run (show/hide)'); }); test('wholeRange expands to whole lines, editor selection default', async function () { + console.log('*** at the beginning of wholeRange, editor selection default'); + editor.setSelection(new Range(1, 1, 1, 3)); ctrl = instaService.createInstance(TestController, editor); @@ -186,10 +198,14 @@ suite('InteractiveChatController', function () { ctrl.cancelSession(); d.dispose(); + + console.log('*** at the end of wholeRange, editor selection default'); }); test('wholeRange expands to whole lines, session provided', async function () { + console.log('*** at the beginning of wholeRange, editor selection provided'); + editor.setSelection(new Range(1, 1, 1, 1)); ctrl = instaService.createInstance(TestController, editor); @@ -215,9 +231,14 @@ suite('InteractiveChatController', function () { ctrl.cancelSession(); d.dispose(); + + console.log('*** at the end of wholeRange, editor selection provided'); }); test('typing outside of wholeRange finishes session', async function () { + + console.log('*** at the beginning of typing outside'); + ctrl = instaService.createInstance(TestController, editor); ctrl.run({ message: 'Hello', autoSend: true }); @@ -231,10 +252,14 @@ suite('InteractiveChatController', function () { editor.trigger('test', 'type', { text: 'a' }); await ctrl.waitFor([State.ACCEPT]); + + console.log('*** at the end of typing outside'); }); test('\'whole range\' isn\'t updated for edits outside whole range #4346', async function () { + console.log('*** at the beginning of whole range isnt updated'); + editor.setSelection(new Range(3, 1, 3, 1)); const d = inlineChatService.addProvider({ @@ -271,9 +296,14 @@ suite('InteractiveChatController', function () { await ctrl.waitFor([State.MAKE_REQUEST, State.APPLY_RESPONSE, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 4, 12)); + + console.log('*** at the end of whole range isnt updated'); }); test('Stuck inline chat widget #211', async function () { + + console.log('*** at the beginning of stuck inline chat'); + const d = inlineChatService.addProvider({ debugName: 'Unit Test', prepareInlineChatSession() { @@ -306,5 +336,7 @@ suite('InteractiveChatController', function () { await p; assert.strictEqual(ctrl.getWidgetPosition(), undefined); + + console.log('*** at the end of stuck inline chat'); }); }); From 6a152ca5231c2035bc8718e41dc96047e33bed96 Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Fri, 14 Jul 2023 08:49:15 +0000 Subject: [PATCH 130/826] Expose the focused element and change event in the TreeView API (#184268) * Expose the focused element and event in the TreeView API * Exposed active TreeItem through extension proposal * Add proposal to test extension * Merge change selection and focus events * Finish selection+focus change in treeview * Clean up * Clean up * Add checkProposedApiEnabled back in --------- Co-authored-by: Ehab Younes Co-authored-by: Alex Ross --- extensions/vscode-api-tests/package.json | 1 + .../api/browser/mainThreadTreeViews.ts | 3 +- .../workbench/api/common/extHost.protocol.ts | 3 +- .../workbench/api/common/extHostTreeViews.ts | 41 +++++++++++-------- .../workbench/browser/parts/views/treeView.ts | 26 ++++++++---- src/vs/workbench/common/views.ts | 4 +- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.treeViewActiveItem.d.ts | 30 ++++++++++++++ 8 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index b230a1a4897..369927c00f2 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -44,6 +44,7 @@ "timeline", "tokenInformation", "treeItemCheckbox", + "treeViewActiveItem", "treeViewReveal", "testInvalidateResults", "workspaceTrust", diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 6b6a75aaf60..04170a3c9a0 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -177,8 +177,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie private registerListeners(treeViewId: string, treeView: ITreeView): void { this._register(treeView.onDidExpandItem(item => this._proxy.$setExpanded(treeViewId, item.handle, true))); this._register(treeView.onDidCollapseItem(item => this._proxy.$setExpanded(treeViewId, item.handle, false))); - this._register(treeView.onDidChangeSelection(items => this._proxy.$setSelection(treeViewId, items.map(({ handle }) => handle)))); - this._register(treeView.onDidChangeFocus(item => this._proxy.$setFocus(treeViewId, item.handle))); + this._register(treeView.onDidChangeSelectionAndFocus(items => this._proxy.$setSelectionAndFocus(treeViewId, items.selection.map(({ handle }) => handle), items.focus.handle))); this._register(treeView.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible))); this._register(treeView.onDidChangeCheckboxState(items => { this._proxy.$changeCheckboxState(treeViewId, items.map(item => { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3471b4cecaf..5320cb4a8b0 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1563,8 +1563,7 @@ export interface ExtHostTreeViewsShape { $handleDrop(destinationViewId: string, requestId: number, treeDataTransfer: DataTransferDTO, targetHandle: string | undefined, token: CancellationToken, operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise; $handleDrag(sourceViewId: string, sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise; $setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void; - $setSelection(treeViewId: string, treeItemHandles: string[]): void; - $setFocus(treeViewId: string, treeItemHandle: string): void; + $setSelectionAndFocus(treeViewId: string, selectionHandles: string[], focusHandle: string): void; $setVisible(treeViewId: string, visible: boolean): void; $changeCheckboxState(treeViewId: string, checkboxUpdates: CheckboxUpdate[]): void; $hasResolve(treeViewId: string): Promise; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 2950a3b5894..3aaa6a38b29 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -24,6 +24,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ITreeViewsDnDService, TreeViewsDnDService } from 'vs/editor/common/services/treeViewsDnd'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; type TreeItemHandle = string; @@ -99,6 +100,14 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { get onDidExpandElement() { return treeView.onDidExpandElement; }, get selection() { return treeView.selectedElements; }, get onDidChangeSelection() { return treeView.onDidChangeSelection; }, + get activeItem() { + checkProposedApiEnabled(extension, 'treeViewActiveItem'); + return treeView.focusedElement; + }, + get onDidChangeActiveItem() { + checkProposedApiEnabled(extension, 'treeViewActiveItem'); + return treeView.onDidChangeActiveItem; + }, get visible() { return treeView.visible; }, get onDidChangeVisibility() { return treeView.onDidChangeVisibility; }, get onDidChangeCheckboxState() { @@ -222,20 +231,12 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { treeView.setExpanded(treeItemHandle, expanded); } - $setSelection(treeViewId: string, treeItemHandles: string[]): void { + $setSelectionAndFocus(treeViewId: string, selectedHandles: string[], focusedHandle: string) { const treeView = this.treeViews.get(treeViewId); if (!treeView) { throw new NoTreeViewError(treeViewId); } - treeView.setSelection(treeItemHandles); - } - - $setFocus(treeViewId: string, treeItemHandles: string) { - const treeView = this.treeViews.get(treeViewId); - if (!treeView) { - throw new NoTreeViewError(treeViewId); - } - treeView.setFocus(treeItemHandles); + treeView.setSelectionAndFocus(selectedHandles, focusedHandle); } $setVisible(treeViewId: string, isVisible: boolean): void { @@ -313,6 +314,9 @@ class ExtHostTreeView extends Disposable { private _onDidChangeSelection: Emitter> = this._register(new Emitter>()); readonly onDidChangeSelection: Event> = this._onDidChangeSelection.event; + private _onDidChangeActiveItem: Emitter> = this._register(new Emitter>()); + readonly onDidChangeActiveItem: Event> = this._onDidChangeActiveItem.event; + private _onDidChangeVisibility: Emitter = this._register(new Emitter()); readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; @@ -479,15 +483,20 @@ class ExtHostTreeView extends Disposable { } } - setSelection(treeItemHandles: TreeItemHandle[]): void { - if (!equals(this._selectedHandles, treeItemHandles)) { - this._selectedHandles = treeItemHandles; + setSelectionAndFocus(selectedHandles: TreeItemHandle[], focusedHandle: string): void { + const changedSelection = !equals(this._selectedHandles, selectedHandles); + this._selectedHandles = selectedHandles; + + const changedFocus = this._focusedHandle !== focusedHandle; + this._focusedHandle = focusedHandle; + + if (changedSelection) { this._onDidChangeSelection.fire(Object.freeze({ selection: this.selectedElements })); } - } - setFocus(treeItemHandle: TreeItemHandle) { - this._focusedHandle = treeItemHandle; + if (changedFocus) { + this._onDidChangeActiveItem.fire(Object.freeze({ activeItem: this.focusedElement })); + } } setVisible(visible: boolean): void { diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 5cacc80e6ba..77b9e62a7b7 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -215,6 +215,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { private root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; + private lastSelection: readonly ITreeItem[] = []; + private lastActive: ITreeItem; private readonly _onDidExpandItem: Emitter = this._register(new Emitter()); readonly onDidExpandItem: Event = this._onDidExpandItem.event; @@ -222,11 +224,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { private readonly _onDidCollapseItem: Emitter = this._register(new Emitter()); readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; - private _onDidChangeSelection: Emitter = this._register(new Emitter()); - readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; - - private _onDidChangeFocus: Emitter = this._register(new Emitter()); - readonly onDidChangeFocus: Event = this._onDidChangeFocus.event; + private _onDidChangeSelectionAndFocus: Emitter<{ selection: readonly ITreeItem[]; focus: ITreeItem }> = this._register(new Emitter<{ selection: readonly ITreeItem[]; focus: ITreeItem }>()); + readonly onDidChangeSelectionAndFocus: Event<{ selection: readonly ITreeItem[]; focus: ITreeItem }> = this._onDidChangeSelectionAndFocus.event; private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; @@ -267,6 +266,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { ) { super(); this.root = new Root(); + this.lastActive = this.root; // Try not to add anything that could be costly to this constructor. It gets called once per tree view // during startup, and anything added here can affect performance. } @@ -702,10 +702,17 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { const customTreeKey = RawCustomTreeViewContextKey.bindTo(this.tree.contextKeyService); customTreeKey.set(true); this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner))); - this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements))); + + this._register(this.tree.onDidChangeSelection(e => { + this.lastSelection = e.elements; + this.lastActive = this.tree?.getFocus()[0] ?? this.lastActive; + this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive }); + })); this._register(this.tree.onDidChangeFocus(e => { - if (e.elements.length) { - this._onDidChangeFocus.fire(e.elements[0]); + if (e.elements.length && (e.elements[0] !== this.lastActive)) { + this.lastActive = e.elements[0]; + this.lastSelection = this.tree?.getSelection() ?? this.lastSelection; + this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive }); } })); this._register(this.tree.onDidChangeCollapseState(e => { @@ -957,7 +964,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { } const newSelection = tree.getSelection(); if (oldSelection.length !== newSelection.length || oldSelection.some((value, index) => value.handle !== newSelection[index].handle)) { - this._onDidChangeSelection.fire(newSelection); + this.lastSelection = newSelection; + this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive }); } this.refreshing = false; this._onDidCompleteRefresh.fire(); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 99efb9f3133..f86f9cb92bb 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -667,9 +667,7 @@ export interface ITreeView extends IDisposable { readonly onDidCollapseItem: Event; - readonly onDidChangeSelection: Event; - - readonly onDidChangeFocus: Event; + readonly onDidChangeSelectionAndFocus: Event<{ selection: readonly ITreeItem[]; focus: ITreeItem }>; readonly onDidChangeVisibility: Event; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index ed18b53ce4f..98f89dca708 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -91,6 +91,7 @@ export const allApiProposals = Object.freeze({ textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts', timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts', tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts', + treeViewActiveItem: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts', treeViewReveal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts', tunnels: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts', windowActivity: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.windowActivity.d.ts', diff --git a/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts b/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts new file mode 100644 index 00000000000..27eab8603f6 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/170248 + + export interface TreeView extends Disposable { + /** + * Currently active item. + */ + readonly activeItem: T | undefined; + /** + * Event that is fired when the {@link TreeView.activeItem active item} has changed + */ + readonly onDidChangeActiveItem: Event>; + } + + /** + * The event that is fired when there is a change in {@link TreeView.activeItem tree view's active item} + */ + export interface TreeViewActiveItemChangeEvent { + /** + * Active item. + */ + readonly activeItem: T | undefined; + } +} From 4635c352d4a1d0a7452a39d76ba7f4e027b2813f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 11:18:41 +0200 Subject: [PATCH 131/826] refactoring the code --- src/vs/editor/contrib/hover/browser/hover.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 46c94e313df..0058f9179da 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -216,6 +216,9 @@ export class ModesHoverController implements IEditorContribution { this._glyphWidget.startShowingAt(target.position.lineNumber); return; } + if (this._contentWidget?.isFocused()) { + return; + } if (!this._contentWidget?.widget.isResizing && !_sticky) { this._hideWidgets(); } From e86cdd8dfe168e5cff9152f7e2ef6058bd06a42b Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 11:18:50 +0200 Subject: [PATCH 132/826] refactoring the code --- .../contrib/hover/browser/contentHover.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index cb9e19bd001..6ceb236f057 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -195,9 +195,7 @@ export class ContentHoverController extends Disposable { private _setCurrentResult(hoverResult: HoverResult | null): void { if (this._currentResult === hoverResult) { - if (hoverResult === null && !this._widget.isFocused) { - this._widget.hide(); - } + // avoid updating the DOM to avoid resetting the user selection return; } if (hoverResult && hoverResult.messages.length === 0) { @@ -207,9 +205,6 @@ export class ContentHoverController extends Disposable { if (this._currentResult) { this._renderMessages(this._currentResult.anchor, this._currentResult.messages); } else { - if (this._widget.isFocused) { - return; - } this._widget.hide(); } } @@ -232,6 +227,10 @@ export class ContentHoverController extends Disposable { return this._widget.isVisible; } + public isFocused(): boolean { + return this._widget.isFocused; + } + public containsNode(node: Node | null | undefined): boolean { return (node ? this._widget.getDomNode().contains(node) : false); } @@ -264,7 +263,9 @@ export class ContentHoverController extends Disposable { return; } } - + if (hoverResult.messages.length === 0 && this._widget.isFocused) { + return; + } this._setCurrentResult(hoverResult); } @@ -735,7 +736,10 @@ export class ContentHoverWidget extends ResizableContentWidget { } public hide(): void { - const stoleFocus = this._visibleData?.stoleFocus || this._hoverFocusedKey.get(); + if (!this._visibleData) { + return; + } + const stoleFocus = this._visibleData.stoleFocus || this._hoverFocusedKey.get(); this._setHoverData(undefined); this._resizableNode.maxSize = new dom.Dimension(Infinity, Infinity); this._resizableNode.clearSashHoverState(); From ede257b88938d0c5aec83d20ec0c3354e78ae65f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 11:22:12 +0200 Subject: [PATCH 133/826] cleaning the code --- src/vs/editor/contrib/hover/browser/hover.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 0058f9179da..5fff46b45b5 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -216,12 +216,14 @@ export class ModesHoverController implements IEditorContribution { this._glyphWidget.startShowingAt(target.position.lineNumber); return; } - if (this._contentWidget?.isFocused()) { + if ( + this._contentWidget?.isFocused() + || this._contentWidget?.widget.isResizing + || _sticky + ) { return; } - if (!this._contentWidget?.widget.isResizing && !_sticky) { - this._hideWidgets(); - } + this._hideWidgets(); } private _onKeyDown(e: IKeyboardEvent): void { From dc142e4cd111b8f370d0549d8c207df7de55facd Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 14 Jul 2023 11:52:40 +0200 Subject: [PATCH 134/826] BUILD: Exclude third party notices from indentation filter (#187913) --- build/filters.js | 1 + 1 file changed, 1 insertion(+) diff --git a/build/filters.js b/build/filters.js index d729582eb6c..93cddb57ac7 100644 --- a/build/filters.js +++ b/build/filters.js @@ -66,6 +66,7 @@ module.exports.indentationFilter = [ // except specific files '!**/ThirdPartyNotices.txt', + '!**/ThirdPartyNotices.cli.txt', '!**/LICENSE.{txt,rtf}', '!LICENSES.chromium.html', '!**/LICENSE', From e2cc9c28e680170d452c0ee0e24eceb49a6a6ffd Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 14:13:39 +0200 Subject: [PATCH 135/826] change from review --- src/vs/editor/contrib/hover/browser/contentHover.ts | 3 --- src/vs/editor/contrib/hover/browser/hover.ts | 10 +++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 6ceb236f057..37cf599c4fe 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -263,9 +263,6 @@ export class ContentHoverController extends Disposable { return; } } - if (hoverResult.messages.length === 0 && this._widget.isFocused) { - return; - } this._setCurrentResult(hoverResult); } diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 5fff46b45b5..eba76ac91f8 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -155,6 +155,10 @@ export class ModesHoverController implements IEditorContribution { private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { const target = mouseEvent.target; + if (this._contentWidget?.isFocused() || this._contentWidget?.widget.isResizing) { + return; + } + if (this._isMouseDown && this._hoverClicked) { return; } @@ -216,11 +220,7 @@ export class ModesHoverController implements IEditorContribution { this._glyphWidget.startShowingAt(target.position.lineNumber); return; } - if ( - this._contentWidget?.isFocused() - || this._contentWidget?.widget.isResizing - || _sticky - ) { + if (_sticky) { return; } this._hideWidgets(); From d656306c7a72819d262dc84eb19c8c71330e2b8c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 14:14:26 +0200 Subject: [PATCH 136/826] cleaning the code more --- src/vs/editor/contrib/hover/browser/contentHover.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 37cf599c4fe..bb8c57d1307 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -263,6 +263,7 @@ export class ContentHoverController extends Disposable { return; } } + this._setCurrentResult(hoverResult); } From fb15f1df481e36aa8bf49aeeb197f7c4d0448328 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 14:20:16 +0200 Subject: [PATCH 137/826] Cleaning the code, using getters instead --- .../colorPicker/browser/colorContributions.ts | 2 +- .../contrib/hover/browser/contentHover.ts | 12 ++++++---- src/vs/editor/contrib/hover/browser/hover.ts | 24 +++++++++---------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts b/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts index 7ff5f54f248..2c041f1c749 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts @@ -60,7 +60,7 @@ export class ColorContribution extends Disposable implements IEditorContribution if (!hoverController) { return; } - if (!hoverController.isColorPickerVisible()) { + if (!hoverController.isColorPickerVisible) { const range = new Range(target.range.startLineNumber, target.range.startColumn + 1, target.range.endLineNumber, target.range.endColumn + 1); hoverController.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Mouse, false, true); } diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index bb8c57d1307..e6467686589 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -215,22 +215,26 @@ export class ContentHoverController extends Disposable { this._setCurrentResult(null); } - public isColorPickerVisible(): boolean { + get isColorPickerVisible(): boolean { return this._widget.isColorPickerVisible; } - public isVisibleFromKeyboard(): boolean { + get isVisibleFromKeyboard(): boolean { return this._widget.isVisibleFromKeyboard; } - public isVisible(): boolean { + get isVisible(): boolean { return this._widget.isVisible; } - public isFocused(): boolean { + get isFocused(): boolean { return this._widget.isFocused; } + get isResizing(): boolean { + return this._widget.isResizing; + } + public containsNode(node: Node | null | undefined): boolean { return (node ? this._widget.getDomNode().contains(node) : false); } diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index eba76ac91f8..0510a0bbc6f 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -155,7 +155,7 @@ export class ModesHoverController implements IEditorContribution { private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { const target = mouseEvent.target; - if (this._contentWidget?.isFocused() || this._contentWidget?.widget.isResizing) { + if (this._contentWidget?.isFocused || this._contentWidget?.isResizing) { return; } @@ -175,7 +175,7 @@ export class ModesHoverController implements IEditorContribution { if ( !this._isHoverSticky && target.type === MouseTargetType.CONTENT_WIDGET && target.detail === ContentHoverWidget.ID - && this._contentWidget?.isColorPickerVisible() + && this._contentWidget?.isColorPickerVisible ) { // though the hover is not sticky, the color picker needs to. return; @@ -186,7 +186,7 @@ export class ModesHoverController implements IEditorContribution { return; } - if (this._isHoverSticky && this._contentWidget?.isVisibleFromKeyboard()) { + if (this._isHoverSticky && this._contentWidget?.isVisibleFromKeyboard) { // Sticky mode is on and the hover has been shown via keyboard // so moving the mouse has no effect return; @@ -233,7 +233,7 @@ export class ModesHoverController implements IEditorContribution { const resolvedKeyboardEvent = this._keybindingService.softDispatch(e, this._editor.getDomNode()); // If the beginning of a multi-chord keybinding is pressed, or the command aims to focus the hover, set the variable to true, otherwise false - const mightTriggerFocus = (resolvedKeyboardEvent.kind === ResultKind.MoreChordsNeeded || (resolvedKeyboardEvent.kind === ResultKind.KbFound && resolvedKeyboardEvent.commandId === 'editor.action.showHover' && this._contentWidget?.isVisible())); + const mightTriggerFocus = (resolvedKeyboardEvent.kind === ResultKind.MoreChordsNeeded || (resolvedKeyboardEvent.kind === ResultKind.KbFound && resolvedKeyboardEvent.commandId === 'editor.action.showHover' && this._contentWidget?.isVisible)); if (e.keyCode !== KeyCode.Ctrl && e.keyCode !== KeyCode.Alt && e.keyCode !== KeyCode.Meta && e.keyCode !== KeyCode.Shift && !mightTriggerFocus) { @@ -246,7 +246,7 @@ export class ModesHoverController implements IEditorContribution { if (_sticky) { return; } - if ((this._isMouseDown && this._hoverClicked && this._contentWidget?.isColorPickerVisible()) || InlineSuggestionHintsContentWidget.dropDownVisible) { + if ((this._isMouseDown && this._hoverClicked && this._contentWidget?.isColorPickerVisible) || InlineSuggestionHintsContentWidget.dropDownVisible) { return; } this._hoverActivatedByColorDecoratorClick = false; @@ -262,10 +262,6 @@ export class ModesHoverController implements IEditorContribution { return this._contentWidget; } - public isColorPickerVisible(): boolean { - return this._contentWidget?.isColorPickerVisible() || false; - } - public showContentHover(range: Range, mode: HoverStartMode, source: HoverStartSource, focus: boolean, activatedByColorDecoratorClick: boolean = false): void { this._hoverActivatedByColorDecoratorClick = activatedByColorDecoratorClick; this._getOrCreateContentWidget().startShowingAtRange(range, mode, source, focus); @@ -307,8 +303,12 @@ export class ModesHoverController implements IEditorContribution { this._contentWidget?.goToBottom(); } - public isHoverVisible(): boolean | undefined { - return this._contentWidget?.isVisible(); + get isColorPickerVisible(): boolean | undefined { + return this._contentWidget?.isColorPickerVisible; + } + + get isHoverVisible(): boolean | undefined { + return this._contentWidget?.isVisible; } public dispose(): void { @@ -372,7 +372,7 @@ class ShowOrFocusHoverAction extends EditorAction { const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); const focus = editor.getOption(EditorOption.accessibilitySupport) === AccessibilitySupport.Enabled || !!args?.focus; - if (controller.isHoverVisible()) { + if (controller.isHoverVisible) { controller.focus(); } else { controller.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Keyboard, focus); From 8df24c594697be28aa9b94396d3f21113ae83eb6 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 14:22:48 +0200 Subject: [PATCH 138/826] cleaning the code --- src/vs/editor/contrib/hover/browser/contentHover.ts | 10 +++++----- src/vs/editor/contrib/hover/browser/hover.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index e6467686589..6989029fe4f 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -215,23 +215,23 @@ export class ContentHoverController extends Disposable { this._setCurrentResult(null); } - get isColorPickerVisible(): boolean { + public get isColorPickerVisible(): boolean { return this._widget.isColorPickerVisible; } - get isVisibleFromKeyboard(): boolean { + public get isVisibleFromKeyboard(): boolean { return this._widget.isVisibleFromKeyboard; } - get isVisible(): boolean { + public get isVisible(): boolean { return this._widget.isVisible; } - get isFocused(): boolean { + public get isFocused(): boolean { return this._widget.isFocused; } - get isResizing(): boolean { + public get isResizing(): boolean { return this._widget.isResizing; } diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 0510a0bbc6f..2c1e2b2611e 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -303,11 +303,11 @@ export class ModesHoverController implements IEditorContribution { this._contentWidget?.goToBottom(); } - get isColorPickerVisible(): boolean | undefined { + public get isColorPickerVisible(): boolean | undefined { return this._contentWidget?.isColorPickerVisible; } - get isHoverVisible(): boolean | undefined { + public get isHoverVisible(): boolean | undefined { return this._contentWidget?.isVisible; } From 8e1ae0da75850323611f6878a3449bee62d6250f Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Fri, 14 Jul 2023 14:19:13 +0100 Subject: [PATCH 139/826] Add proposed api to support auto closing pairs on langauge configuration (#186567) * adding support for auto closing pair --- .../api/browser/mainThreadLanguageFeatures.ts | 4 +++- src/vs/workbench/api/common/extHost.protocol.ts | 5 +++++ .../api/common/extHostLanguageFeatures.ts | 6 ++++++ .../extensions/common/extensionsApiProposals.ts | 1 + ...d.languageConfigurationAutoClosingPairs.d.ts | 17 +++++++++++++++++ 5 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 732637669fb..548cc6b8517 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -852,7 +852,9 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread __electricCharacterSupport: undefined }; - if (_configuration.__characterPairSupport) { + if (_configuration.autoClosingPairs) { + configuration.autoClosingPairs = _configuration.autoClosingPairs; + } else if (_configuration.__characterPairSupport) { // backwards compatibility configuration.autoClosingPairs = _configuration.__characterPairSupport.autoClosingPairs; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5320cb4a8b0..0dc33e49a04 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -344,6 +344,11 @@ export interface ILanguageConfigurationDto { notIn?: string[]; }[]; }; + autoClosingPairs?: { + open: string; + close: string; + notIn?: string[]; + }[]; } export type GlobPattern = string | IRelativePattern; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 78ecbea0a85..442f8292b3e 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -2533,6 +2533,10 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF `Do not use.`); } + if (configuration.autoClosingPairs) { + checkProposedApiEnabled(extension, 'languageConfigurationAutoClosingPairs'); + } + const handle = this._nextHandle(); const serializedConfiguration: extHostProtocol.ILanguageConfigurationDto = { comments: configuration.comments, @@ -2542,7 +2546,9 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF onEnterRules: configuration.onEnterRules ? ExtHostLanguageFeatures._serializeOnEnterRules(configuration.onEnterRules) : undefined, __electricCharacterSupport: configuration.__electricCharacterSupport, __characterPairSupport: configuration.__characterPairSupport, + autoClosingPairs: configuration.autoClosingPairs }; + this._proxy.$setLanguageConfiguration(handle, languageId, serializedConfiguration); return this._createDisposable(handle); } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 98f89dca708..d42bf233a5c 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -54,6 +54,7 @@ export const allApiProposals = Object.freeze({ interactiveUserActions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts', interactiveWindow: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts', ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', + languageConfigurationAutoClosingPairs: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts', notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', notebookCodeActions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCodeActions.d.ts', notebookControllerAffinityHidden: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts', diff --git a/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts b/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts new file mode 100644 index 00000000000..641e2f5f0d8 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/173738 + + export interface LanguageConfiguration { + autoClosingPairs?: { + open: string; + close: string; + notIn?: string[]; + }[]; + } +} From baabfe503d8477d7c11d0d5a9e08508353f7b496 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 16:54:00 +0200 Subject: [PATCH 140/826] revert the code that was written for the set state optimization --- .../contrib/stickyScroll/browser/stickyScrollWidget.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index 7c7260203bf..7b4a946f5f3 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -6,7 +6,6 @@ import * as dom from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { createTrustedTypesPolicy } from 'vs/base/browser/trustedTypes'; -import { equals } from 'vs/base/common/arrays'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./stickyScroll'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; @@ -22,10 +21,6 @@ export class StickyScrollWidgetState { readonly lineNumbers: number[], readonly lastLineRelativePosition: number ) { } - - public equals(other: StickyScrollWidgetState | undefined): boolean { - return !!other && this.lastLineRelativePosition === other.lastLineRelativePosition && equals(this.lineNumbers, other.lineNumbers); - } } const _ttPolicy = createTrustedTypesPolicy('stickyScrollViewLayer', { createHTML: value => value }); @@ -40,7 +35,6 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { private _lastLineRelativePosition: number = 0; private _hoverOnLine: number = -1; private _hoverOnColumn: number = -1; - private _state: StickyScrollWidgetState | undefined; constructor( private readonly _editor: ICodeEditor @@ -74,10 +68,6 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { } setState(state: StickyScrollWidgetState): void { - if (state.equals(this._state)) { - return; - } - this._state = state; dom.clearNode(this._rootDomNode); this._disposableStore.clear(); this._lineNumbers.length = 0; From f21001c1fecfd966a76ff02fd772dd823ed1d46f Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Jul 2023 17:36:58 +0200 Subject: [PATCH 141/826] wip --- .../api/browser/extensionHost.contribution.ts | 2 + .../api/browser/mainThreadChatProvider.ts | 53 ++++ .../browser/mainThreadChatSlashCommands.ts | 51 ++++ .../workbench/api/common/extHost.api.impl.ts | 19 ++ .../workbench/api/common/extHost.protocol.ts | 26 ++ .../api/common/extHostChatProvider.ts | 69 +++++ .../api/common/extHostChatSlashCommand.ts | 70 ++++++ .../api/common/extHostTypeConverters.ts | 21 ++ src/vs/workbench/api/common/extHostTypes.ts | 19 ++ .../contrib/chat/browser/chat.contribution.ts | 5 +- .../contrib/chat/common/chatProvider.ts | 68 +++++ .../contrib/chat/common/chatSlashCommands.ts | 236 ++++++++++++++++++ .../common/extensionsApiProposals.ts | 2 + .../vscode.proposed.chatProvider.d.ts | 46 ++++ .../vscode.proposed.chatSlashCommands.d.ts | 45 ++++ 15 files changed, 731 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/api/browser/mainThreadChatProvider.ts create mode 100644 src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts create mode 100644 src/vs/workbench/api/common/extHostChatProvider.ts create mode 100644 src/vs/workbench/api/common/extHostChatSlashCommand.ts create mode 100644 src/vs/workbench/contrib/chat/common/chatProvider.ts create mode 100644 src/vs/workbench/contrib/chat/common/chatSlashCommands.ts create mode 100644 src/vscode-dts/vscode.proposed.chatProvider.d.ts create mode 100644 src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index d582b31b0b0..6328cfd2df9 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -19,6 +19,8 @@ import { StatusBarItemsExtensionPoint } from 'vs/workbench/api/browser/statusBar // --- mainThread participants import './mainThreadLocalization'; import './mainThreadBulkEdits'; +import './mainThreadChatProvider'; +import './mainThreadChatSlashCommands'; import './mainThreadCodeInsets'; import './mainThreadCLICommands'; import './mainThreadClipboard'; diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts new file mode 100644 index 00000000000..bdb067ec1d8 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableMap } from 'vs/base/common/lifecycle'; +import { IProgress } from 'vs/platform/progress/common/progress'; +import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; + +@extHostNamedCustomer(MainContext.MainThreadChatProvider) +export class MainThreadChatProvider implements MainThreadChatProviderShape { + + private readonly _proxy: ExtHostChatProviderShape; + private readonly _providerRegistrations = new DisposableMap(); + private readonly _pendingProgress = new Map>(); + + constructor( + extHostContext: IExtHostContext, + @IChatProviderService private readonly _chatProviderService: IChatProviderService, + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); + } + + dispose(): void { + this._providerRegistrations.dispose(); + } + + $registerProvider(handle: number, metadata: IChatResponseProviderMetadata): void { + const registration = this._chatProviderService.registerChatResponseProvider({ + metadata, + provideChatResponse: async (messages, options, progress, token) => { + const requestId = (Math.random() * 1e6) | 0; + this._pendingProgress.set(requestId, progress); + try { + await this._proxy.$provideChatResponse(handle, requestId, messages, options, token); + } finally { + this._pendingProgress.delete(requestId); + } + } + }); + this._providerRegistrations.set(handle, registration); + } + + async $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise { + this._pendingProgress.get(requestId)?.report(chunk); + } + + $unregisterProvider(handle: number): void { + this._providerRegistrations.deleteAndDispose(handle); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts b/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts new file mode 100644 index 00000000000..839885602ed --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableMap } from 'vs/base/common/lifecycle'; +import { IProgress } from 'vs/platform/progress/common/progress'; +import { ExtHostChatSlashCommandsShape, ExtHostContext, MainContext, MainThreadChatSlashCommandsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IChatSlashCommandService, IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; + + +@extHostNamedCustomer(MainContext.MainThreadChatSlashCommands) +export class MainThreadChatSlashCommands implements MainThreadChatSlashCommandsShape { + + private readonly _commands = new DisposableMap; + private readonly _pendingProgress = new Map>(); + private readonly _proxy: ExtHostChatSlashCommandsShape; + + constructor( + extHostContext: IExtHostContext, + @IChatSlashCommandService private readonly _chatSlashCommandService: IChatSlashCommandService + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatSlashCommands); + } + + dispose(): void { + this._commands.clearAndDisposeAll(); + } + + $registerCommand(handle: number, name: string): void { + const d = this._chatSlashCommandService.registerSlashCallback(name, async (prompt, progress, history, token) => { + const requestId = Math.random(); + this._pendingProgress.set(requestId, progress); + try { + await this._proxy.$executeCommand(handle, requestId, prompt, { history }, token); + } finally { + this._pendingProgress.delete(requestId); + } + }); + this._commands.set(handle, d); + } + + async $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise { + this._pendingProgress.get(requestId)?.report(chunk); + } + + $unregisterCommand(handle: number): void { + this._commands.deleteAndDispose(handle); + } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1e585c0d3b0..14a51200438 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -104,6 +104,8 @@ import { ExtHostSemanticSimilarity } from 'vs/workbench/api/common/extHostSemant import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter'; import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; import { ExtHostShare } from 'vs/workbench/api/common/extHostShare'; +import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { ExtHostChatSlashCommands } from 'vs/workbench/api/common/extHostChatSlashCommand'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -203,6 +205,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); + const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService)); + const extHostChatSlashCommands = rpcProtocol.set(ExtHostContext.ExtHostChatSlashCommands, new ExtHostChatSlashCommands(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService)); const extHostSemanticSimilarity = rpcProtocol.set(ExtHostContext.ExtHostSemanticSimilarity, new ExtHostSemanticSimilarity(rpcProtocol)); const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol)); @@ -1324,6 +1328,18 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } }; + // namespace: llm + const llm: typeof vscode.llm = { + registerChatResponseProvider(provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { + checkProposedApiEnabled(extension, 'chatProvider'); + return extHostChatProvider.registerProvider(extension.identifier, provider, metadata); + }, + registerSlashCommand(name: string, command: vscode.SlashCommand) { + checkProposedApiEnabled(extension, 'chatSlashCommands'); + return extHostChatSlashCommands.registerCommand(extension.identifier, name, command); + } + }; + return { version: initData.version, // namespaces @@ -1337,6 +1353,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I interactive, interactiveSlashCommands, l10n, + llm, languages, notebooks, scm, @@ -1347,6 +1364,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // types Breakpoint: extHostTypes.Breakpoint, TerminalOutputAnchor: extHostTypes.TerminalOutputAnchor, + ChatMessage: extHostTypes.ChatMessage, + ChatMessageRole: extHostTypes.ChatMessageRole, CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, CallHierarchyItem: extHostTypes.CallHierarchyItem, CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5320cb4a8b0..cf713f31a88 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -74,6 +74,8 @@ import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/qu import * as search from 'vs/workbench/services/search/common/search'; import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { TerminalCommandMatchResult, TerminalQuickFixCommand, TerminalQuickFixOpener } from 'vscode'; +import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; export type TerminalQuickFix = TerminalQuickFixCommand | TerminalQuickFixOpener; @@ -1119,6 +1121,26 @@ export interface MainThreadNotebookRenderersShape extends IDisposable { export interface MainThreadInteractiveShape extends IDisposable { } +export interface MainThreadChatProviderShape extends IDisposable { + $registerProvider(handle: number, metadata: IChatResponseProviderMetadata): void; + $unregisterProvider(handle: number): void; + $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; +} + +export interface ExtHostChatProviderShape { + $provideChatResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; +} + +export interface MainThreadChatSlashCommandsShape extends IDisposable { + $registerCommand(handle: number, name: string): void; + $unregisterCommand(handle: number): void; + $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise; +} + +export interface ExtHostChatSlashCommandsShape { + $executeCommand(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise; +} + export interface MainThreadInlineChatShape extends IDisposable { $registerInteractiveEditorProvider(handle: number, debugName: string, supportsFeedback: boolean): Promise; $handleProgressChunk(requestId: string, chunk: { message?: string; edits?: languages.TextEdit[] }): Promise; @@ -2517,6 +2539,8 @@ export interface MainThreadTestingShape { export const MainContext = { MainThreadAuthentication: createProxyIdentifier('MainThreadAuthentication'), MainThreadBulkEdits: createProxyIdentifier('MainThreadBulkEdits'), + MainThreadChatProvider: createProxyIdentifier('MainThreadChatProvider'), + MainThreadChatSlashCommands: createProxyIdentifier('MainThreadChatSlashCommands'), MainThreadClipboard: createProxyIdentifier('MainThreadClipboard'), MainThreadCommands: createProxyIdentifier('MainThreadCommands'), MainThreadComments: createProxyIdentifier('MainThreadComments'), @@ -2635,6 +2659,8 @@ export const ExtHostContext = { ExtHostInteractive: createProxyIdentifier('ExtHostInteractive'), ExtHostInlineChat: createProxyIdentifier('ExtHostInlineChatShape'), ExtHostChat: createProxyIdentifier('ExtHostChat'), + ExtHostChatSlashCommands: createProxyIdentifier('ExtHostChatSlashCommands'), + ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), ExtHostSemanticSimilarity: createProxyIdentifier('ExtHostSemanticSimilarity'), ExtHostTheming: createProxyIdentifier('ExtHostTheming'), ExtHostTunnelService: createProxyIdentifier('ExtHostTunnelService'), diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts new file mode 100644 index 00000000000..067cba16aca --- /dev/null +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostChatProviderShape, IMainContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import type * as vscode from 'vscode'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; + + +type ProviderData = { + readonly extension: ExtensionIdentifier; + readonly provider: vscode.ChatResponseProvider; +}; + + +export class ExtHostChatProvider implements ExtHostChatProviderShape { + + private static _idPool = 1; + + private readonly _proxy: MainThreadChatProviderShape; + private readonly _providers = new Map(); + + constructor( + mainContext: IMainContext, + private readonly _logService: ILogService, + ) { + this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); + } + + all() { + return Array.from(this._providers.values(), v => v.provider); + } + + registerProvider(extension: ExtensionIdentifier, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { + + const handle = ExtHostChatProvider._idPool++; + this._providers.set(handle, { extension, provider }); + this._proxy.$registerProvider(handle, { extension, displayName: metadata.name ?? extension.value }); + + return toDisposable(() => { + this._proxy.$unregisterProvider(handle); + this._providers.delete(handle); + }); + } + + async $provideChatResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise { + const data = this._providers.get(handle); + if (!data) { + return; + } + const progress = new Progress(async fragment => { + if (token.isCancellationRequested) { + this._logService.warn(`[CHAT](${data.extension.value}) CANNOT send progress because the REQUEST IS CANCELLED`); + return; + } + await this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); + }, { async: true }); + + return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); + } + +} diff --git a/src/vs/workbench/api/common/extHostChatSlashCommand.ts b/src/vs/workbench/api/common/extHostChatSlashCommand.ts new file mode 100644 index 00000000000..49f99bf8d70 --- /dev/null +++ b/src/vs/workbench/api/common/extHostChatSlashCommand.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostChatSlashCommandsShape, IMainContext, MainContext, MainThreadChatSlashCommandsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { ChatMessageRole } from 'vs/workbench/api/common/extHostTypes'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import type * as vscode from 'vscode'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; + +export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { + + private static _idPool = 0; + + private readonly _commands = new Map(); + private readonly _proxy: MainThreadChatSlashCommandsShape; + + constructor( + mainContext: IMainContext, + private readonly _extHostChatProvider: ExtHostChatProvider, + private readonly _logService: ILogService, + ) { + this._proxy = mainContext.getProxy(MainContext.MainThreadChatSlashCommands); + } + + registerCommand(extension: ExtensionIdentifier, name: string, command: vscode.SlashCommand): IDisposable { + + const handle = ExtHostChatSlashCommands._idPool++; + this._commands.set(handle, { extension, command }); + this._proxy.$registerCommand(handle, name); + + return toDisposable(() => { + this._proxy.$unregisterCommand(handle); + this._commands.delete(handle); + }); + } + + async $executeCommand(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise { + const data = this._commands.get(handle); + if (!data) { + this._logService.warn(`[CHAT](${handle}) CANNOT execute command because the command is not registered`); + return; + } + + // TODO@jrieken this isn't proper, instead the code should call to the renderer which + // coordinates and picks the right provider + const provider = this._extHostChatProvider.all()[0]; + if (!provider) { + this._logService.warn(`[CHAT](${handle}) CANNOT execute command because there is no provider`); + return; + } + + await data.command( + provider, + { role: ChatMessageRole.User, content: prompt }, + { history: context.history.map(typeConvert.ChatMessage.to) }, + new Progress(p => { + this._proxy.$handleProgressChunk(requestId, { value: p.message.value }); + }), + token + ); + } +} diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index a4fa2e9340b..6eeecf18669 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -44,6 +44,7 @@ import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGro import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; +import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; export namespace Command { @@ -2155,3 +2156,23 @@ export namespace ChatFollowup { } } } + +export namespace ChatMessage { + export function to(message: chatProvider.IChatMessage): vscode.ChatMessage { + const res = new types.ChatMessage(ChatMessageRole.to(message.role), message.content); + res.name = message.name; + return res; + } +} + +export namespace ChatMessageRole { + + export function to(role: chatProvider.ChatMessageRole): vscode.ChatMessageRole { + switch (role) { + case chatProvider.ChatMessageRole.System: return types.ChatMessageRole.System; + case chatProvider.ChatMessageRole.User: return types.ChatMessageRole.User; + case chatProvider.ChatMessageRole.Assistant: return types.ChatMessageRole.Assistant; + case chatProvider.ChatMessageRole.Function: return types.ChatMessageRole.Function; + } + } +} diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 2647d41c5db..79eb3f08b26 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4095,4 +4095,23 @@ export enum InteractiveEditorResponseFeedbackKind { Undone = 2 } +export enum ChatMessageRole { + System = 0, + User = 1, + Assistant = 2, + Function = 3, +} + +export class ChatMessage implements vscode.ChatMessage { + + role: ChatMessageRole; + content: string; + name?: string; + + constructor(role: ChatMessageRole, content: string) { + this.role = role; + this.content = content; + } +} + //#endregion diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 656485ed70a..6fe1558dcf5 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -44,6 +44,8 @@ import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatCo import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction'; +import { ChatProviderService, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -201,4 +203,5 @@ registerSingleton(IChatContributionService, ChatContributionService, Instantiati registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delayed); registerSingleton(IChatAccessibilityService, ChatAccessibilityService, InstantiationType.Delayed); registerSingleton(IChatWidgetHistoryService, ChatWidgetHistoryService, InstantiationType.Delayed); - +registerSingleton(IChatProviderService, ChatProviderService, InstantiationType.Delayed); +registerSingleton(IChatSlashCommandService, ChatSlashCommandService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts new file mode 100644 index 00000000000..8ed47816d7e --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProgress } from 'vs/platform/progress/common/progress'; + +export const enum ChatMessageRole { + System, + User, + Assistant, + Function, +} + +export interface IChatMessage { + readonly role: ChatMessageRole; + readonly content: string; + readonly name?: string; +} + +export interface IChatResponseFragment { + index: number; + part: string; +} + +export interface IChatResponseProviderMetadata { + readonly extension: ExtensionIdentifier; + readonly displayName: string; + readonly description?: string; +} + +export interface IChatResponseProvider { + metadata: IChatResponseProviderMetadata; + provideChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Thenable; +} + +export const IChatProviderService = createDecorator('chatProviderService'); + +export interface IChatProviderService { + + readonly _serviceBrand: undefined; + + registerChatResponseProvider(provider: IChatResponseProvider): IDisposable; + getAllProviders(): Iterable; +} + +export class ChatProviderService implements IChatProviderService { + readonly _serviceBrand: undefined; + + private readonly _providers: Set = new Set(); + + registerChatResponseProvider(provider: IChatResponseProvider): IDisposable { + this._providers.add(provider); + return { + dispose: () => { + this._providers.delete(provider); + } + }; + } + + getAllProviders(): Iterable { + return this._providers; + } +} diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts new file mode 100644 index 00000000000..651cf22126d --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -0,0 +1,236 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Iterable } from 'vs/base/common/iterator'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProgress, IProgressService, Progress, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { ChatMessageRole, IChatMessage, IChatProviderService, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; + +//#region extension point + +const slashItem: IJSONSchema = { + type: 'object', + required: ['name', 'detail'], + properties: { + name: { + type: 'string', + markdownDescription: localize('name', "The name of the slash command which will be used as prefix.") + }, + detail: { + type: 'string', + markdownDescription: localize('details', "The details of the slash command.") + }, + } +}; + +const slashItems: IJSONSchema = { + description: localize('vscode.extension.contributes.slashes', "Contributes slash commands to chat"), + oneOf: [ + slashItem, + { + type: 'array', + items: slashItem + } + ] +}; + +export const slashesExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'slashes', + jsonSchema: slashItems +}); + +//#region slash service, commands etc + +export interface IChatSlashData { + id: string; + name: string; + detail: string; +} + +export interface IChatSlashFragment { + value: string; +} + +export type IChatSlashCallback = { (prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise }; + +export const IChatSlashCommandService = createDecorator('chatSlashCommandService'); + +export interface IChatSlashCommandService { + _serviceBrand: undefined; + registerSlashData(data: IChatSlashData): IDisposable; + registerSlashCallback(id: string, command: IChatSlashCallback): IDisposable; + executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise; + getCommands(): Array; +} + +type Tuple = { data: IChatSlashData; command?: IChatSlashCallback }; + +export class ChatSlashCommandService implements IChatSlashCommandService { + + declare _serviceBrand: undefined; + + private readonly _commands = new Map(); + + constructor(@IExtensionService private readonly _extensionService: IExtensionService) { + + const contributions = new DisposableStore(); + + slashesExtPoint.setHandler(extensions => { + contributions.clear(); + + for (const entry of extensions) { + if (!isProposedApiEnabled(entry.description, 'chatSlashCommands')) { + entry.collector.error(`The ${slashesExtPoint.name} is proposed API`); + continue; + } + + const { value } = entry; + + for (const candidate of Iterable.wrap(value)) { + contributions.add(this.registerSlashData({ ...candidate, id: candidate.name })); + } + } + }); + } + + dispose(): void { + this._commands.clear(); + } + + registerSlashData(data: IChatSlashData): IDisposable { + if (this._commands.has(data.id)) { + throw new Error(`Already registered a command with id ${data.id}}`); + } + this._commands.set(data.id, { data }); + return toDisposable(() => this._commands.delete(data.id)); + } + + registerSlashCallback(id: string, command: IChatSlashCallback): IDisposable { + const data = this._commands.get(id); + if (!data) { + throw new Error(`No command with id ${id} registered`); + } + data.command = command; + return toDisposable(() => data.command = undefined); + } + + getCommands(): Array { + return Array.from(this._commands.values(), v => v.data); + } + + async executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise { + const data = this._commands.get(id); + if (!data) { + throw new Error('No command with id ${id} NOT registered'); + } + if (!data.command) { + await this._extensionService.activateByEvent(`onSlash:${id}`); + } + if (!data.command) { + throw new Error(`No command with id ${id} NOT resolved`); + } + + await data.command(prompt, progress, history, token); + } +} + + + + +// --- debug + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'chatbot.helloWorld', + title: 'Hello World', + category: 'Chatbot', + menu: { id: MenuId.CommandPalette } + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const chatProvider = accessor.get(IChatProviderService); + + for (const provider of chatProvider.getAllProviders()) { + + const p = new Progress(value => { + console.log(provider.metadata.displayName, value); + }); + + await provider.provideChatResponse([{ role: ChatMessageRole.User, content: 'Hello.' }], { n: 2 }, p, CancellationToken.None); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'chatbot.helloWorld', + title: 'Slashes for the Masses', + category: 'Chatbot', + menu: { id: MenuId.CommandPalette } + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const quickPick = accessor.get(IQuickInputService); + const slashCommandService = accessor.get(IChatSlashCommandService); + const progress = accessor.get(IProgressService); + + const items: (IQuickPickItem & { cmd: IChatSlashData })[] = []; + for (const cmd of slashCommandService.getCommands()) { + items.push({ + cmd, + label: cmd.name, + detail: cmd.detail + }); + } + + const pick = await quickPick.pick(items, { placeHolder: 'Pick a command' }); + + if (!pick) { + return; + } + + const value = `/${pick.cmd.name} `; + const prompt = await quickPick.input({ value: value, valueSelection: [value.length, value.length], placeHolder: 'Enter a prompt' }); + + if (!prompt) { + return; + } + + const cts = new CancellationTokenSource(); + + progress.withProgress({ + location: ProgressLocation.Notification, + title: `${prompt}`, + cancellable: true, + }, async p => { + + const task = slashCommandService.executeCommand( + pick.cmd.id, + prompt, + new Progress(value => { + p.report({ message: value.value }); + console.log(value); + }), + [], + cts.token + ); + + await task; + }, () => cts.cancel()); + + } +}); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 98f89dca708..448a524e212 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -9,6 +9,8 @@ export const allApiProposals = Object.freeze({ authGetSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authGetSessions.d.ts', authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts', canonicalUriProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts', + chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', + chatSlashCommands: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', commentsDraftState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts', contribCommentEditorActionsMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts new file mode 100644 index 00000000000..e5edcafe90d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export enum ChatMessageRole { + System = 0, + User = 1, + Assistant = 2, + Function = 3, + } + + export class ChatMessage { + role: ChatMessageRole; + content: string; + name?: string; + + constructor(role: ChatMessageRole, content: string); + } + + // TODO: chat response builder + export interface ChatResponse { + message: ChatMessage; + } + + export interface ChatResponseFragment { + index: number; + part: string; + } + + export interface ChatResponseProvider { + provideChatResponse(messages: ChatMessage[], options: { [name: string]: any }, progress: Progress, token: CancellationToken): Thenable; + } + + export interface ChatResponseProviderMetadata { + // TODO: add way to compute token count + name: string; + } + + export namespace llm { + export function registerChatResponseProvider(provider: ChatResponseProvider, metadata: ChatResponseProviderMetadata): Disposable; + } + +} diff --git a/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts b/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts new file mode 100644 index 00000000000..487b458a5c7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface SlashCommandContext { + + // messages so far + history: ChatMessage[]; + + // TODO: access to embeddings + // embeddings: {}; + + // TODO: access to "InputSourceId" + // DebugConsoleOutput + // Terminal + // CorrespondingTestFile + // CorrespondingImplementationFile + // ExtensionApi + // VSCode + // Workspace + } + + export interface SlashResponse { + message: MarkdownString; + // edits?: TextEdit[] | WorkspaceEdit; + } + + export interface SlashResult { + followUp?: InteractiveSessionFollowup[]; + } + + export interface SlashCommand { + + // TODO: Progress vs AsyncIterable + // CONSUMER of chat + (chat: ChatResponseProvider, prompt: ChatMessage, context: SlashCommandContext, progress: Progress, token: CancellationToken): Thenable; + } + + export namespace llm { + export function registerSlashCommand(name: string, command: SlashCommand): Disposable; + } +} From 6bff31c75f8d4ebebf384da9c2dcbabdec4bc4b1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 14 Jul 2023 17:50:20 +0200 Subject: [PATCH 142/826] More feedback (#187934) * #156144 More feedback * :lipstick: * add telemetry --- .../platform/quickinput/browser/quickInput.ts | 3 +- .../media/userDataProfileCreateWidget.css | 28 ++ .../browser/userDataProfile.ts | 349 ++++++++---------- .../userDataProfileImportExportService.ts | 46 +-- .../userDataProfile/common/userDataProfile.ts | 5 +- 5 files changed, 204 insertions(+), 227 deletions(-) create mode 100644 src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index f9829e3d98f..c8158ad9b50 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1326,7 +1326,6 @@ export class QuickInputController extends Disposable { rightActionBar.domNode.classList.add('quick-input-right-action-bar'); const headerContainer = dom.append(container, $('.quick-input-header')); - const description1 = dom.append(container, $('.quick-input-description')); const checkAll = dom.append(headerContainer, $('input.quick-input-check-all')); checkAll.type = 'checkbox'; @@ -1379,6 +1378,8 @@ export class QuickInputController extends Disposable { const widget = dom.append(container, $('.quick-input-html-widget')); widget.tabIndex = -1; + const description1 = dom.append(container, $('.quick-input-description')); + const listId = this.idPrefix + 'list'; const list = this._register(new QuickInputList(container, listId, this.options)); inputBox.setAttribute('aria-controls', listId); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css b/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css new file mode 100644 index 00000000000..899cf1a074a --- /dev/null +++ b/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.profile-type-widget { + display: flex; + margin: 0px 6px 8px 11px; + align-items: center; + justify-content: space-between; + font-size: 12px; +} + +.profile-type-widget>.profile-type-select-container { + overflow: hidden; + padding-left: 10px; + flex: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.profile-type-widget>.profile-type-select-container>.monaco-select-box { + cursor: pointer; + line-height: 17px; + padding: 2px 23px 2px 8px; + border-radius: 2px; +} diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 9b2002bdfe2..3c8e7b62c30 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -3,20 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/userDataProfileCreateWidget'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { Event } from 'vs/base/common/event'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, IMenuService, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IUserDataProfile, IUserDataProfilesService, ProfileResourceType, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { RenameProfileAction } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfileActions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, PROFILES_CATEGORY, PROFILE_FILTER, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ProfilesMenu, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TITLE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { IQuickInputService, IQuickPickItem, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { URI } from 'vs/base/common/uri'; @@ -31,32 +30,18 @@ import { IRequestService, asJson } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; import Severity from 'vs/base/common/severity'; - -const CREATE_EMPTY_PROFILE_ACTION_ID = 'workbench.profiles.actions.createEmptyProfile'; -const CREATE_EMPTY_PROFILE_ACTION_TITLE = { - value: localize('create empty profile', "Create an Empty Profile..."), - original: 'Create an Empty Profile...' -}; - -const CREATE_FROM_CURRENT_PROFILE_ACTION_ID = 'workbench.profiles.actions.createFromCurrentProfile'; -const CREATE_FROM_CURRENT_PROFILE_ACTION_TITLE = { - value: localize('save profile as', "Save Current Profile As..."), - original: 'Save Current Profile As...' -}; - -const CREATE_NEW_PROFILE_ACTION_ID = 'workbench.profiles.actions.createNewProfile'; -const CREATE_NEW_PROFILE_ACTION_TITLE = { - value: localize('create new profile', "Create New Profile..."), - original: 'Create New Profile...' -}; +import { $, append } from 'vs/base/browser/dom'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { isString } from 'vs/base/common/types'; interface IProfileTemplateInfo { readonly name: string; readonly url: string; } -type IProfileTemplateQuickPickItem = IQuickPickItem & IProfileTemplateInfo; - export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { private readonly currentProfileContext: IContextKey; @@ -77,6 +62,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements @ILifecycleService private readonly lifecycleService: ILifecycleService, @IProductService private readonly productService: IProductService, @IRequestService private readonly requestService: IRequestService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextViewService private readonly contextViewService: IContextViewService, @ILogService private readonly logService: ILogService, ) { super(); @@ -115,9 +102,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.registerCurrentProfilesActions(); this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => this.registerCurrentProfilesActions())); - this.registerCreateEmptyProfileAction(); this.registerCreateFromCurrentProfileAction(); - this.registerCreateNewProfileAction(); this.registerCreateProfileAction(); this.registerDeleteProfileAction(); @@ -214,21 +199,21 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements private readonly currentprofileActionsDisposable = this._register(new MutableDisposable()); private registerCurrentProfilesActions(): void { this.currentprofileActionsDisposable.value = new DisposableStore(); - this.currentprofileActionsDisposable.value.add(this.registerRenameCurrentProfileAction()); + this.currentprofileActionsDisposable.value.add(this.registerEditCurrentProfileAction()); this.currentprofileActionsDisposable.value.add(this.registerShowCurrentProfileContentsAction()); this.currentprofileActionsDisposable.value.add(this.registerExportCurrentProfileAction()); this.currentprofileActionsDisposable.value.add(this.registerImportProfileAction()); } - private registerRenameCurrentProfileAction(): IDisposable { + private registerEditCurrentProfileAction(): IDisposable { const that = this; return registerAction2(class RenameCurrentProfileAction extends Action2 { constructor() { super({ - id: `workbench.profiles.actions.renameCurrentProfile`, + id: `workbench.profiles.actions.editCurrentProfile`, title: { - value: localize('rename profile', "Rename..."), - original: `Rename...` + value: localize('edit profile', "Edit..."), + original: `Edit...` }, menu: [ { @@ -240,8 +225,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements ] }); } - async run(accessor: ServicesAccessor) { - accessor.get(ICommandService).executeCommand(RenameProfileAction.ID, that.userDataProfileService.currentProfile); + run() { + return that.saveProfile(that.userDataProfileService.currentProfile); } }); } @@ -413,8 +398,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this._register(registerAction2(class CreateFromCurrentProfileAction extends Action2 { constructor() { super({ - id: CREATE_FROM_CURRENT_PROFILE_ACTION_ID, - title: CREATE_FROM_CURRENT_PROFILE_ACTION_TITLE, + id: 'workbench.profiles.actions.createFromCurrentProfile', + title: { + value: localize('save profile as', "Save Current Profile As..."), + original: 'Save Current Profile As...' + }, category: PROFILES_CATEGORY, f1: true, precondition: PROFILES_ENABLEMENT_CONTEXT @@ -422,86 +410,35 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } run(accessor: ServicesAccessor) { - return that.createFromCurrentProfile(); + return that.saveProfile(undefined, that.userDataProfileService.currentProfile); } })); } - private registerCreateNewProfileAction(): void { - const that = this; - this._register(registerAction2(class CreateFromCurrentProfileAction extends Action2 { - constructor() { - super({ - id: CREATE_NEW_PROFILE_ACTION_ID, - title: CREATE_NEW_PROFILE_ACTION_TITLE, - category: PROFILES_CATEGORY, - f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT - }); - } + private async saveProfile(profile: IUserDataProfile): Promise; + private async saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | string): Promise; + private async saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | string): Promise { - run() { - return that.createNewProfile(); - } - })); - } + type CreateProfileInfoClassification = { + owner: 'sandy081'; + comment: 'Report when profile is about to be created'; + }; + this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.startCreate'); - private registerCreateEmptyProfileAction(): void { - const that = this; - this._register(registerAction2(class CreateEmptyProfileAction extends Action2 { - constructor() { - super({ - id: CREATE_EMPTY_PROFILE_ACTION_ID, - title: CREATE_EMPTY_PROFILE_ACTION_TITLE, - category: PROFILES_CATEGORY, - f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT - }); - } + const disposables = new DisposableStore(); + const title = profile ? localize('save profile', "Edit Profile...") : localize('create new profle', "Create New Profile..."); - run(accessor: ServicesAccessor) { - return that.createEmptyProfile(); - } - })); - } - - private async createEmptyProfile(): Promise { - const name = await this.getNameForProfile(localize('create empty profile', "Create an Empty Profile...")); - if (!name) { - return; - } - try { - await this.userDataProfileManagementService.createAndEnterProfile(name, undefined); - } catch (error) { - this.notificationService.error(error); - } - } - - private async createFromCurrentProfile(): Promise { - const name = await this.getNameForProfile(localize('create from current profle', "Create from Current Profile...")); - if (!name) { - return; - } - try { - await this.userDataProfileImportExportService.SaveCurrentProfileAs(name); - } catch (error) { - this.notificationService.error(error); - } - } - - private async createNewProfile(): Promise { - const title = localize('create new profle', "Create New Profile..."); - - const settings: IQuickPickItem = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: true }; - const keybindings: IQuickPickItem = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: true }; - const snippets: IQuickPickItem = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: true }; - const tasks: IQuickPickItem = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: true }; - const extensions: IQuickPickItem = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: true }; + const settings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: profile?.useDefaultFlags?.settings }; + const keybindings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: profile?.useDefaultFlags?.keybindings }; + const snippets: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: profile?.useDefaultFlags?.snippets }; + const tasks: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: profile?.useDefaultFlags?.tasks }; + const extensions: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: profile?.useDefaultFlags?.extensions }; const resources = [settings, keybindings, snippets, tasks, extensions]; const quickPick = this.quickInputService.createQuickPick(); quickPick.title = title; - quickPick.placeholder = localize('name placeholder', "Name the new profile"); + quickPick.placeholder = localize('name placeholder', "Profile name"); + quickPick.value = profile?.name ?? ''; quickPick.canSelectMany = true; quickPick.matchOnDescription = false; quickPick.matchOnDetail = false; @@ -512,42 +449,109 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements quickPick.customButton = true; quickPick.hideCheckAll = true; quickPick.ignoreFocusOut = true; - quickPick.customLabel = localize('create', "Create Profile"); - quickPick.description = localize('customise the profile', "Choose what to configure in the profile. Unselected items are shared from the default profile."); - quickPick.items = resources; - quickPick.selectedItems = resources.filter(item => item.picked); + quickPick.customLabel = profile ? localize('save', "Save") : localize('create', "Create"); + quickPick.description = localize('customise the profile', "Select configurations to share from the Default profile:"); + quickPick.items = [...resources]; + + const updateSelection = () => { + quickPick.selectedItems = resources.filter(item => item.picked); + }; + updateSelection(); + + const validate = () => { + if (!profile && this.userDataProfilesService.profiles.some(p => p.name === quickPick.value)) { + quickPick.validationMessage = localize('profileExists', "Profile with name {0} already exists.", quickPick.value); + quickPick.severity = Severity.Error; + return; + } + if (resources.every(resource => resource.picked)) { + quickPick.validationMessage = localize('cannot share all', "Cannot share all configurations from the Default Profile."); + quickPick.severity = Severity.Error; + return; + } + quickPick.severity = Severity.Ignore; + quickPick.validationMessage = undefined; + }; - const disposables = new DisposableStore(); disposables.add(quickPick.onDidChangeSelection(items => { for (const resource of resources) { resource.picked = items.includes(resource); } + validate(); })); - disposables.add(quickPick.onDidChangeValue(value => { - if (this.userDataProfilesService.profiles.some(p => p.name === value)) { - quickPick.validationMessage = localize('profileExists', "Profile with name {0} already exists.", value); - quickPick.severity = Severity.Error; - } else { - quickPick.severity = Severity.Ignore; - quickPick.validationMessage = undefined; - } - })); + disposables.add(quickPick.onDidChangeValue(validate)); let result: { name: string; items: ReadonlyArray } | undefined; - disposables.add(quickPick.onDidCustom(async () => { + disposables.add(Event.any(quickPick.onDidCustom, quickPick.onDidAccept)(() => { if (!quickPick.value) { quickPick.validationMessage = localize('name required', "Provide a name for the new profile"); quickPick.severity = Severity.Error; + } + if (quickPick.validationMessage) { return; } - if (resources.some(resource => quickPick.selectedItems.includes(resource))) { - result = { name: quickPick.value, items: quickPick.selectedItems }; - quickPick.hide(); - } + result = { name: quickPick.value, items: quickPick.selectedItems }; + quickPick.hide(); quickPick.severity = Severity.Ignore; quickPick.validationMessage = undefined; })); + + if (!profile) { + const domNode = $('.profile-type-widget'); + append(domNode, $('.profile-type-create-label', undefined, localize('create from', "Copy from:"))); + const separator = { text: '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500', isDisabled: true }; + const profileOptions: (ISelectOptionItem & { id?: string; source?: IUserDataProfile | string })[] = []; + profileOptions.push({ text: localize('empty profile', "None") }); + const templates = await this.getProfileTemplatesFromProduct(); + if (templates.length) { + profileOptions.push({ ...separator, decoratorRight: localize('from templates', "Profile Templates") }); + for (const template of templates) { + profileOptions.push({ text: template.name, id: template.url, source: template.url }); + } + } + profileOptions.push({ ...separator, decoratorRight: localize('from existing profiles', "Existing Profiles") }); + for (const profile of this.userDataProfilesService.profiles) { + profileOptions.push({ text: profile.name, id: profile.id, source: profile }); + } + + const findOptionIndex = () => { + const index = profileOptions.findIndex(option => { + if (isString(source)) { + return option.id === source; + } else if (source) { + return option.id === source.id; + } + return false; + }); + return index > -1 ? index : 0; + }; + + const selectBox = disposables.add(this.instantiationService.createInstance(SelectBox, profileOptions, findOptionIndex(), this.contextViewService, defaultSelectBoxStyles, { useCustomDrawn: true })); + selectBox.render(append(domNode, $('.profile-type-select-container'))); + quickPick.widget = domNode; + + const updateOptions = () => { + const index = findOptionIndex(); + if (index <= 0) { + return; + } + const option = profileOptions[index]; + if (!isString(option.source)) { + for (const resource of resources) { + resource.picked = option.source?.useDefaultFlags?.[resource.id]; + } + updateSelection(); + } + }; + + updateOptions(); + disposables.add(selectBox.onDidSelect(({ index }) => { + source = profileOptions[index].source; + updateOptions(); + })); + } + quickPick.show(); await new Promise((c, e) => { @@ -558,37 +562,36 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }); if (!result) { + this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.cancelCreate'); return; } - const { name, items } = result; + this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.successCreate'); + try { - const useDefaultFlags: UseDefaultProfileFlags | undefined = items.length !== resources.length ? { - settings: !items.includes(settings), - keybindings: !items.includes(keybindings), - snippets: !items.includes(snippets), - tasks: !items.includes(tasks), - extensions: !items.includes(extensions) + const useDefaultFlags: UseDefaultProfileFlags | undefined = result.items.length ? { + settings: result.items.includes(settings), + keybindings: result.items.includes(keybindings), + snippets: result.items.includes(snippets), + tasks: result.items.includes(tasks), + extensions: result.items.includes(extensions) } : undefined; - await this.userDataProfileManagementService.createAndEnterProfile(name, { useDefaultFlags }); + if (profile) { + await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, useDefaultFlags }); + } else { + if (isString(source)) { + await this.userDataProfileImportExportService.importProfile(URI.parse(source), { mode: 'apply', name: result.name, useDefaultFlags }); + } else if (source) { + await this.userDataProfileImportExportService.createFromProfile(source, result.name, { useDefaultFlags }); + } else { + await this.userDataProfileManagementService.createAndEnterProfile(result.name, { useDefaultFlags }); + } + } } catch (error) { this.notificationService.error(error); } } - private async getNameForProfile(title: string): Promise { - return this.quickInputService.input({ - placeHolder: localize('name', "Profile name"), - title, - validateInput: async (value: string) => { - if (this.userDataProfilesService.profiles.some(p => p.name === value)) { - return localize('profileExists', "Profile with name {0} already exists.", value); - } - return undefined; - } - }); - } - private registerCreateProfileAction(): void { const that = this; this._register(registerAction2(class CreateProfileAction extends Action2 { @@ -601,6 +604,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }, category: PROFILES_CATEGORY, precondition: PROFILES_ENABLEMENT_CONTEXT, + f1: true, menu: [ { id: ProfilesMenu, @@ -613,52 +617,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } async run(accessor: ServicesAccessor) { - const quickInputService = accessor.get(IQuickInputService); - const commandService = accessor.get(ICommandService); - const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); - const quickPickItems: QuickPickItem[] = [{ - id: CREATE_EMPTY_PROFILE_ACTION_ID, - label: localize('empty profile', "Empty Profile..."), - }, { - id: CREATE_NEW_PROFILE_ACTION_ID, - label: localize('new profile', "New Profile..."), - }, { - type: 'separator', - }, { - id: CREATE_FROM_CURRENT_PROFILE_ACTION_ID, - label: localize('using current', "Save Current Profile As..."), - }]; - const profileTemplateQuickPickItems = await that.getProfileTemplatesQuickPickItems(); - if (profileTemplateQuickPickItems.length) { - quickPickItems.push({ - type: 'separator', - label: localize('templates', "Profile Templates") - }, ...profileTemplateQuickPickItems); - } - const pick = await quickInputService.pick(quickPickItems, - { - hideInput: true, - canPickMany: false, - title: localize('create profile title', "Create Profile...") - }); - if (pick) { - if (pick.id) { - return commandService.executeCommand(pick.id); - } - if ((pick).url) { - type ProfileCreationFromTemplateActionClassification = { - owner: 'sandy081'; - comment: 'Report profile creation from template action'; - profileName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Name of the profile created from template' }; - }; - type ProfileCreationFromTemplateActionEvent = { - profileName: string; - }; - that.telemetryService.publicLog2('profileCreationAction:builtinTemplate', { profileName: (pick).name }); - const uri = URI.parse((pick).url); - return userDataProfileImportExportService.importProfile(uri, { mode: 'apply' }); - } - } + return that.saveProfile(); } })); } @@ -736,18 +695,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements })); } - private async getProfileTemplatesQuickPickItems(): Promise { - const quickPickItems: IProfileTemplateQuickPickItem[] = []; - const profileTemplates = await this.getProfileTemplatesFromProduct(); - for (const template of profileTemplates) { - quickPickItems.push({ - label: template.name, - ...template - }); - } - return quickPickItems; - } - private async getProfileTemplatesFromProduct(): Promise { if (this.productService.profileTemplatesUrl) { try { diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 3fe9085063c..dbfc7ae2f6e 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -18,7 +18,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Extensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, TreeItemCollapsibleState, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IUserDataProfile, IUserDataProfilesService, ProfileResourceType, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, ProfileResourceType, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -209,11 +209,11 @@ export class UserDataProfileImportExportService extends Disposable implements IU return; } if (mode === 'preview') { - await this.previewProfile(profileTemplate); + await this.previewProfile(profileTemplate, options); } else if (mode === 'apply') { - await this.createAndSwitch(profileTemplate, false, true, localize('create profile', "Create Profile")); + await this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); } else if (mode === 'both') { - await this.importAndPreviewProfile(uri, profileTemplate); + await this.importAndPreviewProfile(uri, profileTemplate, options); } } finally { disposables.dispose(); @@ -249,11 +249,11 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - async SaveCurrentProfileAs(name: string): Promise { - const userDataProfilesExportState = this.instantiationService.createInstance(UserDataProfileExportState, this.userDataProfileService.currentProfile); + async createFromProfile(profile: IUserDataProfile, name: string, options?: IUserDataProfileOptions): Promise { + const userDataProfilesExportState = this.instantiationService.createInstance(UserDataProfileExportState, profile); try { const profileTemplate = await userDataProfilesExportState.getProfileTemplate(name, undefined); - await this.createAndSwitch(profileTemplate, false, true, localize('save profile as', "Save Profile As")); + await this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); } finally { userDataProfilesExportState.dispose(); } @@ -269,7 +269,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU sticky: true, }, async progress => { const reportProgress = (message: string) => progress.report({ message: localize('troubleshoot profile progress', "Setting up Troubleshoot Profile: {0}", message) }); - const profile = await this.createProfile(profileTemplate, true, false, reportProgress); + const profile = await this.createProfile(profileTemplate, true, false, { useDefaultFlags: this.userDataProfileService.currentProfile.useDefaultFlags }, reportProgress); if (profile) { reportProgress(localize('progress extensions', "Applying Extensions...")); await this.instantiationService.createInstance(ExtensionsResource).copy(this.userDataProfileService.currentProfile, profile, true); @@ -362,21 +362,21 @@ export class UserDataProfileImportExportService extends Disposable implements IU return profileTemplate; } - private async importAndPreviewProfile(uri: URI, profileTemplate: IUserDataProfileTemplate): Promise { + private async importAndPreviewProfile(uri: URI, profileTemplate: IUserDataProfileTemplate, options: IUserDataProfileOptions | undefined): Promise { const disposables = new DisposableStore(); try { const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate)); profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); - const importedProfile = await this.createAndSwitch(profileTemplate, true, false, localize('preview profile', "Preview Profile")); + const importedProfile = await this.createAndSwitch(profileTemplate, true, false, options, localize('preview profile', "Preview Profile")); if (!importedProfile) { return; } const barrier = new Barrier(); - const importAction = this.getCreateAction(barrier, userDataProfileImportState); + const importAction = this.getCreateAction(barrier, userDataProfileImportState, options); const primaryAction = isWeb ? new Action('importInDesktop', localize('import in desktop', "Create Profile in {0}", this.productService.nameLong), undefined, true, async () => this.openerService.open(uri, { openExternal: true })) : importAction; @@ -435,15 +435,15 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - private async previewProfile(profileTemplate: IUserDataProfileTemplate): Promise { + private async previewProfile(profileTemplate: IUserDataProfileTemplate, options: IUserDataProfileOptions | undefined): Promise { const disposables = new DisposableStore(); try { const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate)); if (userDataProfileImportState.isEmpty()) { - await this.createAndSwitch(profileTemplate, false, true, localize('create profile', "Create Profile")); + await this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); } else { const barrier = new Barrier(); - const importAction = this.getCreateAction(barrier, userDataProfileImportState); + const importAction = this.getCreateAction(barrier, userDataProfileImportState, options); await this.showProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW, profileTemplate.name, importAction, new BarrierAction(barrier, new Action('cancel', localize('cancel', "Cancel")), this.notificationService), false, userDataProfileImportState); await barrier.wait(); await this.hideProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW); @@ -453,16 +453,16 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - private getCreateAction(barrier: Barrier, userDataProfileImportState: UserDataProfileImportState): IAction { + private getCreateAction(barrier: Barrier, userDataProfileImportState: UserDataProfileImportState, options: IUserDataProfileOptions | undefined): IAction { const importAction = new BarrierAction(barrier, new Action('title', localize('import', "Create Profile"), undefined, true, async () => { importAction.enabled = false; const profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); - return this.createAndSwitch(profileTemplate, false, true, localize('create profile', "Create Profile")); + return this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); }), this.notificationService); return importAction; } - private async createAndSwitch(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, title: string): Promise { + private async createAndSwitch(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, options: IUserDataProfileOptions | undefined, title: string): Promise { return this.progressService.withProgress({ location: ProgressLocation.Notification, delay: 500, @@ -471,7 +471,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU title = `${title} (${profileTemplate.name})`; progress.report({ message: title }); const reportProgress = (message: string) => progress.report({ message: `${title}: ${message}` }); - const profile = await this.createProfile(profileTemplate, temporaryProfile, extensions, reportProgress); + const profile = await this.createProfile(profileTemplate, temporaryProfile, extensions, options, reportProgress); if (profile) { reportProgress(localize('switching profile', "Switching Profile...")); await this.userDataProfileManagementService.switchProfile(profile); @@ -480,8 +480,8 @@ export class UserDataProfileImportExportService extends Disposable implements IU }); } - private async createProfile(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, progress: (message: string) => void): Promise { - const profile = await this.getProfileToImport(profileTemplate, temporaryProfile); + private async createProfile(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, options: IUserDataProfileOptions | undefined, progress: (message: string) => void): Promise { + const profile = await this.getProfileToImport(profileTemplate, temporaryProfile, options); if (!profile) { return undefined; } @@ -569,12 +569,12 @@ export class UserDataProfileImportExportService extends Disposable implements IU return result?.id; } - private async getProfileToImport(profileTemplate: IUserDataProfileTemplate, temp: boolean): Promise { + private async getProfileToImport(profileTemplate: IUserDataProfileTemplate, temp: boolean, options: IUserDataProfileOptions | undefined): Promise { const profileName = profileTemplate.name; const profile = this.userDataProfilesService.profiles.find(p => p.name === profileName); if (profile) { if (temp) { - return this.userDataProfilesService.createNamedProfile(`${profileName} ${this.getProfileNameIndex(profileName)}`, { shortName: profileTemplate.shortName, transient: temp }); + return this.userDataProfilesService.createNamedProfile(`${profileName} ${this.getProfileNameIndex(profileName)}`, { ...options, shortName: profileTemplate.shortName, transient: temp }); } enum ImportProfileChoice { @@ -625,7 +625,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU } return this.userDataProfilesService.createNamedProfile(name); } else { - return this.userDataProfilesService.createNamedProfile(profileName, { shortName: profileTemplate.shortName, transient: temp }); + return this.userDataProfilesService.createNamedProfile(profileName, { ...options, shortName: profileTemplate.shortName, transient: temp }); } } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index bd126081ab0..3c56ae564f2 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -73,7 +73,8 @@ export function toUserDataProfileUri(path: string, productService: IProductServi }); } -export interface IProfileImportOptions { +export interface IProfileImportOptions extends IUserDataProfileOptions { + readonly name?: string; readonly mode?: 'preview' | 'apply' | 'both'; } @@ -87,7 +88,7 @@ export interface IUserDataProfileImportExportService { exportProfile(): Promise; importProfile(uri: URI, options?: IProfileImportOptions): Promise; showProfileContents(): Promise; - SaveCurrentProfileAs(name: string): Promise; + createFromProfile(profile: IUserDataProfile, name: string, options?: IUserDataProfileOptions): Promise; createTroubleshootProfile(): Promise; setProfile(profile: IUserDataProfileTemplate): Promise; } From 80b3fcc08deccbbd4e4f844762f905ef78692a8c Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Fri, 14 Jul 2023 09:51:43 -0700 Subject: [PATCH 143/826] Notebook Find widget doesn't remove decorations on empty query (#187749) * Notebook Find widget doesn't remove decorations on empty query Fixes #187748 * send empty strings to find --- .../notebook/browser/contrib/find/findModel.ts | 12 +++++------- .../browser/view/renderers/backLayerWebView.ts | 4 ++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index 1d955dbbcdd..5c58ffda1bc 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -471,6 +471,9 @@ export class FindModel extends Disposable { } private async _compute(token: CancellationToken): Promise { + if (!this._notebookEditor.hasModel()) { + return null; + } let ret: CellFindMatchWithIndex[] | null = null; const val = this._state.searchString; const wordSeparators = this._configurationService.inspect('editor.wordSeparators').value; @@ -485,13 +488,8 @@ export class FindModel extends Disposable { includeMarkupPreview: !!this._state.filters?.markupPreview, includeOutput: !!this._state.filters?.codeOutput }; - if (!val) { - ret = null; - } else if (!this._notebookEditor.hasModel()) { - ret = null; - } else { - ret = await this._notebookEditor.find(val, options, token); - } + + ret = await this._notebookEditor.find(val, options, token); if (token.isCancellationRequested) { return null; 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 4a3c05451ea..d45c567c327 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -1610,6 +1610,10 @@ export class BackLayerWebView extends Themable { async find(query: string, options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean; shouldGetSearchPreviewInfo: boolean; ownerID: string }): Promise { if (query === '') { + this._sendMessageToWebview({ + type: 'findStop', + ownerID: options.ownerID + }); return []; } From b84241ef4cdec078fa22030ec340e4a20e022d1f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 14 Jul 2023 10:03:05 -0700 Subject: [PATCH 144/826] part of #163506 --- .../contrib/codeEditor/browser/outline/documentSymbolsTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 2974894d3ca..36d335f61ca 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -49,7 +49,7 @@ export class DocumentSymbolAccessibilityProvider implements IListAccessibilityPr if (element instanceof OutlineGroup) { return element.label; } else { - return element.symbol.name; + return `${element.symbol.name} Symbol: ${SymbolKinds.toIcon(element.symbol.kind).id.replaceAll('symbol-', '')} `; } } } From bac433ac7b196f4980a9b192a944c9bab5d7477e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 14 Jul 2023 10:12:35 -0700 Subject: [PATCH 145/826] make it a function --- src/vs/editor/common/languages.ts | 4 ++++ src/vs/monaco.d.ts | 2 ++ .../contrib/codeEditor/browser/outline/documentSymbolsTree.ts | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index d589e8aa275..f42c695cc77 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1189,6 +1189,10 @@ export namespace SymbolKinds { } return icon; } + + export function asAriaLabel(label: string, kind: SymbolKind): string { + return `${label} Symbol: ${SymbolKinds.toIcon(kind).id.replaceAll('symbol-', '')} `; + } } export interface DocumentSymbol { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 770c1268f3a..4901b152ff2 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7459,6 +7459,8 @@ declare namespace monaco.languages { Deprecated = 1 } + function asAriaLabel(label: string, kind: SymbolKind): string; + export interface DocumentSymbol { name: string; detail: string; diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 36d335f61ca..69680c0af9d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -49,7 +49,7 @@ export class DocumentSymbolAccessibilityProvider implements IListAccessibilityPr if (element instanceof OutlineGroup) { return element.label; } else { - return `${element.symbol.name} Symbol: ${SymbolKinds.toIcon(element.symbol.kind).id.replaceAll('symbol-', '')} `; + return SymbolKinds.asAriaLabel(element.symbol.name, element.symbol.kind); } } } From ed7c51f064d483554ea5df4fad253817cf90d0ca Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 14 Jul 2023 10:15:54 -0700 Subject: [PATCH 146/826] localize --- .../contrib/codeEditor/browser/outline/documentSymbolsTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 69680c0af9d..f5d9755eb38 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -49,7 +49,7 @@ export class DocumentSymbolAccessibilityProvider implements IListAccessibilityPr if (element instanceof OutlineGroup) { return element.label; } else { - return SymbolKinds.asAriaLabel(element.symbol.name, element.symbol.kind); + return localize('document-symbol-aria-label', "{0}", SymbolKinds.asAriaLabel(element.symbol.name, element.symbol.kind)); } } } From 48d1ad43faa2f20a1f1b7693ea9bf1f1cfbb7a83 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 14 Jul 2023 19:18:29 +0200 Subject: [PATCH 147/826] Uses diff editor v2 in inline chat. (#187928) --- .../diffEditorWidget2/diffEditorWidget2.ts | 4 ++ .../widget/embeddedCodeEditorWidget.ts | 47 +++++++++++++++++++ .../browser/inlineChatLivePreviewWidget.ts | 4 +- .../inlineChat/browser/inlineChatWidget.ts | 8 ++-- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts index 1dab8ec750e..2e3980aac92 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts @@ -220,6 +220,10 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { })); } + public getContentHeight() { + return this._editors.modified.getContentHeight(); + } + protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly, editorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget { const editor = instantiationService.createInstance(CodeEditorWidget, container, options, editorWidgetOptions); return editor; diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 553470a8494..06c449a33ea 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -20,6 +20,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { DiffEditorWidget2 } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2'; export class EmbeddedCodeEditorWidget extends CodeEditorWidget { @@ -67,6 +68,9 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { } } +/** + * @deprecated Use EmbeddedDiffEditorWidget2 instead. + */ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { private readonly _parentEditor: ICodeEditor; @@ -111,3 +115,46 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { super.updateOptions(this._overwriteOptions); } } + +/** + * TODO: Rename to EmbeddedDiffEditorWidget once EmbeddedDiffEditorWidget is removed. + */ +export class EmbeddedDiffEditorWidget2 extends DiffEditorWidget2 { + + private readonly _parentEditor: ICodeEditor; + private readonly _overwriteOptions: IDiffEditorOptions; + + constructor( + domElement: HTMLElement, + options: Readonly, + codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, + parentEditor: ICodeEditor, + @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService instantiationService: IInstantiationService, + @ICodeEditorService codeEditorService: ICodeEditorService, + ) { + super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService); + + this._parentEditor = parentEditor; + this._overwriteOptions = options; + + // Overwrite parent's options + super.updateOptions(this._overwriteOptions); + + this._register(parentEditor.onDidChangeConfiguration(e => this._onParentConfigurationChanged(e))); + } + + getParentEditor(): ICodeEditor { + return this._parentEditor; + } + + private _onParentConfigurationChanged(e: ConfigurationChangedEvent): void { + super.updateOptions(this._parentEditor.getRawOptions()); + super.updateOptions(this._overwriteOptions); + } + + override updateOptions(newOptions: IEditorOptions): void { + objects.mixin(this._overwriteOptions, newOptions, true); + super.updateOptions(this._overwriteOptions); + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index 9fd5b51465c..3f38c66ae4e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -7,7 +7,7 @@ import { Dimension, h } from 'vs/base/browser/dom'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget2 } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; @@ -62,7 +62,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { .getEditorContributions() .filter(c => c.id !== INLINE_CHAT_ID && c.id !== FoldingController.ID); - this._diffEditor = instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.domNode, { + this._diffEditor = instantiationService.createInstance(EmbeddedDiffEditorWidget2, this._elements.domNode, { scrollbar: { useShadows: false, alwaysConsumeMouseWheel: false }, scrollBeyondLastLine: false, renderMarginRevertIcon: true, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 48165a82036..e2c4e8a394e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -22,7 +22,7 @@ import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { IModelService } from 'vs/editor/common/services/model'; import { URI } from 'vs/base/common/uri'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget2 } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; @@ -164,7 +164,7 @@ export class InlineChatWidget { private readonly _progressBar: ProgressBar; - private readonly _previewDiffEditor: IdleValue; + private readonly _previewDiffEditor: IdleValue; private readonly _previewDiffModel = this._store.add(new MutableDisposable()); private readonly _previewCreateTitle: ResourceLabel; @@ -329,7 +329,7 @@ export class InlineChatWidget { this._store.add(feedbackToolbar); // preview editors - this._previewDiffEditor = new IdleValue(() => this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, _previewEditorEditorOptions, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, parentEditor))); + this._previewDiffEditor = new IdleValue(() => this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget2, this._elements.previewDiff, _previewEditorEditorOptions, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, parentEditor))); this._previewCreateTitle = this._store.add(_instantiationService.createInstance(ResourceLabel, this._elements.previewCreateTitle, { supportIcons: true })); this._previewCreateEditor = new IdleValue(() => this._store.add(_instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, parentEditor))); @@ -408,7 +408,7 @@ export class InlineChatWidget { const base = getTotalHeight(this._elements.progress) + getTotalHeight(this._elements.status); const editorHeight = this._inputEditor.getContentHeight() + 12 /* padding and border */; const markdownMessageHeight = getTotalHeight(this._elements.markdownMessage); - const previewDiffHeight = this._previewDiffEditor.value.getModel().modified ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0; + const previewDiffHeight = this._previewDiffEditor.value.getModel() ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0; const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle); const previewCreateHeight = this._previewCreateEditor.value.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())) : 0; return base + editorHeight + markdownMessageHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + 18 /* padding */ + 8 /*shadow*/; From 599c7e7639f7b00bbcb81b6c958fae993c3cff7e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 14 Jul 2023 10:19:42 -0700 Subject: [PATCH 148/826] revert change --- src/vs/monaco.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 4901b152ff2..770c1268f3a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7459,8 +7459,6 @@ declare namespace monaco.languages { Deprecated = 1 } - function asAriaLabel(label: string, kind: SymbolKind): string; - export interface DocumentSymbol { name: string; detail: string; From 8dcddb412477dadd79a263805fec3a6616e0ab26 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 14 Jul 2023 10:39:24 -0700 Subject: [PATCH 149/826] Have auth proxy use application storage service (#187094) * Have auth proxy use application storage service Since this code is already using the encryption service, it already takes advantage of the better encryption provided by Electron so it can be stored in the normal storage service. With that said, we do need a migration path, so this handles that migration. * re-encrypt to force using the new electron algorithm * it should already be removed from the old location --- src/vs/code/electron-main/auth.ts | 55 +++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index 9e56bcfa902..6a18eba4eb5 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -8,11 +8,14 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { hash } from 'vs/base/common/hash'; import { Disposable } from 'vs/base/common/lifecycle'; +import { withNullAsUndefined } from 'vs/base/common/types'; import { generateUuid } from 'vs/base/common/uuid'; import { ICredentialsMainService } from 'vs/platform/credentials/common/credentials'; import { IEncryptionMainService } from 'vs/platform/encryption/common/encryptionService'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; +import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IApplicationStorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails { @@ -56,7 +59,8 @@ enum ProxyAuthState { export class ProxyAuthHandler extends Disposable { - private readonly PROXY_CREDENTIALS_SERVICE_KEY = `${this.productService.urlProtocol}.proxy-credentials`; + private readonly OLD_PROXY_CREDENTIALS_SERVICE_KEY = `${this.productService.urlProtocol}.proxy-credentials`; + private readonly PROXY_CREDENTIALS_SERVICE_KEY = 'proxy-credentials://'; private pendingProxyResolve: Promise | undefined = undefined; @@ -69,6 +73,7 @@ export class ProxyAuthHandler extends Disposable { @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @ICredentialsMainService private readonly credentialsService: ICredentialsMainService, @IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService, + @IApplicationStorageMainService private readonly applicationStorageMainService: IApplicationStorageMainService, @IProductService private readonly productService: IProductService ) { super(); @@ -141,6 +146,34 @@ export class ProxyAuthHandler extends Disposable { return undefined; } + // TODO: remove this migration in a release or two. + private async getAndMigrateProxyCredentials(authInfoHash: string): Promise<{ storedUsername: string | undefined; storedPassword: string | undefined }> { + // Find any previously stored credentials + try { + let encryptedSerializedProxyCredentials = this.applicationStorageMainService.get(this.PROXY_CREDENTIALS_SERVICE_KEY + authInfoHash, StorageScope.APPLICATION); + let decryptedSerializedProxyCredentials: string | undefined; + if (!encryptedSerializedProxyCredentials) { + encryptedSerializedProxyCredentials = withNullAsUndefined(await this.credentialsService.getPassword(this.OLD_PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash)); + if (encryptedSerializedProxyCredentials) { + // re-encrypt to force new encryption algorithm to apply + decryptedSerializedProxyCredentials = await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials); + encryptedSerializedProxyCredentials = await this.encryptionMainService.encrypt(decryptedSerializedProxyCredentials); + this.applicationStorageMainService.store(this.PROXY_CREDENTIALS_SERVICE_KEY + authInfoHash, encryptedSerializedProxyCredentials, StorageScope.APPLICATION, StorageTarget.MACHINE); + // Remove it from the old location since it's in the new location. + await this.credentialsService.deletePassword(this.OLD_PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); + } + } + if (encryptedSerializedProxyCredentials) { + const credentials: Credentials = JSON.parse(decryptedSerializedProxyCredentials ?? await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials)); + + return { storedUsername: credentials.username, storedPassword: credentials.password }; + } + } catch (error) { + this.logService.error(error); // handle errors by asking user for login via dialog + } + return { storedUsername: undefined, storedPassword: undefined }; + } + private async doResolveProxyCredentials(authInfo: AuthInfo): Promise { this.logService.trace('auth#doResolveProxyCredentials - enter', authInfo); @@ -149,21 +182,7 @@ export class ProxyAuthHandler extends Disposable { // given the properties of the auth request // (see https://github.com/microsoft/vscode/issues/109497) const authInfoHash = String(hash({ scheme: authInfo.scheme, host: authInfo.host, port: authInfo.port })); - - // Find any previously stored credentials - let storedUsername: string | undefined = undefined; - let storedPassword: string | undefined = undefined; - try { - const encryptedSerializedProxyCredentials = await this.credentialsService.getPassword(this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); - if (encryptedSerializedProxyCredentials) { - const credentials: Credentials = JSON.parse(await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials)); - - storedUsername = credentials.username; - storedPassword = credentials.password; - } - } catch (error) { - this.logService.error(error); // handle errors by asking user for login via dialog - } + const { storedUsername, storedPassword } = await this.getAndMigrateProxyCredentials(authInfoHash); // Reply with stored credentials unless we used them already. // In that case we need to show a login dialog again because @@ -212,9 +231,9 @@ export class ProxyAuthHandler extends Disposable { try { if (reply.remember) { const encryptedSerializedCredentials = await this.encryptionMainService.encrypt(JSON.stringify(credentials)); - await this.credentialsService.setPassword(this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials); + this.applicationStorageMainService.store(this.PROXY_CREDENTIALS_SERVICE_KEY + authInfoHash, encryptedSerializedCredentials, StorageScope.APPLICATION, StorageTarget.MACHINE); } else { - await this.credentialsService.deletePassword(this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); + this.applicationStorageMainService.remove(this.PROXY_CREDENTIALS_SERVICE_KEY + authInfoHash, StorageScope.APPLICATION); } } catch (error) { this.logService.error(error); // handle gracefully From 88b4c049d0d09003fbe5efb5da46c1aa5bfe7ef8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 14 Jul 2023 11:10:11 -0700 Subject: [PATCH 150/826] localize --- src/vs/editor/common/languages.ts | 3 ++- src/vs/monaco.d.ts | 2 ++ .../contrib/codeEditor/browser/outline/documentSymbolsTree.ts | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index f42c695cc77..0e2c97189d5 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -21,6 +21,7 @@ import { LanguageId } from 'vs/editor/common/encodedTokenAttributes'; import * as model from 'vs/editor/common/model'; import { TokenizationRegistry as TokenizationRegistryImpl } from 'vs/editor/common/tokenizationRegistry'; import { ContiguousMultilineTokens } from 'vs/editor/common/tokens/contiguousMultilineTokens'; +import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IMarkerData } from 'vs/platform/markers/common/markers'; @@ -1191,7 +1192,7 @@ export namespace SymbolKinds { } export function asAriaLabel(label: string, kind: SymbolKind): string { - return `${label} Symbol: ${SymbolKinds.toIcon(kind).id.replaceAll('symbol-', '')} `; + return localize('symbolAriaLabel', "{0} Symbol: {1}", label, SymbolKinds.toIcon(kind).id.replaceAll('symbol-', '')); } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 770c1268f3a..4901b152ff2 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7459,6 +7459,8 @@ declare namespace monaco.languages { Deprecated = 1 } + function asAriaLabel(label: string, kind: SymbolKind): string; + export interface DocumentSymbol { name: string; detail: string; diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index f5d9755eb38..ada95c86308 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -49,7 +49,7 @@ export class DocumentSymbolAccessibilityProvider implements IListAccessibilityPr if (element instanceof OutlineGroup) { return element.label; } else { - return localize('document-symbol-aria-label', "{0}", SymbolKinds.asAriaLabel(element.symbol.name, element.symbol.kind)); + return SymbolKinds.asAriaLabel(element.symbol.name, element.symbol.kind); } } } @@ -195,7 +195,7 @@ export class DocumentSymbolRenderer implements ITreeRenderer Date: Fri, 14 Jul 2023 11:13:11 -0700 Subject: [PATCH 151/826] revert a change --- .../contrib/codeEditor/browser/outline/documentSymbolsTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index ada95c86308..69680c0af9d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -195,7 +195,7 @@ export class DocumentSymbolRenderer implements ITreeRenderer Date: Fri, 14 Jul 2023 11:19:24 -0700 Subject: [PATCH 152/826] fix: refresh tests button causing error (#187948) Fixes #187770 --- src/vs/workbench/contrib/testing/browser/testExplorerActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index a5bce0dab7e..e17df8e852d 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -1421,7 +1421,7 @@ export class RefreshTestsAction extends Action2 { const testService = accessor.get(ITestService); const progressService = accessor.get(IProgressService); - const controllerIds = distinct(elements.map(e => e.test.controllerId)); + const controllerIds = distinct(elements.filter(isDefined).map(e => e.test.controllerId)); return progressService.withProgress({ location: Testing.ViewletId }, async () => { if (controllerIds.length) { await Promise.all(controllerIds.map(id => testService.refreshTests(id))); From bbed4d12951990a7a6fe402e5f2c451637288678 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 14 Jul 2023 11:24:02 -0700 Subject: [PATCH 153/826] add internal --- src/vs/editor/common/languages.ts | 3 +++ src/vs/monaco.d.ts | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 0e2c97189d5..c445cb52ea6 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1191,6 +1191,9 @@ export namespace SymbolKinds { return icon; } + /** + * @internal + */ export function asAriaLabel(label: string, kind: SymbolKind): string { return localize('symbolAriaLabel', "{0} Symbol: {1}", label, SymbolKinds.toIcon(kind).id.replaceAll('symbol-', '')); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 4901b152ff2..770c1268f3a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7459,8 +7459,6 @@ declare namespace monaco.languages { Deprecated = 1 } - function asAriaLabel(label: string, kind: SymbolKind): string; - export interface DocumentSymbol { name: string; detail: string; From 0729d2c1a9d618795026cc6848faa865d4d874ac Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 14 Jul 2023 11:33:42 -0700 Subject: [PATCH 154/826] Remove unused eslint private error suppression (#187522) For #186737 --- .../editor/contrib/codeAction/browser/codeActionController.ts | 2 -- src/vs/editor/contrib/codeAction/browser/codeActionModel.ts | 2 -- .../contrib/webviewPanel/browser/webviewWorkbenchService.ts | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 5b8d6576b14..14307f031e5 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* eslint-disable local/code-no-native-private */ - import { getDomNodePagePosition } from 'vs/base/browser/dom'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { IAction } from 'vs/base/common/actions'; diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts index 19963bfe57f..b21d5f2dc5d 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* eslint-disable local/code-no-native-private */ - import { CancelablePromise, createCancelablePromise, TimeoutTimer } from 'vs/base/common/async'; import { isCancellationError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts index 39750b798b2..1bb57c73a74 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* eslint-disable local/code-no-native-private */ - import { CancelablePromise, createCancelablePromise, DeferredPromise } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; From 6c94c8e08883eaa344d727344c1cd8429cceeace Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 14 Jul 2023 13:36:38 -0500 Subject: [PATCH 155/826] [typescript-language-features] Update autoImportFileExcludePatterns description (#186528) Update autoImportFileExcludePatterns description --- extensions/typescript-language-features/package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index bd4dd6366ad..81260c405e1 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -152,7 +152,7 @@ "typescript.preferences.includePackageJsonAutoImports.auto": "Search dependencies based on estimated performance impact.", "typescript.preferences.includePackageJsonAutoImports.on": "Always search dependencies.", "typescript.preferences.includePackageJsonAutoImports.off": "Never search dependencies.", - "typescript.preferences.autoImportFileExcludePatterns": "Specify glob patterns of files to exclude from auto imports. Requires using TypeScript 4.8 or newer in the workspace.", + "typescript.preferences.autoImportFileExcludePatterns": "Specify glob patterns of files to exclude from auto imports. Relative paths are resolved relative to the workspace root. Patterns are evaluated using tsconfig.json [`exclude`](https://www.typescriptlang.org/tsconfig#exclude) semantics. Requires using TypeScript 4.8 or newer in the workspace.", "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code.", "typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.", "typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.", From d7b089d5e3b5a56f131ace79b10b26ea0982da34 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Jul 2023 20:38:25 +0200 Subject: [PATCH 156/826] proper request making, ensure fetcher cannot be used after command is done --- .../api/browser/mainThreadChatProvider.ts | 11 +++++-- .../workbench/api/common/extHost.protocol.ts | 3 ++ .../api/common/extHostChatProvider.ts | 25 +++++++++++----- .../api/common/extHostChatSlashCommand.ts | 29 ++++++++++++++----- .../api/common/extHostTypeConverters.ts | 21 ++++++++++++++ .../contrib/chat/common/chatProvider.ts | 14 ++++++--- .../contrib/chat/common/chatSlashCommands.ts | 13 +++------ 7 files changed, 87 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index bdb067ec1d8..082c0b417d9 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableMap } from 'vs/base/common/lifecycle'; -import { IProgress } from 'vs/platform/progress/common/progress'; +import { IProgress, Progress } from 'vs/platform/progress/common/progress'; import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChatProvider) @@ -50,4 +51,10 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { $unregisterProvider(handle: number): void { this._providerRegistrations.deleteAndDispose(handle); } + + async $fetchResponse(requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { + return this._chatProviderService.fetchChatResponse(messages, options, new Progress(value => { + this._proxy.$handleResponseFragment(requestId, value); + }), token); + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index cf713f31a88..a209eb6a7e4 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1125,10 +1125,13 @@ export interface MainThreadChatProviderShape extends IDisposable { $registerProvider(handle: number, metadata: IChatResponseProviderMetadata): void; $unregisterProvider(handle: number): void; $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; + + $fetchResponse(requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise; } export interface ExtHostChatProviderShape { $provideChatResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; + $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } export interface MainThreadChatSlashCommandsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 067cba16aca..99b55b9cb32 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -10,16 +10,14 @@ import { ExtHostChatProviderShape, IMainContext, MainContext, MainThreadChatProv import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; -import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatMessage, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; - type ProviderData = { readonly extension: ExtensionIdentifier; readonly provider: vscode.ChatResponseProvider; }; - export class ExtHostChatProvider implements ExtHostChatProviderShape { private static _idPool = 1; @@ -34,10 +32,6 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); } - all() { - return Array.from(this._providers.values(), v => v.provider); - } - registerProvider(extension: ExtensionIdentifier, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { const handle = ExtHostChatProvider._idPool++; @@ -66,4 +60,21 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); } + //#region --- making request + + private readonly _pendingRequest = new Map>(); + + async makeChatRequest(messages: vscode.ChatMessage[], options: { [name: string]: any }, progress: vscode.Progress, token: CancellationToken) { + const requestId = (Math.random() * 1e6) | 0; + this._pendingRequest.set(requestId, progress); + try { + await this._proxy.$fetchResponse(requestId, messages.map(typeConvert.ChatMessage.from), options, token); + } finally { + this._pendingRequest.delete(requestId); + } + } + + async $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise { + this._pendingRequest.get(requestId)?.report(chunk); + } } diff --git a/src/vs/workbench/api/common/extHostChatSlashCommand.ts b/src/vs/workbench/api/common/extHostChatSlashCommand.ts index 49f99bf8d70..1a0375199c4 100644 --- a/src/vs/workbench/api/common/extHostChatSlashCommand.ts +++ b/src/vs/workbench/api/common/extHostChatSlashCommand.ts @@ -14,6 +14,7 @@ import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { raceCancellation } from 'vs/base/common/async'; export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { @@ -49,22 +50,36 @@ export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { return; } - // TODO@jrieken this isn't proper, instead the code should call to the renderer which - // coordinates and picks the right provider - const provider = this._extHostChatProvider.all()[0]; - if (!provider) { - this._logService.warn(`[CHAT](${handle}) CANNOT execute command because there is no provider`); - return; + let done = false; + const that = this; + function throwIfDone() { + if (done) { + throw new Error('Only valid while executing the command'); + } } - await data.command( + const provider: vscode.ChatResponseProvider = { + provideChatResponse(messages, options, progress, token) { + throwIfDone(); + return that._extHostChatProvider.makeChatRequest(messages, options, progress, token); + } + }; + + const task = data.command( provider, { role: ChatMessageRole.User, content: prompt }, { history: context.history.map(typeConvert.ChatMessage.to) }, new Progress(p => { + throwIfDone(); this._proxy.$handleProgressChunk(requestId, { value: p.message.value }); }), token ); + + try { + await raceCancellation(Promise.resolve(task), token); + } finally { + done = true; + } } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 6eeecf18669..a0390023241 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2163,8 +2163,18 @@ export namespace ChatMessage { res.name = message.name; return res; } + + + export function from(message: vscode.ChatMessage): chatProvider.IChatMessage { + return { + role: ChatMessageRole.from(message.role), + content: message.content, + name: message.name + }; + } } + export namespace ChatMessageRole { export function to(role: chatProvider.ChatMessageRole): vscode.ChatMessageRole { @@ -2175,4 +2185,15 @@ export namespace ChatMessageRole { case chatProvider.ChatMessageRole.Function: return types.ChatMessageRole.Function; } } + + export function from(role: vscode.ChatMessageRole): chatProvider.ChatMessageRole { + switch (role) { + case types.ChatMessageRole.System: return chatProvider.ChatMessageRole.System; + case types.ChatMessageRole.Assistant: return chatProvider.ChatMessageRole.Assistant; + case types.ChatMessageRole.Function: return chatProvider.ChatMessageRole.Function; + case types.ChatMessageRole.User: + default: + return chatProvider.ChatMessageRole.User; + } + } } diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index 8ed47816d7e..a905a6e267a 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { Iterable } from 'vs/base/common/iterator'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -35,7 +36,7 @@ export interface IChatResponseProviderMetadata { export interface IChatResponseProvider { metadata: IChatResponseProviderMetadata; - provideChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Thenable; + provideChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } export const IChatProviderService = createDecorator('chatProviderService'); @@ -45,7 +46,8 @@ export interface IChatProviderService { readonly _serviceBrand: undefined; registerChatResponseProvider(provider: IChatResponseProvider): IDisposable; - getAllProviders(): Iterable; + + fetchChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } export class ChatProviderService implements IChatProviderService { @@ -62,7 +64,11 @@ export class ChatProviderService implements IChatProviderService { }; } - getAllProviders(): Iterable { - return this._providers; + fetchChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { + const provider = Iterable.first(this._providers); // TODO@jrieken have plan how N providers are handled + if (!provider) { + throw new Error('NO chat provider registered'); + } + return provider.provideChatResponse(messages, options, progress, token); } } diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts index 651cf22126d..1b4cbcc31e6 100644 --- a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -145,8 +145,6 @@ export class ChatSlashCommandService implements IChatSlashCommandService { } - - // --- debug registerAction2(class extends Action2 { @@ -162,14 +160,11 @@ registerAction2(class extends Action2 { override async run(accessor: ServicesAccessor, ...args: any[]): Promise { const chatProvider = accessor.get(IChatProviderService); - for (const provider of chatProvider.getAllProviders()) { + const p = new Progress(value => { + console.log(value); + }); + await chatProvider.fetchChatResponse([{ role: ChatMessageRole.User, content: 'Hello.' }], { n: 2 }, p, CancellationToken.None); - const p = new Progress(value => { - console.log(provider.metadata.displayName, value); - }); - - await provider.provideChatResponse([{ role: ChatMessageRole.User, content: 'Hello.' }], { n: 2 }, p, CancellationToken.None); - } } }); From 5eb82ae6c400e8ff5eb3ab89809a4669c6b05bd5 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 14 Jul 2023 11:41:23 -0700 Subject: [PATCH 157/826] debug: better copying of eval expression results (#187951) For #187784. Needs DAP methods for a complete solution. --- .../workbench/contrib/debug/browser/repl.ts | 35 +++++++++++++++---- .../contrib/debug/browser/replViewer.ts | 4 +-- .../contrib/debug/common/replModel.ts | 12 +++---- .../contrib/debug/test/browser/repl.test.ts | 4 +-- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index a65674a80f9..b12c97f0b8f 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -19,13 +19,14 @@ import { HistoryNavigator } from 'vs/base/common/history'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { removeAnsiEscapeCodes } from 'vs/base/common/strings'; +import { ThemeIcon } from 'vs/base/common/themables'; import { URI as uri } from 'vs/base/common/uri'; import 'vs/css!./media/repl'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; +import { EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IDecorationOptions } from 'vs/editor/common/editorCommon'; @@ -55,7 +56,6 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ThemeIcon } from 'vs/base/common/themables'; import { FilterViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -63,10 +63,10 @@ import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/d import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { ReplFilter } from 'vs/workbench/contrib/debug/browser/replFilter'; -import { ReplAccessibilityProvider, ReplDataSource, ReplDelegate, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplGroupRenderer, ReplRawObjectsRenderer, ReplOutputElementRenderer, ReplVariablesRenderer } from 'vs/workbench/contrib/debug/browser/replViewer'; -import { CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_REPL, CONTEXT_MULTI_SESSION_REPL, DEBUG_SCHEME, getStateLabel, IDebugConfiguration, IDebugService, IDebugSession, IReplConfiguration, IReplElement, IReplOptions, REPL_VIEW_ID, State } from 'vs/workbench/contrib/debug/common/debug'; +import { ReplAccessibilityProvider, ReplDataSource, ReplDelegate, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplGroupRenderer, ReplOutputElementRenderer, ReplRawObjectsRenderer, ReplVariablesRenderer } from 'vs/workbench/contrib/debug/browser/replViewer'; +import { CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_REPL, CONTEXT_MULTI_SESSION_REPL, DEBUG_SCHEME, IDebugConfiguration, IDebugService, IDebugSession, IReplConfiguration, IReplElement, IReplOptions, REPL_VIEW_ID, State, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; -import { ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; +import { ReplEvaluationResult, ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const $ = dom.$; @@ -1045,12 +1045,33 @@ registerAction2(class extends Action2 { async run(accessor: ServicesAccessor, element: IReplElement): Promise { const clipboardService = accessor.get(IClipboardService); + const debugService = accessor.get(IDebugService); const nativeSelection = window.getSelection(); const selectedText = nativeSelection?.toString(); if (selectedText && selectedText.length > 0) { - await clipboardService.writeText(selectedText); + return clipboardService.writeText(selectedText); } else if (element) { - await clipboardService.writeText(element.toString()); + return clipboardService.writeText(await this.tryEvaluateAndCopy(debugService, element) || element.toString()); + } + } + + private async tryEvaluateAndCopy(debugService: IDebugService, element: IReplElement): Promise { + // todo: we should expand DAP to allow copying more types here (#187784) + if (!(element instanceof ReplEvaluationResult)) { + return; + } + + const stackFrame = debugService.getViewModel().focusedStackFrame; + const session = debugService.getViewModel().focusedSession; + if (!stackFrame || !session || !session.capabilities.supportsClipboardContext) { + return; + } + + try { + const evaluation = await session.evaluate(element.originalExpression, stackFrame.frameId, 'clipboard'); + return evaluation?.body.result; + } catch (e) { + return; } } }); diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 4193677ab1e..1ba443a95f1 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -239,14 +239,14 @@ export class ReplVariablesRenderer extends AbstractExpressionsRenderer, _index: number, data: IExpressionTemplateData): void { const element = node.element; - super.renderExpressionElement(element instanceof ReplVariableElement ? element.expr : element, node, data); + super.renderExpressionElement(element instanceof ReplVariableElement ? element.expression : element, node, data); } protected renderExpression(expression: IExpression | ReplVariableElement, data: IExpressionTemplateData, highlights: IHighlight[]): void { const isReplVariable = expression instanceof ReplVariableElement; if (isReplVariable || !expression.name) { data.label.set(''); - renderExpressionValue(isReplVariable ? expression.expr : expression, data.value, { showHover: false, colorize: true, linkDetector: this.linkDetector }); + renderExpressionValue(isReplVariable ? expression.expression : expression, data.value, { showHover: false, colorize: true, linkDetector: this.linkDetector }); data.expression.classList.remove('nested-variable'); } else { renderVariable(expression as Variable, data, true, highlights, this.linkDetector); diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 9e28f1d66d1..4402ed3f3b5 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -76,19 +76,19 @@ export class ReplVariableElement implements INestingReplElement { private readonly id = generateUuid(); constructor( - public readonly expr: IExpression, + public readonly expression: IExpression, public readonly severity: severity, public readonly sourceData?: IReplElementSource, ) { - this.hasChildren = expr.hasChildren; + this.hasChildren = expression.hasChildren; } getChildren(): IReplElement[] | Promise { - return this.expr.getChildren(); + return this.expression.getChildren(); } toString(): string { - return this.expr.toString(); + return this.expression.toString(); } getId(): string { @@ -169,7 +169,7 @@ export class ReplEvaluationResult extends ExpressionContainer implements IReplEl return this._available; } - constructor() { + constructor(public readonly originalExpression: string) { super(undefined, undefined, 0, generateUuid()); } @@ -271,7 +271,7 @@ export class ReplModel { async addReplExpression(session: IDebugSession, stackFrame: IStackFrame | undefined, name: string): Promise { this.addReplElement(new ReplEvaluationInput(name)); - const result = new ReplEvaluationResult(); + const result = new ReplEvaluationResult(name); await result.evaluateExpression(name, session, stackFrame, 'repl'); this.addReplElement(result); } diff --git a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index 276f3e74aec..a04f2ed1963 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -51,8 +51,8 @@ suite('Debug - REPL', () => { const keyValueObject = { 'key1': 2, 'key2': 'value' }; repl.appendToRepl(session, { output: '', expression: new RawObjectReplElement('fakeid', 'fake', keyValueObject), sev: severity.Info }); const element = repl.getReplElements()[3]; - assert.strictEqual(element.expr.value, 'Object'); - assert.deepStrictEqual((element.expr as RawObjectReplElement).valueObj, keyValueObject); + assert.strictEqual(element.expression.value, 'Object'); + assert.deepStrictEqual((element.expression as RawObjectReplElement).valueObj, keyValueObject); repl.removeReplExpressions(); assert.strictEqual(repl.getReplElements().length, 0); From ef42b49e806b4a4f1202ec2e31f441465a7047de Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 14 Jul 2023 11:53:47 -0700 Subject: [PATCH 158/826] Support proxies in the telemetry service (#187952) --- src/vs/code/node/cliProcessMain.ts | 5 +- .../node/sharedProcess/sharedProcessMain.ts | 5 +- src/vs/platform/telemetry/node/1dsAppender.ts | 89 +++++++++++++++---- src/vs/server/node/serverServices.ts | 5 +- .../contrib/debug/node/telemetryApp.ts | 2 +- 5 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 0065a8da62e..a003267c043 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -192,7 +192,8 @@ class CliMain extends Disposable { services.set(IUriIdentityService, new UriIdentityService(fileService)); // Request - services.set(IRequestService, new SyncDescriptor(RequestService, undefined, true)); + const requestService = new RequestService(configurationService, environmentService, logService, loggerService); + services.set(IRequestService, requestService); // Download Service services.set(IDownloadService, new SyncDescriptor(DownloadService, undefined, true)); @@ -212,7 +213,7 @@ class CliMain extends Disposable { const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { if (productService.aiConfig && productService.aiConfig.ariaKey) { - appenders.push(new OneDataSystemAppender(isInternal, 'monacoworkbench', null, productService.aiConfig.ariaKey)); + appenders.push(new OneDataSystemAppender(requestService, isInternal, 'monacoworkbench', null, productService.aiConfig.ariaKey)); } const config: ITelemetryServiceConfig = { diff --git a/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts index d8b6d791e74..f911b0da431 100644 --- a/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -251,7 +251,8 @@ class SharedProcessMain extends Disposable { services.set(IUriIdentityService, uriIdentityService); // Request - services.set(IRequestService, new RequestChannelClient(mainProcessService.getChannel('request'))); + const requestService = new RequestChannelClient(mainProcessService.getChannel('request')); + services.set(IRequestService, requestService); // Checksum services.set(IChecksumService, new SyncDescriptor(ChecksumService, undefined, false /* proxied to other processes */)); @@ -279,7 +280,7 @@ class SharedProcessMain extends Disposable { const logAppender = new TelemetryLogAppender(logService, loggerService, environmentService, productService); appenders.push(logAppender); if (productService.aiConfig?.ariaKey) { - const collectorAppender = new OneDataSystemAppender(internalTelemetry, 'monacoworkbench', null, productService.aiConfig.ariaKey); + const collectorAppender = new OneDataSystemAppender(requestService, internalTelemetry, 'monacoworkbench', null, productService.aiConfig.ariaKey); this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data appenders.push(collectorAppender); } diff --git a/src/vs/platform/telemetry/node/1dsAppender.ts b/src/vs/platform/telemetry/node/1dsAppender.ts index 8d3d82ca50c..94f0dc30692 100644 --- a/src/vs/platform/telemetry/node/1dsAppender.ts +++ b/src/vs/platform/telemetry/node/1dsAppender.ts @@ -4,41 +4,100 @@ *--------------------------------------------------------------------------------------------*/ import type { IPayloadData, IXHROverride } from '@microsoft/1ds-post-js'; -import * as https from 'https'; +import { streamToBuffer } from 'vs/base/common/buffer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IRequestOptions } from 'vs/base/parts/request/common/request'; +import { IRequestService } from 'vs/platform/request/common/request'; import { AbstractOneDataSystemAppender, IAppInsightsCore } from 'vs/platform/telemetry/common/1dsAppender'; +/** + * Completes a request to submit telemetry to the server utilizing the request service + * @param options The options which will be used to make the request + * @param requestService The request service + * @returns An object containing the headers, statusCode, and responseData + */ +async function makeTelemetryRequest(options: IRequestOptions, requestService: IRequestService) { + const response = await requestService.request(options, CancellationToken.None); + const responseData = (await streamToBuffer(response.stream)).toString(); + const statusCode = response.res.statusCode ?? 200; + const headers = response.res.headers as Record; + return { + headers, + statusCode, + responseData + }; +} + +/** + * Complete a request to submit telemetry to the server utilizing the https module. Only used when the request service is not available + * @param options The options which will be used to make the request + * @param httpsModule The https node module + * @returns An object containing the headers, statusCode, and responseData + */ +function makeLegacyTelemetryRequest(options: IRequestOptions, httpsModule: typeof import('https')) { + const httpsOptions = { + method: options.type, + headers: options.headers + }; + const req = httpsModule.request(options.url ?? '', httpsOptions, res => { + res.on('data', function (responseData) { + return { + headers: res.headers as Record, + statusCode: res.statusCode ?? 200, + responseData: responseData.toString() + }; + }); + // On response with error send status of 0 and a blank response to oncomplete so we can retry events + res.on('error', function (err) { + throw err; + }); + }); + req.write(options.data); + req.end(); + return; +} + export class OneDataSystemAppender extends AbstractOneDataSystemAppender { constructor( + requestService: IRequestService | undefined, isInternalTelemetry: boolean, eventPrefix: string, defaultData: { [key: string]: any } | null, iKeyOrClientFactory: string | (() => IAppInsightsCore), // allow factory function for testing ) { + let httpsModule: typeof import('https') | undefined; + if (!requestService) { + httpsModule = require('https'); + } // Override the way events get sent since node doesn't have XHTMLRequest const customHttpXHROverride: IXHROverride = { sendPOST: (payload: IPayloadData, oncomplete) => { - const options = { - method: 'POST', + + const telemetryRequestData = typeof payload.data === 'string' ? payload.data : new TextDecoder().decode(payload.data); + const requestOptions: IRequestOptions = { + type: 'POST', headers: { ...payload.headers, 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(payload.data) - } + 'Content-Length': Buffer.byteLength(payload.data).toString() + }, + url: payload.urlString, + data: telemetryRequestData }; + try { - const req = https.request(payload.urlString, options, res => { - res.on('data', function (responseData) { - oncomplete(res.statusCode ?? 200, res.headers as Record, responseData.toString()); + if (requestService) { + makeTelemetryRequest(requestOptions, requestService).then(({ statusCode, headers, responseData }) => { + oncomplete(statusCode, headers, responseData); }); - // On response with error send status of 0 and a blank response to oncomplete so we can retry events - res.on('error', function (err) { - oncomplete(0, {}); - }); - }); - req.write(payload.data); - req.end(); + } else { + if (!httpsModule) { + throw new Error('https module is undefined'); + } + makeLegacyTelemetryRequest(requestOptions, httpsModule); + } } catch { // If it errors out, send status of 0 and a blank response to oncomplete so we can retry events oncomplete(0, {}); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index 910bbe0e5d1..d291af5dd1c 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -144,13 +144,14 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IExtensionHostStatusService, extensionHostStatusService); // Request - services.set(IRequestService, new SyncDescriptor(RequestService)); + const requestService = new RequestService(configurationService, environmentService, logService, loggerService); + services.set(IRequestService, requestService); let oneDsAppender: ITelemetryAppender = NullAppender; const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { if (productService.aiConfig && productService.aiConfig.ariaKey) { - oneDsAppender = new OneDataSystemAppender(isInternal, eventPrefix, null, productService.aiConfig.ariaKey); + oneDsAppender = new OneDataSystemAppender(requestService, isInternal, eventPrefix, null, productService.aiConfig.ariaKey); disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data } diff --git a/src/vs/workbench/contrib/debug/node/telemetryApp.ts b/src/vs/workbench/contrib/debug/node/telemetryApp.ts index 6296aeb862e..802a806486f 100644 --- a/src/vs/workbench/contrib/debug/node/telemetryApp.ts +++ b/src/vs/workbench/contrib/debug/node/telemetryApp.ts @@ -7,7 +7,7 @@ import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; -const appender = new OneDataSystemAppender(false, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]); +const appender = new OneDataSystemAppender(undefined, false, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]); process.once('exit', () => appender.flush()); const channel = new TelemetryAppenderChannel([appender]); From b607d75611e29cb5c5846d1425c3415d0c7d1319 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 14 Jul 2023 12:03:42 -0700 Subject: [PATCH 159/826] move things around --- src/vs/base/common/iconLabels.ts | 3 +- src/vs/editor/common/languages.ts | 40 +++++++++++++++---- .../browser/gotoSymbolQuickAccess.ts | 4 +- .../standalone/browser/standaloneLanguages.ts | 1 + src/vs/monaco.d.ts | 2 + .../browser/outline/documentSymbolsTree.ts | 35 ++-------------- 6 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/vs/base/common/iconLabels.ts b/src/vs/base/common/iconLabels.ts index 089afb9455c..eb657f5d15b 100644 --- a/src/vs/base/common/iconLabels.ts +++ b/src/vs/base/common/iconLabels.ts @@ -6,7 +6,6 @@ import { IMatch, matchesFuzzy } from 'vs/base/common/filters'; import { ltrim } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; - const iconStartMarker = '$('; const iconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameExpression}(?:${ThemeIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups @@ -48,6 +47,8 @@ export function getCodiconAriaLabel(text: string | undefined) { } + + export interface IParsedLabelWithIcons { readonly text: string; readonly iconOffsets?: readonly number[]; diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index c445cb52ea6..17b1c5e3448 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1143,6 +1143,39 @@ export const enum SymbolKind { TypeParameter = 25 } +export const symbolKindNames: { [symbol: number]: string } = { + [SymbolKind.Array]: localize('Array', "array"), + [SymbolKind.Boolean]: localize('Boolean', "boolean"), + [SymbolKind.Class]: localize('Class', "class"), + [SymbolKind.Constant]: localize('Constant', "constant"), + [SymbolKind.Constructor]: localize('Constructor', "constructor"), + [SymbolKind.Enum]: localize('Enum', "enumeration"), + [SymbolKind.EnumMember]: localize('EnumMember', "enumeration member"), + [SymbolKind.Event]: localize('Event', "event"), + [SymbolKind.Field]: localize('Field', "field"), + [SymbolKind.File]: localize('File', "file"), + [SymbolKind.Function]: localize('Function', "function"), + [SymbolKind.Interface]: localize('Interface', "interface"), + [SymbolKind.Key]: localize('Key', "key"), + [SymbolKind.Method]: localize('Method', "method"), + [SymbolKind.Module]: localize('Module', "module"), + [SymbolKind.Namespace]: localize('Namespace', "namespace"), + [SymbolKind.Null]: localize('Null', "null"), + [SymbolKind.Number]: localize('Number', "number"), + [SymbolKind.Object]: localize('Object', "object"), + [SymbolKind.Operator]: localize('Operator', "operator"), + [SymbolKind.Package]: localize('Package', "package"), + [SymbolKind.Property]: localize('Property', "property"), + [SymbolKind.String]: localize('String', "string"), + [SymbolKind.Struct]: localize('Struct', "struct"), + [SymbolKind.TypeParameter]: localize('TypeParameter', "type parameter"), + [SymbolKind.Variable]: localize('Variable', "variable"), +}; + +export function getAriaLabelForSymbol(symbolName: string, kind: SymbolKind): string { + return localize('symbolAriaLabel', '{0} Symbol: {1}', symbolName, symbolKindNames[kind]); +} + export const enum SymbolTag { Deprecated = 1, } @@ -1190,13 +1223,6 @@ export namespace SymbolKinds { } return icon; } - - /** - * @internal - */ - export function asAriaLabel(label: string, kind: SymbolKind): string { - return localize('symbolAriaLabel', "{0} Symbol: {1}", label, SymbolKinds.toIcon(kind).id.replaceAll('symbol-', '')); - } } export interface DocumentSymbol { diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts index d6c3feb2a31..77197c2f9d4 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts @@ -14,7 +14,7 @@ import { format, trim } from 'vs/base/common/strings'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { DocumentSymbol, SymbolKind, SymbolKinds, SymbolTag } from 'vs/editor/common/languages'; +import { DocumentSymbol, SymbolKind, SymbolKinds, SymbolTag, getAriaLabelForSymbol } from 'vs/editor/common/languages'; import { IOutlineModelService } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; import { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAccessOptions, IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess'; import { localize } from 'vs/nls'; @@ -316,7 +316,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit kind: symbol.kind, score: symbolScore, label: symbolLabelWithIcon, - ariaLabel: symbolLabel, + ariaLabel: getAriaLabelForSymbol(symbol.name, symbol.kind), description: containerLabel, highlights: deprecated ? undefined : { label: symbolMatches, diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 939d3391034..031bc170bd7 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -778,6 +778,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { registerDocumentRangeSemanticTokensProvider: registerDocumentRangeSemanticTokensProvider, registerInlineCompletionsProvider: registerInlineCompletionsProvider, registerInlayHintsProvider: registerInlayHintsProvider, + getAriaLabelForSymbol: languages.getAriaLabelForSymbol, // enums DocumentHighlightKind: standaloneEnums.DocumentHighlightKind, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 770c1268f3a..6bfb3fb8f62 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7455,6 +7455,8 @@ declare namespace monaco.languages { TypeParameter = 25 } + export function getAriaLabelForSymbol(symbolName: string, kind: SymbolKind): string; + export enum SymbolTag { Deprecated = 1 } diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 69680c0af9d..3e7bdeec9c0 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -11,7 +11,7 @@ import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelega import { ITreeNode, ITreeRenderer, ITreeFilter } from 'vs/base/browser/ui/tree/tree'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { Range } from 'vs/editor/common/core/range'; -import { SymbolKind, SymbolKinds, SymbolTag } from 'vs/editor/common/languages'; +import { SymbolKind, SymbolKinds, SymbolTag, getAriaLabelForSymbol, symbolKindNames } from 'vs/editor/common/languages'; import { OutlineElement, OutlineGroup, OutlineModel } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; import { localize } from 'vs/nls'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; @@ -49,7 +49,7 @@ export class DocumentSymbolAccessibilityProvider implements IListAccessibilityPr if (element instanceof OutlineGroup) { return element.label; } else { - return SymbolKinds.asAriaLabel(element.symbol.name, element.symbol.kind); + return getAriaLabelForSymbol(element.symbol.name, element.symbol.kind); } } } @@ -138,7 +138,7 @@ export class DocumentSymbolRenderer implements ITreeRenderer Date: Fri, 14 Jul 2023 12:10:04 -0700 Subject: [PATCH 160/826] clean up --- src/vs/base/common/iconLabels.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/base/common/iconLabels.ts b/src/vs/base/common/iconLabels.ts index eb657f5d15b..089afb9455c 100644 --- a/src/vs/base/common/iconLabels.ts +++ b/src/vs/base/common/iconLabels.ts @@ -6,6 +6,7 @@ import { IMatch, matchesFuzzy } from 'vs/base/common/filters'; import { ltrim } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; + const iconStartMarker = '$('; const iconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameExpression}(?:${ThemeIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups @@ -47,8 +48,6 @@ export function getCodiconAriaLabel(text: string | undefined) { } - - export interface IParsedLabelWithIcons { readonly text: string; readonly iconOffsets?: readonly number[]; From 76f9fca873d4877968745dc78336fc5e6be5a019 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 14 Jul 2023 17:16:55 -0700 Subject: [PATCH 161/826] fix error --- src/vs/monaco.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 6bfb3fb8f62..e87de797b6a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7455,6 +7455,10 @@ declare namespace monaco.languages { TypeParameter = 25 } + export const symbolKindNames: { + [symbol: number]: string; + }; + export function getAriaLabelForSymbol(symbolName: string, kind: SymbolKind): string; export enum SymbolTag { From a06ece3136b11030e2033cd2651c102eee30dcb4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 14 Jul 2023 17:20:04 -0700 Subject: [PATCH 162/826] fix issue --- src/vs/editor/standalone/browser/standaloneLanguages.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 031bc170bd7..03da1d4e3be 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -779,6 +779,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { registerInlineCompletionsProvider: registerInlineCompletionsProvider, registerInlayHintsProvider: registerInlayHintsProvider, getAriaLabelForSymbol: languages.getAriaLabelForSymbol, + symbolKindNames: languages.symbolKindNames, // enums DocumentHighlightKind: standaloneEnums.DocumentHighlightKind, From 247fab678c9858ff977f4e5ed51eb509bd9ed33e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 14 Jul 2023 18:43:33 -0700 Subject: [PATCH 163/826] mark with internal --- src/vs/editor/common/languages.ts | 6 ++++++ src/vs/editor/standalone/browser/standaloneLanguages.ts | 2 -- src/vs/monaco.d.ts | 6 ------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 17b1c5e3448..3a08292c8d6 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1143,6 +1143,9 @@ export const enum SymbolKind { TypeParameter = 25 } +/** + * @internal + */ export const symbolKindNames: { [symbol: number]: string } = { [SymbolKind.Array]: localize('Array', "array"), [SymbolKind.Boolean]: localize('Boolean', "boolean"), @@ -1172,6 +1175,9 @@ export const symbolKindNames: { [symbol: number]: string } = { [SymbolKind.Variable]: localize('Variable', "variable"), }; +/** + * @internal + */ export function getAriaLabelForSymbol(symbolName: string, kind: SymbolKind): string { return localize('symbolAriaLabel', '{0} Symbol: {1}', symbolName, symbolKindNames[kind]); } diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 03da1d4e3be..939d3391034 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -778,8 +778,6 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { registerDocumentRangeSemanticTokensProvider: registerDocumentRangeSemanticTokensProvider, registerInlineCompletionsProvider: registerInlineCompletionsProvider, registerInlayHintsProvider: registerInlayHintsProvider, - getAriaLabelForSymbol: languages.getAriaLabelForSymbol, - symbolKindNames: languages.symbolKindNames, // enums DocumentHighlightKind: standaloneEnums.DocumentHighlightKind, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index e87de797b6a..770c1268f3a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7455,12 +7455,6 @@ declare namespace monaco.languages { TypeParameter = 25 } - export const symbolKindNames: { - [symbol: number]: string; - }; - - export function getAriaLabelForSymbol(symbolName: string, kind: SymbolKind): string; - export enum SymbolTag { Deprecated = 1 } From b288ef1be837f120afc939c6ed78c471965d63e6 Mon Sep 17 00:00:00 2001 From: yshaojun Date: Sat, 15 Jul 2023 12:09:45 +0800 Subject: [PATCH 164/826] feat: update test case --- .../editor/test/browser/viewModel/modelLineProjection.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts b/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts index 06de7665c02..530ae84b562 100644 --- a/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts +++ b/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts @@ -921,7 +921,7 @@ suite('SplitLinesCollection', () => { })), [ { inlineDecorations: [{ startOffset: 8, endOffset: 23 }] }, - { inlineDecorations: [{ startOffset: 4, endOffset: 42 }] }, + { inlineDecorations: [{ startOffset: 4, endOffset: 30 }] }, { inlineDecorations: [{ startOffset: 4, endOffset: 16 }] }, { inlineDecorations: undefined }, { inlineDecorations: undefined }, From dd6af83b854b86d233de36a03b7af1d4ec8744e2 Mon Sep 17 00:00:00 2001 From: yshaojun Date: Sat, 15 Jul 2023 22:58:51 +0800 Subject: [PATCH 165/826] fix: prevent duplicate reason(#186206) --- .../unicodeHighlighter/browser/unicodeHighlighter.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts index 9b77b25b72e..122648ba83c 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts @@ -429,6 +429,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa } const result: MarkdownHover[] = []; + const existedReason = new Map(); let index = 300; for (const d of lineDecorations) { @@ -480,6 +481,11 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa break; } + if (existedReason.has(reason)) { + continue; + } + existedReason.set(reason, true); + const adjustSettingsArgs: ShowExcludeOptionsArgs = { codePoint: codePoint, reason: highlightInfo.reason, From 2a19176aa6719e12975e4730990ef81fb5e757e8 Mon Sep 17 00:00:00 2001 From: yshaojun Date: Sun, 16 Jul 2023 14:55:48 +0800 Subject: [PATCH 166/826] fix: modified editor width(#175397) --- src/vs/editor/browser/widget/diffEditorWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index e21c1e9f3be..123bbc901c0 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -1368,7 +1368,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._originalDomNode.style.width = splitPoint + 'px'; this._originalDomNode.style.left = '0px'; - this._modifiedDomNode.style.width = (width - splitPoint) + 'px'; + this._modifiedDomNode.style.width = (width - splitPoint - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH) + 'px'; this._modifiedDomNode.style.left = splitPoint + 'px'; this._overviewDomElement.style.top = '0px'; From 02d3b49d8035a9b30db3b0ae706796af9b1a4260 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sun, 16 Jul 2023 20:38:37 -0700 Subject: [PATCH 167/826] Notebook extension recommendatation on open. (#188030) --- .../notebook/browser/notebookEditor.ts | 23 +++++++++++++- .../services/notebookWorkerServiceImpl.ts | 12 ++++++++ .../common/services/notebookSimpleWorker.ts | 30 +++++++++++++++++++ .../common/services/notebookWorkerService.ts | 1 + 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index af68d86e8a7..159a26de442 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -46,6 +46,7 @@ import { EnablementState } from 'vs/workbench/services/extensionManagement/commo import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { streamToBuffer } from 'vs/base/common/buffer'; import { ILogService } from 'vs/platform/log/common/log'; +import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -89,7 +90,8 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { @INotebookService private readonly _notebookService: INotebookService, @IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService, @IWorkingCopyBackupService private readonly _workingCopyBackupService: IWorkingCopyBackupService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @INotebookEditorWorkerService private readonly _notebookEditorWorkerService: INotebookEditorWorkerService, ) { super(NotebookEditor.ID, telemetryService, themeService, storageService); this._editorMemento = this.getEditorMemento(_editorGroupService, configurationService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY); @@ -318,6 +320,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { } this._handlePerfMark(perf, input); + this._handlePromptRecommendations(model.notebook); } catch (e) { this.logService.warn('NotebookEditorWidget#setInput failed', e); if (isEditorOpenError(e)) { @@ -425,6 +428,24 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { }); } + private _handlePromptRecommendations(model: NotebookTextModel) { + this._notebookEditorWorkerService.canPromptRecommendation(model.uri).then(shouldPrompt => { + type WorkbenchNotebookShouldPromptRecommendationClassification = { + owner: 'rebornix'; + comment: 'The notebook file metrics. Used to get a better understanding of if we should prompt for notebook extension recommendations'; + shouldPrompt: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Should we prompt for notebook extension recommendations' }; + }; + + type WorkbenchNotebookShouldPromptRecommendationEvent = { + shouldPrompt: boolean; + }; + + this.telemetryService.publicLog2('notebook/shouldPromptRecommendation', { + shouldPrompt: shouldPrompt + }); + }); + } + override clearInput(): void { this._inputListener.clear(); diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts index 6a1d82f3b26..65980c57a9c 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts @@ -35,6 +35,12 @@ export class NotebookEditorWorkerServiceImpl extends Disposable implements INote return client.computeDiff(original, modified); }); } + + canPromptRecommendation(model: URI): Promise { + return this._workerManager.withWorker().then(client => { + return client.canPromptRecommendation(model); + }); + } } class WorkerManager extends Disposable { @@ -218,6 +224,12 @@ class NotebookWorkerClient extends Disposable { }); } + canPromptRecommendation(modelUri: URI) { + return this._withSyncedResources([modelUri]).then(proxy => { + return proxy.canPromptRecommendation(modelUri.toString()); + }); + } + private _getOrCreateModelManager(proxy: NotebookEditorSimpleWorker): NotebookEditorModelManager { if (!this._modelManager) { this._modelManager = this._register(new NotebookEditorModelManager(proxy, this._notebookService)); diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts index 685d07a5073..1b8d4055433 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts @@ -13,6 +13,7 @@ import { CellKind, ICellDto2, IMainCellDto, INotebookDiffResult, IOutputDto, Not import { Range } from 'vs/editor/common/core/range'; import { INotebookWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerHost'; import { VSBuffer } from 'vs/base/common/buffer'; +import { SearchParams } from 'vs/editor/common/model/textModelSearch'; function bufferHash(buffer: VSBuffer): number { let initialHashVal = numberHash(104579, 0); @@ -275,6 +276,35 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable }; } + canPromptRecommendation(modelUrl: string): boolean { + const model = this._getModel(modelUrl); + const cells = model.cells; + + for (let i = 0; i < cells.length; i++) { + const cell = cells[i]; + if (cell.cellKind === CellKind.Markup) { + continue; + } + + const lineCount = cell.textBuffer.getLineCount(); + const maxLineCount = Math.min(lineCount, 20); + const range = new Range(1, 1, maxLineCount, cell.textBuffer.getLineLength(maxLineCount) + 1); + const searchParams = new SearchParams('import\\s*pandas', true, false, null); + const searchData = searchParams.parseSearchRequest(); + + if (!searchData) { + continue; + } + + const cellMatches = cell.textBuffer.findMatchesLineByLine(range, searchData, true, 1); + if (cellMatches.length > 0) { + return true; + } + } + + return false; + } + protected _getModel(uri: string): MirrorNotebookDocument { return this._models[uri]; } diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerService.ts b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerService.ts index ec7081a2852..8155c9d58db 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerService.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerService.ts @@ -15,4 +15,5 @@ export interface INotebookEditorWorkerService { canComputeDiff(original: URI, modified: URI): boolean; computeDiff(original: URI, modified: URI): Promise; + canPromptRecommendation(model: URI): Promise; } From 4dd562c7b0a6d1429bc146ec41255a0f9ca1bc40 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 17 Jul 2023 10:58:06 +0200 Subject: [PATCH 168/826] Fix gaps in updating the profile (#188050) #156144 Fix gaps in updating the profile --- .../userDataProfile/common/userDataProfile.ts | 2 +- .../common/userDataProfileService.test.ts | 9 + .../common/userDataProfilesManifestMerge.ts | 7 +- .../common/userDataProfilesManifestSync.ts | 2 +- .../userDataProfilesManifestMerge.test.ts | 13 +- .../userDataProfilesManifestSync.test.ts | 41 +++++ .../parts/activitybar/activitybarActions.ts | 3 +- .../browser/parts/titlebar/windowTitle.ts | 1 - .../browser/extensions.contribution.ts | 43 ++--- .../browser/preferences.contribution.ts | 169 ++++++++--------- .../browser/commands/configureSnippets.ts | 170 ++++++++---------- .../snippets/browser/snippets.contribution.ts | 4 +- .../tasks/browser/task.contribution.ts | 57 +++--- .../browser/userDataProfile.ts | 4 +- .../browser/configurationService.ts | 3 +- .../extensionManagementChannelClient.ts | 6 +- .../common/webExtensionManagementService.ts | 6 +- .../keybinding/browser/keybindingService.ts | 15 +- .../browser/userDataProfileManagement.ts | 18 +- .../userDataProfile/common/userDataProfile.ts | 3 +- .../common/userDataProfileService.ts | 13 +- .../test/browser/workbenchTestServices.ts | 2 +- 22 files changed, 299 insertions(+), 292 deletions(-) diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index 83065021bdb..e42d012c995 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -365,7 +365,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf throw new Error(`Profile '${profileToUpdate.name}' does not exist`); } - profile = toUserDataProfile(profile.id, options.name ?? profile.name, profile.location, this.profilesCacheHome, { shortName: options.shortName ?? profile.shortName, transient: options.transient ?? profile.isTransient, useDefaultFlags: options.useDefaultFlags ?? profile.useDefaultFlags }); + profile = toUserDataProfile(profile.id, options.name ?? profile.name, profile.location, this.profilesCacheHome, { shortName: options.shortName ?? profile.shortName, transient: options.transient ?? profile.isTransient, useDefaultFlags: options.useDefaultFlags ?? profile.useDefaultFlags }, this.defaultProfile); this.updateProfiles([], [], [profile]); return profile; diff --git a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts index dbcd309c77e..38f24155224 100644 --- a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts +++ b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts @@ -226,4 +226,13 @@ suite('UserDataProfileService (Common)', () => { assert.strictEqual(profile.extensionsResource.toString(), testObject.defaultProfile.extensionsResource.toString()); }); + test('update profile using default profile for keybindings', async () => { + let profile = await testObject.createNamedProfile('name'); + profile = await testObject.updateProfile(profile, { useDefaultFlags: { keybindings: true } }); + + assert.strictEqual(profile.isDefault, false); + assert.deepStrictEqual(profile.useDefaultFlags, { keybindings: true }); + assert.strictEqual(profile.keybindingsResource.toString(), testObject.defaultProfile.keybindingsResource.toString()); + }); + }); diff --git a/src/vs/platform/userDataSync/common/userDataProfilesManifestMerge.ts b/src/vs/platform/userDataSync/common/userDataProfilesManifestMerge.ts index 43df2e6df15..072554b04f8 100644 --- a/src/vs/platform/userDataSync/common/userDataProfilesManifestMerge.ts +++ b/src/vs/platform/userDataSync/common/userDataProfilesManifestMerge.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { equals } from 'vs/base/common/objects'; +import { IUserDataProfile, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ISyncUserDataProfile } from 'vs/platform/userDataSync/common/userDataSync'; interface IRelaxedMergeResult { @@ -17,6 +18,7 @@ interface IUserDataProfileInfo { readonly id: string; readonly name: string; readonly shortName?: string; + readonly useDefaultFlags?: UseDefaultProfileFlags; } export function merge(local: IUserDataProfile[], remote: ISyncUserDataProfile[] | null, lastSync: ISyncUserDataProfile[] | null, ignored: string[]): IMergeResult { @@ -117,7 +119,7 @@ function compare(from: IUserDataProfileInfo[] | null, to: IUserDataProfileInfo[] const removed = fromKeys.filter(key => !toKeys.includes(key)); const updated: string[] = []; - for (const { id, name, shortName } of from) { + for (const { id, name, shortName, useDefaultFlags } of from) { if (removed.includes(id)) { continue; } @@ -125,6 +127,7 @@ function compare(from: IUserDataProfileInfo[] | null, to: IUserDataProfileInfo[] if (!toProfile || toProfile.name !== name || toProfile.shortName !== shortName + || !equals(toProfile.useDefaultFlags, useDefaultFlags) ) { updated.push(id); } diff --git a/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts b/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts index 9f179f239af..606983a1c0a 100644 --- a/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts +++ b/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts @@ -206,7 +206,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i if (localProfile) { promises.push((async () => { this.logService.trace(`${this.syncResourceLogLabel}: Updating '${profile.name}' profile...`); - await this.userDataProfilesService.updateProfile(localProfile, { name: profile.name, shortName: profile.shortName }); + await this.userDataProfilesService.updateProfile(localProfile, { name: profile.name, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags }); this.logService.info(`${this.syncResourceLogLabel}: Updated profile '${profile.name}'.`); })()); } else { diff --git a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts index 5eb007eb9b2..b0036c3869b 100644 --- a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts @@ -71,6 +71,8 @@ suite('UserDataProfilesManifestMerge', () => { toUserDataProfile('5', '5', URI.file('5'), URI.file('cache')), toUserDataProfile('6', '6', URI.file('6'), URI.file('cache')), toUserDataProfile('8', '8', URI.file('8'), URI.file('cache')), + toUserDataProfile('10', '10', URI.file('8'), URI.file('cache'), { useDefaultFlags: { tasks: true } }), + toUserDataProfile('11', '11', URI.file('1'), URI.file('cache'), { useDefaultFlags: { keybindings: true } }), ]; const base: ISyncUserDataProfile[] = [ { id: '1', name: '1', collection: '1' }, @@ -79,6 +81,8 @@ suite('UserDataProfilesManifestMerge', () => { { id: '4', name: '4', collection: '4' }, { id: '5', name: '5', collection: '5' }, { id: '6', name: '6', collection: '6' }, + { id: '10', name: '10', collection: '10', useDefaultFlags: { tasks: true } }, + { id: '11', name: '11', collection: '11' }, ]; const remoteProfiles: ISyncUserDataProfile[] = [ { id: '1', name: '1', collection: '1' }, @@ -87,15 +91,18 @@ suite('UserDataProfilesManifestMerge', () => { { id: '4', name: 'changed remote', collection: '4' }, { id: '5', name: '5', collection: '5' }, { id: '7', name: '7', collection: '7' }, + { id: '9', name: '9', collection: '9', useDefaultFlags: { snippets: true } }, + { id: '10', name: '10', collection: '10' }, + { id: '11', name: '11', collection: '11' }, ]; const actual = merge(localProfiles, remoteProfiles, base, []); - assert.deepStrictEqual(actual.local.added, [remoteProfiles[5]]); + assert.deepStrictEqual(actual.local.added, [remoteProfiles[5], remoteProfiles[6]]); assert.deepStrictEqual(actual.local.removed, [localProfiles[4]]); - assert.deepStrictEqual(actual.local.updated, [remoteProfiles[2], remoteProfiles[3]]); + assert.deepStrictEqual(actual.local.updated, [remoteProfiles[2], remoteProfiles[3], remoteProfiles[7]]); assert.deepStrictEqual(actual.remote?.added, [localProfiles[5]]); - assert.deepStrictEqual(actual.remote?.updated, [localProfiles[0]]); + assert.deepStrictEqual(actual.remote?.updated, [localProfiles[0], localProfiles[7]]); assert.deepStrictEqual(actual.remote?.removed, [remoteProfiles[1]]); }); diff --git a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts index 068718a3f85..d1c7b6f572d 100644 --- a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts @@ -217,6 +217,47 @@ suite('UserDataProfilesManifestSync', () => { assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: { keybindings: true } }]); }); + test('sync profile when the profile is updated to use default profile locally', async () => { + await client2.instantiationService.get(IUserDataProfilesService).createProfile('1', 'name 1'); + await client2.sync(); + + await testObject.sync(await testClient.getResourceManifest()); + + const profile = testClient.instantiationService.get(IUserDataProfilesService).profiles.find(p => p.id === '1')!; + testClient.instantiationService.get(IUserDataProfilesService).updateProfile(profile, { useDefaultFlags: { keybindings: true } }); + + await testObject.sync(await testClient.getResourceManifest()); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts.conflicts, []); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseRemoteProfiles(content!); + assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1', useDefaultFlags: { keybindings: true } }]); + assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: { keybindings: true } }]); + }); + + test('sync profile when the profile is updated to use default profile remotely', async () => { + const profile = await client2.instantiationService.get(IUserDataProfilesService).createProfile('1', 'name 1'); + await client2.sync(); + + await testObject.sync(await testClient.getResourceManifest()); + + client2.instantiationService.get(IUserDataProfilesService).updateProfile(profile, { useDefaultFlags: { keybindings: true } }); + await client2.sync(); + + await testObject.sync(await testClient.getResourceManifest()); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts.conflicts, []); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseRemoteProfiles(content!); + assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1', useDefaultFlags: { keybindings: true } }]); + + assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: { keybindings: true } }]); + }); + function parseRemoteProfiles(content: string): ISyncUserDataProfile[] { const syncData: ISyncData = JSON.parse(content); return JSON.parse(syncData.content); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 6fbd655eb67..d09cfa07930 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -9,7 +9,6 @@ import { EventType, addDisposableListener, EventHelper, append, $, clearNode, hi import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { Action, IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions'; -import { Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IMenuService, MenuId, IMenu, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions'; @@ -485,7 +484,7 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem { @IKeybindingService keybindingService: IKeybindingService, ) { super(MenuId.GlobalActivity, action, contextMenuActionsProvider, true, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService); - this._register(Event.any(this.userDataProfileService.onDidUpdateCurrentProfile, this.userDataProfileService.onDidChangeCurrentProfile)(() => this.updateProfileBadge())); + this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => this.updateProfileBadge())); } override render(container: HTMLElement): void { diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index 805ee48cd29..bcfc971b2a4 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -76,7 +76,6 @@ export class WindowTitle extends Disposable { this._register(this.contextService.onDidChangeWorkspaceName(() => this.titleUpdater.schedule())); this._register(this.labelService.onDidChangeFormatters(() => this.titleUpdater.schedule())); this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => this.titleUpdater.schedule())); - this._register(this.userDataProfileService.onDidUpdateCurrentProfile(() => this.titleUpdater.schedule())); } private onConfigurationChanged(event: IConfigurationChangeEvent): void { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index bd7165c5d12..52c03ecba67 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -78,7 +78,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStringDictionary } from 'vs/base/common/collections'; import { CONTEXT_KEYBINDINGS_EDITOR } from 'vs/workbench/contrib/preferences/common/preferences'; import { DeprecatedExtensionsChecker } from 'vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */); @@ -473,7 +472,6 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, ) { super(); const hasGalleryContext = CONTEXT_HAS_GALLERY.bindTo(contextKeyService); @@ -517,31 +515,22 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi // Global actions private registerGlobalActions(): void { - const getTitle = (title: string) => !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.extensions - ? `${title} (${localize('default profile', "Default Profile")})` - : title; - const registerOpenExtensionsActionDisposables = this._register(new DisposableStore()); - const registerOpenExtensionsAction = () => { - registerOpenExtensionsActionDisposables.clear(); - registerOpenExtensionsActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - command: { - id: VIEWLET_ID, - title: getTitle(localize({ key: 'miPreferencesExtensions', comment: ['&& denotes a mnemonic'] }, "&&Extensions")) - }, - group: '2_configuration', - order: 3 - })); - registerOpenExtensionsActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - command: { - id: VIEWLET_ID, - title: getTitle(localize('showExtensions', "Extensions")) - }, - group: '2_configuration', - order: 3 - })); - }; - registerOpenExtensionsAction(); - this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => registerOpenExtensionsAction())); + this._register(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + command: { + id: VIEWLET_ID, + title: localize({ key: 'miPreferencesExtensions', comment: ['&& denotes a mnemonic'] }, "&&Extensions") + }, + group: '2_configuration', + order: 3 + })); + this._register(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + command: { + id: VIEWLET_ID, + title: localize('showExtensions', "Extensions") + }, + group: '2_configuration', + order: 3 + })); this.registerExtensionAction({ id: 'workbench.extensions.action.installExtensions', diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 90a505ca3d7..71973bd7653 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -12,7 +12,6 @@ import 'vs/css!./media/preferences'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest'; import * as nls from 'vs/nls'; -import { Event } from 'vs/base/common/event'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -188,46 +187,37 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon } private registerSettingsActions() { - const registerOpenSettingsActionDisposables = this._register(new DisposableStore()); - const registerOpenSettingsAction = () => { - registerOpenSettingsActionDisposables.clear(); - const getTitle = (title: string) => !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.settings - ? `${title} (${nls.localize('default profile', "Default Profile")})` - : title; - registerOpenSettingsActionDisposables.add(registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_COMMAND_OPEN_SETTINGS, - title: { - value: getTitle(nls.localize('settings', "Settings")), - mnemonicTitle: getTitle(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings")), - original: 'Settings' - }, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - when: null, - primary: KeyMod.CtrlCmd | KeyCode.Comma, - }, - menu: [{ - id: MenuId.GlobalActivity, - group: '2_configuration', - order: 1 - }, { - id: MenuId.MenubarPreferencesMenu, - group: '2_configuration', - order: 1 - }], - }); - } - run(accessor: ServicesAccessor, args: string | IOpenSettingsActionOptions) { - // args takes a string for backcompat - const opts = typeof args === 'string' ? { query: args } : sanitizeOpenSettingsArgs(args); - return accessor.get(IPreferencesService).openSettings(opts); - } - })); - }; - registerOpenSettingsAction(); - this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => registerOpenSettingsAction())); + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_COMMAND_OPEN_SETTINGS, + title: { + value: nls.localize('settings', "Settings"), + mnemonicTitle: nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings"), + original: 'Settings' + }, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: null, + primary: KeyMod.CtrlCmd | KeyCode.Comma, + }, + menu: [{ + id: MenuId.GlobalActivity, + group: '2_configuration', + order: 1 + }, { + id: MenuId.MenubarPreferencesMenu, + group: '2_configuration', + order: 1 + }], + }); + } + run(accessor: ServicesAccessor, args: string | IOpenSettingsActionOptions) { + // args takes a string for backcompat + const opts = typeof args === 'string' ? { query: args } : sanitizeOpenSettingsArgs(args); + return accessor.get(IPreferencesService).openSettings(opts); + } + })); registerAction2(class extends Action2 { constructor() { super({ @@ -306,13 +296,13 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon } }); - const registerOpenUserSettingsEditorFromJsonActionDisposable = this._register(new MutableDisposable()); + const registerOpenUserSettingsEditorFromJsonActionDisposables = this._register(new MutableDisposable()); const openUserSettingsEditorWhen = ContextKeyExpr.and( ContextKeyExpr.or(ResourceContextKey.Resource.isEqualTo(this.userDataProfileService.currentProfile.settingsResource.toString()), ResourceContextKey.Resource.isEqualTo(this.userDataProfilesService.defaultProfile.settingsResource.toString())), ContextKeyExpr.not('isInDiffEditor')); const registerOpenUserSettingsEditorFromJsonAction = () => { - registerOpenUserSettingsEditorFromJsonActionDisposable.value = registerAction2(class extends Action2 { + registerOpenUserSettingsEditorFromJsonActionDisposables.value = registerAction2(class extends Action2 { constructor() { super({ id: '_workbench.openUserSettingsEditor', @@ -816,58 +806,49 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon private registerKeybindingsActions() { const that = this; const category = { value: nls.localize('preferences', "Preferences"), original: 'Preferences' }; - const registerOpenGlobalKeybindingsActionDisposables = this._register(new DisposableStore()); - const registerOpenGlobalKeybindingsAction = () => { - registerOpenGlobalKeybindingsActionDisposables.clear(); - const id = 'workbench.action.openGlobalKeybindings'; - const shortTitle = !that.userDataProfileService.currentProfile.isDefault && that.userDataProfileService.currentProfile.useDefaultFlags?.keybindings - ? nls.localize('keyboardShortcutsFromDefault', "Keyboard Shortcuts ({0})", nls.localize('default profile', "Default Profile")) - : nls.localize('keyboardShortcuts', "Keyboard Shortcuts"); - registerOpenGlobalKeybindingsActionDisposables.add(registerAction2(class extends Action2 { - constructor() { - super({ - id, - title: { value: nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts"), original: 'Open Keyboard Shortcuts' }, - shortTitle, - category, - icon: preferencesOpenSettingsIcon, - keybinding: { - when: null, - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyS) - }, - menu: [ - { id: MenuId.CommandPalette }, - { - id: MenuId.EditorTitle, - when: ResourceContextKey.Resource.isEqualTo(that.userDataProfileService.currentProfile.keybindingsResource.toString()), - group: 'navigation', - order: 1, - }, - { - id: MenuId.GlobalActivity, - group: '2_configuration', - order: 3 - } - ] - }); - } - run(accessor: ServicesAccessor, args: string | undefined) { - const query = typeof args === 'string' ? args : undefined; - return accessor.get(IPreferencesService).openGlobalKeybindingSettings(false, { query }); - } - })); - registerOpenGlobalKeybindingsActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - command: { + const id = 'workbench.action.openGlobalKeybindings'; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ id, - title: shortTitle, - }, - group: '2_configuration', - order: 3 - })); - }; - registerOpenGlobalKeybindingsAction(); - this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => registerOpenGlobalKeybindingsAction())); + title: { value: nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts"), original: 'Open Keyboard Shortcuts' }, + shortTitle: nls.localize('keyboardShortcuts', "Keyboard Shortcuts"), + category, + icon: preferencesOpenSettingsIcon, + keybinding: { + when: null, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyS) + }, + menu: [ + { id: MenuId.CommandPalette }, + { + id: MenuId.EditorTitle, + when: ResourceContextKey.Resource.isEqualTo(that.userDataProfileService.currentProfile.keybindingsResource.toString()), + group: 'navigation', + order: 1, + }, + { + id: MenuId.GlobalActivity, + group: '2_configuration', + order: 3 + } + ] + }); + } + run(accessor: ServicesAccessor, args: string | undefined) { + const query = typeof args === 'string' ? args : undefined; + return accessor.get(IPreferencesService).openGlobalKeybindingSettings(false, { query }); + } + })); + this._register(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + command: { + id, + title: nls.localize('keyboardShortcuts', "Keyboard Shortcuts"), + }, + group: '2_configuration', + order: 3 + })); registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts index 7f8948aae61..9d2a199fe18 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -4,21 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { isValidBasename } from 'vs/base/common/extpath'; -import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; import { extname } from 'vs/base/common/path'; import { basename, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ILanguageService } from 'vs/editor/common/languages/language'; import * as nls from 'vs/nls'; -import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { MenuId } from 'vs/platform/actions/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SnippetsAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; @@ -224,97 +221,80 @@ async function createLanguageSnippetFile(pick: ISnippetPick, fileService: IFileS await textFileService.write(pick.filepath, contents); } -export class ConfigureSnippetsActions extends Disposable implements IWorkbenchContribution { - - constructor( - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - ) { - super(); - const disposable = this._register(new MutableDisposable()); - disposable.value = this.registerAction(); - this._register(Event.any(userDataProfileService.onDidChangeCurrentProfile, userDataProfileService.onDidUpdateCurrentProfile)(() => disposable.value = this.registerAction())); - } - - private registerAction(): IDisposable { - const getTitle = (title: string) => !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.snippets - ? `${title} (${nls.localize('default', "Default Profile")})` - : title; - return registerAction2(class extends SnippetsAction { - constructor() { - super({ - id: 'workbench.action.openSnippets', - title: { - value: nls.localize('openSnippet.label', "Configure User Snippets"), - original: 'Configure User Snippets' - }, - shortTitle: { - value: getTitle(nls.localize('userSnippets', "User Snippets")), - mnemonicTitle: getTitle(nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets")), - original: 'User Snippets' - }, - f1: true, - menu: [ - { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', order: 4 }, - { id: MenuId.GlobalActivity, group: '2_configuration', order: 4 }, - ] - }); - } - - async run(accessor: ServicesAccessor): Promise { - - const snippetService = accessor.get(ISnippetsService); - const quickInputService = accessor.get(IQuickInputService); - const opener = accessor.get(IOpenerService); - const languageService = accessor.get(ILanguageService); - const userDataProfileService = accessor.get(IUserDataProfileService); - const workspaceService = accessor.get(IWorkspaceContextService); - const fileService = accessor.get(IFileService); - const textFileService = accessor.get(ITextFileService); - const labelService = accessor.get(ILabelService); - - const picks = await computePicks(snippetService, userDataProfileService, languageService, labelService); - const existing: QuickPickInput[] = picks.existing; - - type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string }; - const globalSnippetPicks: SnippetPick[] = [{ - scope: nls.localize('new.global_scope', 'global'), - label: nls.localize('new.global', "New Global Snippets file..."), - uri: userDataProfileService.currentProfile.snippetsHome - }]; - - const workspaceSnippetPicks: SnippetPick[] = []; - for (const folder of workspaceService.getWorkspace().folders) { - workspaceSnippetPicks.push({ - scope: nls.localize('new.workspace_scope', "{0} workspace", folder.name), - label: nls.localize('new.folder', "New Snippets file for '{0}'...", folder.name), - uri: folder.toResource('.vscode') - }); - } - - if (existing.length > 0) { - existing.unshift({ type: 'separator', label: nls.localize('group.global', "Existing Snippets") }); - existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); - } else { - existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); - } - - const pick = await quickInputService.pick(([] as QuickPickInput[]).concat(existing, globalSnippetPicks, workspaceSnippetPicks, picks.future), { - placeHolder: nls.localize('openSnippet.pickLanguage', "Select Snippets File or Create Snippets"), - matchOnDescription: true - }); - - if (globalSnippetPicks.indexOf(pick as SnippetPick) >= 0) { - return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); - } else if (workspaceSnippetPicks.indexOf(pick as SnippetPick) >= 0) { - return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); - } else if (ISnippetPick.is(pick)) { - if (pick.hint) { - await createLanguageSnippetFile(pick, fileService, textFileService); - } - return opener.open(pick.filepath); - } - - } +export class ConfigureSnippetsAction extends SnippetsAction { + constructor() { + super({ + id: 'workbench.action.openSnippets', + title: { + value: nls.localize('openSnippet.label', "Configure User Snippets"), + original: 'Configure User Snippets' + }, + shortTitle: { + value: nls.localize('userSnippets', "User Snippets"), + mnemonicTitle: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), + original: 'User Snippets' + }, + f1: true, + menu: [ + { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', order: 4 }, + { id: MenuId.GlobalActivity, group: '2_configuration', order: 4 }, + ] }); } + + async run(accessor: ServicesAccessor): Promise { + + const snippetService = accessor.get(ISnippetsService); + const quickInputService = accessor.get(IQuickInputService); + const opener = accessor.get(IOpenerService); + const languageService = accessor.get(ILanguageService); + const userDataProfileService = accessor.get(IUserDataProfileService); + const workspaceService = accessor.get(IWorkspaceContextService); + const fileService = accessor.get(IFileService); + const textFileService = accessor.get(ITextFileService); + const labelService = accessor.get(ILabelService); + + const picks = await computePicks(snippetService, userDataProfileService, languageService, labelService); + const existing: QuickPickInput[] = picks.existing; + + type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string }; + const globalSnippetPicks: SnippetPick[] = [{ + scope: nls.localize('new.global_scope', 'global'), + label: nls.localize('new.global', "New Global Snippets file..."), + uri: userDataProfileService.currentProfile.snippetsHome + }]; + + const workspaceSnippetPicks: SnippetPick[] = []; + for (const folder of workspaceService.getWorkspace().folders) { + workspaceSnippetPicks.push({ + scope: nls.localize('new.workspace_scope', "{0} workspace", folder.name), + label: nls.localize('new.folder', "New Snippets file for '{0}'...", folder.name), + uri: folder.toResource('.vscode') + }); + } + + if (existing.length > 0) { + existing.unshift({ type: 'separator', label: nls.localize('group.global', "Existing Snippets") }); + existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); + } else { + existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); + } + + const pick = await quickInputService.pick(([] as QuickPickInput[]).concat(existing, globalSnippetPicks, workspaceSnippetPicks, picks.future), { + placeHolder: nls.localize('openSnippet.pickLanguage', "Select Snippets File or Create Snippets"), + matchOnDescription: true + }); + + if (globalSnippetPicks.indexOf(pick as SnippetPick) >= 0) { + return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); + } else if (workspaceSnippetPicks.indexOf(pick as SnippetPick) >= 0) { + return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); + } else if (ISnippetPick.is(pick)) { + if (pick.hint) { + await createLanguageSnippetFile(pick, fileService, textFileService); + } + return opener.open(pick.filepath); + } + + } } diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index b78f735e1fc..0ad6445077f 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -11,7 +11,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { ConfigureSnippetsActions } from 'vs/workbench/contrib/snippets/browser/commands/configureSnippets'; +import { ConfigureSnippetsAction } from 'vs/workbench/contrib/snippets/browser/commands/configureSnippets'; import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets'; import { InsertSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/insertSnippet'; import { SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet'; @@ -32,10 +32,10 @@ registerAction2(InsertSnippetAction); CommandsRegistry.registerCommandAlias('editor.action.showSnippets', 'editor.action.insertSnippet'); registerAction2(SurroundWithSnippetEditorAction); registerAction2(ApplyFileSnippetAction); +registerAction2(ConfigureSnippetsAction); // workbench contribs const workbenchContribRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContribRegistry.registerWorkbenchContribution(ConfigureSnippetsActions, LifecyclePhase.Restored); workbenchContribRegistry.registerWorkbenchContribution(SnippetCodeActions, LifecyclePhase.Restored); // config diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index fa2b3ec19f2..d20bb9d4370 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -5,8 +5,7 @@ import * as nls from 'vs/nls'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { MenuRegistry, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -40,7 +39,6 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { TerminalMenuBarGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; import { isString } from 'vs/base/common/types'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); @@ -356,43 +354,32 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { class UserTasksGlobalActionContribution extends Disposable implements IWorkbenchContribution { - constructor( - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - ) { + constructor() { super(); this.registerActions(); } private registerActions() { - const registerOpenUserTasksActionDisposables = this._register(new DisposableStore()); - const registerOpenSettingsAction = () => { - registerOpenUserTasksActionDisposables.clear(); - const id = 'workbench.action.tasks.openUserTasks'; - const userTasksTitle = nls.localize('userTasks', "User Tasks"); - const title = !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.tasks - ? `${userTasksTitle} (${nls.localize('default profile', "Default Profile")})` - : userTasksTitle; - registerOpenUserTasksActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - command: { - id, - title - }, - when: TaskExecutionSupportedContext, - group: '2_configuration', - order: 4 - })); - registerOpenUserTasksActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - command: { - id, - title - }, - when: TaskExecutionSupportedContext, - group: '2_configuration', - order: 4 - })); - }; - registerOpenSettingsAction(); - this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => registerOpenSettingsAction())); + const id = 'workbench.action.tasks.openUserTasks'; + const title = nls.localize('userTasks', "User Tasks"); + this._register(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + command: { + id, + title + }, + when: TaskExecutionSupportedContext, + group: '2_configuration', + order: 4 + })); + this._register(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + command: { + id, + title + }, + when: TaskExecutionSupportedContext, + group: '2_configuration', + order: 4 + })); } } workbenchRegistry.registerWorkbenchContribution(UserTasksGlobalActionContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 3c8e7b62c30..709d9d213f1 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -100,7 +100,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this._register(this.userDataProfilesService.onDidChangeProfiles(() => this.registerProfilesActions())); this.registerCurrentProfilesActions(); - this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => this.registerCurrentProfilesActions())); + this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => this.registerCurrentProfilesActions())); this.registerCreateFromCurrentProfileAction(); this.registerCreateProfileAction(); @@ -577,7 +577,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements extensions: result.items.includes(extensions) } : undefined; if (profile) { - await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, useDefaultFlags }); + await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, useDefaultFlags: profile.useDefaultFlags && !useDefaultFlags ? {} : useDefaultFlags }); } else { if (isString(source)) { await this.userDataProfileImportExportService.importProfile(URI.parse(source), { mode: 'apply', name: result.name, useDefaultFlags }); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 9a920e59af0..cb75f62e36c 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -717,7 +717,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat e.join((async () => { const promises: Promise[] = []; promises.push(this.localUserConfiguration.reset(e.profile.settingsResource, e.profile.tasksResource, { scopes: getLocalUserConfigurationScopes(e.profile, !!this.remoteUserConfiguration) })); - if (e.previous.isDefault !== e.profile.isDefault) { + if (e.previous.isDefault !== e.profile.isDefault + || !!e.previous.useDefaultFlags?.settings !== !!e.profile.useDefaultFlags?.settings) { this.createApplicationConfiguration(); if (this.applicationConfiguration) { promises.push(this.reloadApplicationConfiguration(true)); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts index 54fa99d0704..aa017e86fb3 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts @@ -25,7 +25,11 @@ export abstract class ProfileAwareExtensionManagementChannelClient extends BaseE protected readonly uriIdentityService: IUriIdentityService, ) { super(channel); - this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenProfileChanged(e)))); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => { + if (!this.uriIdentityService.extUri.isEqual(e.previous.extensionsResource, e.profile.extensionsResource)) { + e.join(this.whenProfileChanged(e)); + } + })); } protected override fireEvent(event: Emitter, data: InstallExtensionEvent): Promise; diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 5aa0db55715..d26b6ed0bdd 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -64,7 +64,11 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, ) { super(extensionGalleryService, telemetryService, logService, productService, userDataProfilesService); - this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenProfileChanged(e)))); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => { + if (!this.uriIdentityService.extUri.isEqual(e.previous.extensionsResource, e.profile.extensionsResource)) { + e.join(this.whenProfileChanged(e)); + } + })); } private filterEvent({ profileLocation, applicationScoped }: { profileLocation?: URI; applicationScoped?: boolean }): boolean { diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 82356b72fde..e6112182d25 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -51,7 +51,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IKeyboard, INavigatorWithKeyboard } from 'vs/workbench/services/keybinding/browser/navigatorKeyboard'; import { getAllUnboundCommands } from 'vs/workbench/services/keybinding/browser/unboundCommands'; import { IUserKeybindingItem, KeybindingIO, OutputBuilder } from 'vs/workbench/services/keybinding/common/keybindingIO'; -import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; interface ContributedKeyBinding { command: string; @@ -192,6 +193,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { @IHostService private readonly hostService: IHostService, @IExtensionService extensionService: IExtensionService, @IFileService fileService: IFileService, + @IUriIdentityService uriIdentityService: IUriIdentityService, @ILogService logService: ILogService, @IKeyboardLayoutService private readonly keyboardLayoutService: IKeyboardLayoutService ) { @@ -210,7 +212,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this._cachedResolver = null; - this.userKeybindings = this._register(new UserKeybindings(userDataProfileService, fileService, logService)); + this.userKeybindings = this._register(new UserKeybindings(userDataProfileService, uriIdentityService, fileService, logService)); this.userKeybindings.initialize().then(() => { if (this.userKeybindings.keybindings.length) { this.updateResolver(); @@ -699,6 +701,7 @@ class UserKeybindings extends Disposable { constructor( private readonly userDataProfileService: IUserDataProfileService, + private readonly uriIdentityService: IUriIdentityService, private readonly fileService: IFileService, logService: ILogService, ) { @@ -724,10 +727,14 @@ class UserKeybindings extends Disposable { } })); - this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenCurrentProfileChanged(e)))); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => { + if (!this.uriIdentityService.extUri.isEqual(e.previous.keybindingsResource, e.profile.keybindingsResource)) { + e.join(this.whenCurrentProfileChanged()); + } + })); } - private async whenCurrentProfileChanged(e: DidChangeUserDataProfileEvent): Promise { + private async whenCurrentProfileChanged(): Promise { this.watch(); this.reloadConfigurationScheduler.schedule(); } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 75d7d57e639..9468c15aee2 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -43,18 +43,24 @@ export class UserDataProfileManagementService extends Disposable implements IUse this._register(userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e))); this._register(userDataProfilesService.onDidResetWorkspaces(() => this.onDidResetWorkspaces())); this._register(userDataProfileService.onDidChangeCurrentProfile(e => this.onDidChangeCurrentProfile(e))); + this._register(userDataProfilesService.onDidChangeProfiles(e => { + const updatedCurrentProfile = e.updated.find(p => this.userDataProfileService.currentProfile.id === p.id); + if (updatedCurrentProfile) { + this.changeCurrentProfile(updatedCurrentProfile, localize('reload message when updated', "The current profile has been updated. Please reload to switch back to the updated profile")); + } + })); } private onDidChangeProfiles(e: DidChangeProfilesEvent): void { if (e.removed.some(profile => profile.id === this.userDataProfileService.currentProfile.id)) { - this.enterProfile(this.userDataProfilesService.defaultProfile, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile")); + this.changeCurrentProfile(this.userDataProfilesService.defaultProfile, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile")); return; } } private onDidResetWorkspaces(): void { if (!this.userDataProfileService.currentProfile.isDefault) { - this.enterProfile(this.userDataProfilesService.defaultProfile, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile")); + this.changeCurrentProfile(this.userDataProfilesService.defaultProfile, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile")); return; } } @@ -67,14 +73,14 @@ export class UserDataProfileManagementService extends Disposable implements IUse async createAndEnterProfile(name: string, options?: IUserDataProfileOptions): Promise { const profile = await this.userDataProfilesService.createNamedProfile(name, options, toWorkspaceIdentifier(this.workspaceContextService.getWorkspace())); - await this.enterProfile(profile); + await this.changeCurrentProfile(profile); this.telemetryService.publicLog2('profileManagementActionExecuted', { id: 'createAndEnterProfile' }); return profile; } async createAndEnterTransientProfile(): Promise { const profile = await this.userDataProfilesService.createTransientProfile(toWorkspaceIdentifier(this.workspaceContextService.getWorkspace())); - await this.enterProfile(profile); + await this.changeCurrentProfile(profile); this.telemetryService.publicLog2('profileManagementActionExecuted', { id: 'createAndEnterTransientProfile' }); return profile; } @@ -110,11 +116,11 @@ export class UserDataProfileManagementService extends Disposable implements IUse return; } await this.userDataProfilesService.setProfileForWorkspace(workspaceIdentifier, profile); - await this.enterProfile(profile); + await this.changeCurrentProfile(profile); this.telemetryService.publicLog2('profileManagementActionExecuted', { id: 'switchProfile' }); } - private async enterProfile(profile: IUserDataProfile, reloadMessage?: string): Promise { + private async changeCurrentProfile(profile: IUserDataProfile, reloadMessage?: string): Promise { const isRemoteWindow = !!this.environmentService.remoteAuthority; if (!isRemoteWindow) { diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 3c56ae564f2..d68ed2e329e 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -27,9 +27,8 @@ export interface DidChangeUserDataProfileEvent { export const IUserDataProfileService = createDecorator('IUserDataProfileService'); export interface IUserDataProfileService { readonly _serviceBrand: undefined; - readonly onDidUpdateCurrentProfile: Event; - readonly onDidChangeCurrentProfile: Event; readonly currentProfile: IUserDataProfile; + readonly onDidChangeCurrentProfile: Event; updateCurrentProfile(currentProfile: IUserDataProfile): Promise; getShortName(profile: IUserDataProfile): string; } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts index c86fa236bab..7135c0be441 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts @@ -6,6 +6,7 @@ import { Promises } from 'vs/base/common/async'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { equals } from 'vs/base/common/objects'; import { ThemeIcon } from 'vs/base/common/themables'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { defaultUserDataProfileIcon, DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; @@ -17,9 +18,6 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi private readonly _onDidChangeCurrentProfile = this._register(new Emitter()); readonly onDidChangeCurrentProfile = this._onDidChangeCurrentProfile.event; - private readonly _onDidUpdateCurrentProfile = this._register(new Emitter()); - readonly onDidUpdateCurrentProfile = this._onDidUpdateCurrentProfile.event; - private _currentProfile: IUserDataProfile; get currentProfile(): IUserDataProfile { return this._currentProfile; } @@ -29,17 +27,10 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi ) { super(); this._currentProfile = currentProfile; - this._register(userDataProfilesService.onDidChangeProfiles(e => { - const updatedCurrentProfile = e.updated.find(p => this._currentProfile.id === p.id); - if (updatedCurrentProfile) { - this._currentProfile = updatedCurrentProfile; - this._onDidUpdateCurrentProfile.fire(); - } - })); } async updateCurrentProfile(userDataProfile: IUserDataProfile): Promise { - if (this._currentProfile.id === userDataProfile.id) { + if (equals(this._currentProfile, userDataProfile)) { return; } const previous = this._currentProfile; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index a333fabb14e..c892739221a 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -2039,12 +2039,12 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens } copyExtensions(): Promise { throw new Error('Not Supported'); } installExtensionsFromProfile(): Promise { throw new Error('Not Supported'); } + whenProfileChanged(from: IUserDataProfile, to: IUserDataProfile): Promise { throw new Error('Not Supported'); } } export class TestUserDataProfileService implements IUserDataProfileService { readonly _serviceBrand: undefined; - readonly onDidUpdateCurrentProfile = Event.None; readonly onDidChangeCurrentProfile = Event.None; readonly currentProfile = toUserDataProfile('test', 'test', URI.file('tests').with({ scheme: 'vscode-tests' }), URI.file('tests').with({ scheme: 'vscode-tests' })); async updateCurrentProfile(): Promise { } From fa00dc8eabaccb1f3e917cb85aa82b8e679c1163 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 17 Jul 2023 11:19:32 +0200 Subject: [PATCH 169/826] feedback (#188052) #156144 feedback --- .../browser/userDataProfile.ts | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 709d9d213f1..f6d372a3a22 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -428,11 +428,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const disposables = new DisposableStore(); const title = profile ? localize('save profile', "Edit Profile...") : localize('create new profle', "Create New Profile..."); - const settings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: profile?.useDefaultFlags?.settings }; - const keybindings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: profile?.useDefaultFlags?.keybindings }; - const snippets: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: profile?.useDefaultFlags?.snippets }; - const tasks: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: profile?.useDefaultFlags?.tasks }; - const extensions: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: profile?.useDefaultFlags?.extensions }; + const settings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: !profile?.useDefaultFlags?.settings }; + const keybindings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: !profile?.useDefaultFlags?.keybindings }; + const snippets: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: !profile?.useDefaultFlags?.snippets }; + const tasks: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: !profile?.useDefaultFlags?.tasks }; + const extensions: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: !profile?.useDefaultFlags?.extensions }; const resources = [settings, keybindings, snippets, tasks, extensions]; const quickPick = this.quickInputService.createQuickPick(); @@ -450,13 +450,14 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements quickPick.hideCheckAll = true; quickPick.ignoreFocusOut = true; quickPick.customLabel = profile ? localize('save', "Save") : localize('create', "Create"); - quickPick.description = localize('customise the profile', "Select configurations to share from the Default profile:"); + quickPick.description = localize('customise the profile', "Choose what to configure in your Profile:"); quickPick.items = [...resources]; - const updateSelection = () => { + const update = () => { + quickPick.items = resources; quickPick.selectedItems = resources.filter(item => item.picked); }; - updateSelection(); + update(); const validate = () => { if (!profile && this.userDataProfilesService.profiles.some(p => p.name === quickPick.value)) { @@ -464,8 +465,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements quickPick.severity = Severity.Error; return; } - if (resources.every(resource => resource.picked)) { - quickPick.validationMessage = localize('cannot share all', "Cannot share all configurations from the Default Profile."); + if (resources.every(resource => !resource.picked)) { + quickPick.validationMessage = localize('invalid configurations', "The profile should contain at least one configuration."); quickPick.severity = Severity.Error; return; } @@ -474,8 +475,17 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }; disposables.add(quickPick.onDidChangeSelection(items => { + let needUpdate = false; for (const resource of resources) { resource.picked = items.includes(resource); + const description = resource.picked ? undefined : localize('use default profile', "Use Default Profile"); + if (resource.description !== description) { + resource.description = description; + needUpdate = true; + } + } + if (needUpdate) { + update(); } validate(); })); @@ -541,7 +551,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements for (const resource of resources) { resource.picked = option.source?.useDefaultFlags?.[resource.id]; } - updateSelection(); + update(); } }; @@ -569,13 +579,15 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.successCreate'); try { - const useDefaultFlags: UseDefaultProfileFlags | undefined = result.items.length ? { - settings: result.items.includes(settings), - keybindings: result.items.includes(keybindings), - snippets: result.items.includes(snippets), - tasks: result.items.includes(tasks), - extensions: result.items.includes(extensions) - } : undefined; + const useDefaultFlags: UseDefaultProfileFlags | undefined = result.items.length === resources.length + ? undefined + : { + settings: !result.items.includes(settings), + keybindings: !result.items.includes(keybindings), + snippets: !result.items.includes(snippets), + tasks: !result.items.includes(tasks), + extensions: !result.items.includes(extensions) + }; if (profile) { await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, useDefaultFlags: profile.useDefaultFlags && !useDefaultFlags ? {} : useDefaultFlags }); } else { From 433c7361021d5d9ebe080d1869d816f1c37e5b89 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 17 Jul 2023 12:09:36 +0200 Subject: [PATCH 170/826] Fix #177142 (#188053) --- .../common/preferencesContribution.ts | 41 ++++++++----------- .../browser/configurationService.ts | 10 ----- 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index b0f2ac1333d..896ddc2d603 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -103,39 +103,32 @@ export class PreferencesContribution implements IWorkbenchContribution { private start(): void { this.textModelResolverService.registerTextModelContentProvider('vscode', { - provideTextContent: (uri: URI): Promise | null => { + provideTextContent: async (uri: URI): Promise => { if (uri.scheme !== 'vscode') { return null; } if (uri.authority === 'schemas') { - const schemaModel = this.getSchemaModel(uri); - if (schemaModel) { - return Promise.resolve(schemaModel); - } + return this.getSchemaModel(uri); } - return Promise.resolve(this.preferencesService.resolveModel(uri)); + return this.preferencesService.resolveModel(uri); } }); } - private getSchemaModel(uri: URI): ITextModel | null { - let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; - if (schema) { - const modelContent = JSON.stringify(schema); - const languageSelection = this.languageService.createById('jsonc'); - const model = this.modelService.createModel(modelContent, languageSelection, uri); - const disposables = new DisposableStore(); - disposables.add(schemaRegistry.onDidChangeSchema(schemaUri => { - if (schemaUri === uri.toString()) { - schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; - model.setValue(JSON.stringify(schema)); - } - })); - disposables.add(model.onWillDispose(() => disposables.dispose())); - - return model; - } - return null; + private getSchemaModel(uri: URI): ITextModel { + let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()] ?? {} /* Use empty schema if not yet registered */; + const modelContent = JSON.stringify(schema); + const languageSelection = this.languageService.createById('jsonc'); + const model = this.modelService.createModel(modelContent, languageSelection, uri); + const disposables = new DisposableStore(); + disposables.add(schemaRegistry.onDidChangeSchema(schemaUri => { + if (schemaUri === uri.toString()) { + schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; + model.setValue(JSON.stringify(schema)); + } + })); + disposables.add(model.onWillDispose(() => disposables.dispose())); + return model; } dispose(): void { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index cb75f62e36c..bc40358f62f 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -1129,16 +1129,6 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo ) { super(); - this.registerSchemas({ - defaultSettingsSchema: {}, - userSettingsSchema: {}, - profileSettingsSchema: {}, - machineSettingsSchema: {}, - workspaceSettingsSchema: {}, - folderSettingsSchema: {}, - configDefaultsSchema: {}, - }); - extensionService.whenInstalledExtensionsRegistered().then(() => { this.registerConfigurationSchemas(); From 35573c42a59946d4ea0c5b533055f86a5585abbf Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 17 Jul 2023 08:55:02 +0200 Subject: [PATCH 171/826] Update to @vscode/proxy-agent 0.16.0 --- package.json | 4 +- remote/package.json | 2 +- remote/yarn.lock | 108 ++++++++++++++++++++++++------------------- yarn.lock | 109 +++++++++++++++++++++++--------------------- 4 files changed, 122 insertions(+), 101 deletions(-) diff --git a/package.json b/package.json index a1961f94d9d..3c6fd8edce9 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", - "@vscode/proxy-agent": "^0.15.0", + "@vscode/proxy-agent": "^0.16.0", "@vscode/ripgrep": "^1.15.5", "@vscode/spdlog": "^0.13.10", "@vscode/sqlite3": "5.1.6-vscode", @@ -231,4 +231,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} \ No newline at end of file +} diff --git a/remote/package.json b/remote/package.json index 9b92d007a47..e10bd518df4 100644 --- a/remote/package.json +++ b/remote/package.json @@ -7,7 +7,7 @@ "@microsoft/1ds-post-js": "^3.2.2", "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.15.0", + "@vscode/proxy-agent": "^0.16.0", "@vscode/ripgrep": "^1.15.5", "@vscode/spdlog": "^0.13.10", "@vscode/vscode-languagedetection": "1.0.21", diff --git a/remote/yarn.lock b/remote/yarn.lock index 8084ab6e27a..7c74dbf3e57 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -48,27 +48,27 @@ node-addon-api "^3.2.1" node-gyp-build "^4.3.0" -"@tootallnate/once@1", "@tootallnate/once@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tootallnate/once@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-3.0.0.tgz#d52238c9052d746c9689523e650160e70786bc9a" + integrity sha512-OAdBVB7rlwvLD+DiecSAyVKzKVmSfXbouCyM5I6wHGi4MGXIyFqErg1IvyJ7PI1e+GYZuZh7cCHV/c4LA8SKMw== "@vscode/iconv-lite-umd@0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== -"@vscode/proxy-agent@^0.15.0": - version "0.15.0" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.15.0.tgz#b8fb8b89180a71295a8f8682775f69ab1dcf6860" - integrity sha512-HpD4A9CUOwKbC6vLa0+MEsCo/qlgbue9U9s8Z7NzJDdf2YEGjUaYf9Mvj5T1LhJX20Hv1COvkGcc7zPhtIbgbA== +"@vscode/proxy-agent@^0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.16.0.tgz#32054387f7aaf26d1b5d53f553d53bfd8489eab8" + integrity sha512-b8yBHgdngDrP+9HPJtnPUJjPHd+zfEvOYoc8KioWJVs0rFVT2U77nFDVC70Mrrscf87ya2a/sPY32nTrwFfOQQ== dependencies: - "@tootallnate/once" "^1.1.2" - agent-base "^6.0.2" - debug "^4.3.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - socks-proxy-agent "^5.0.0" + "@tootallnate/once" "^3.0.0" + agent-base "^7.0.1" + debug "^4.3.4" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" + socks-proxy-agent "^8.0.1" optionalDependencies: "@vscode/windows-ca-certs" "^0.3.1" @@ -120,7 +120,7 @@ agent-base@4: dependencies: es6-promisify "^5.0.0" -agent-base@6, agent-base@^6.0.2: +agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -134,6 +134,13 @@ agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" +agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.0.tgz#536802b76bc0b34aa50195eb2442276d613e3434" + integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg== + dependencies: + debug "^4.3.4" + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -237,10 +244,10 @@ debug@4: dependencies: ms "^2.1.1" -debug@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== +debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" @@ -356,14 +363,13 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== +http-proxy-agent@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz#e9096c5afd071a3fce56e6252bb321583c124673" + integrity sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ== dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" + agent-base "^7.1.0" + debug "^4.3.4" https-proxy-agent@^2.2.3: version "2.2.4" @@ -381,6 +387,14 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +https-proxy-agent@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz#0277e28f13a07d45c663633841e20a40aaafe0ab" + integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ== + dependencies: + agent-base "^7.0.2" + debug "4" + ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -396,10 +410,10 @@ ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== is-extglob@^2.1.1: version "2.1.1" @@ -691,27 +705,27 @@ simple-get@^4.0.0: once "^1.3.1" simple-concat "^1.0.0" -smart-buffer@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" - integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== -socks-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60" - integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA== +socks-proxy-agent@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz#ffc5859a66dac89b0c4dab90253b96705f3e7120" + integrity sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ== dependencies: - agent-base "6" - debug "4" - socks "^2.3.3" + agent-base "^7.0.1" + debug "^4.3.4" + socks "^2.7.1" -socks@^2.3.3: - version "2.6.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" - integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== +socks@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== dependencies: - ip "^1.1.5" - smart-buffer "^4.1.0" + ip "^2.0.0" + smart-buffer "^4.2.0" string-width@^1.0.1: version "1.0.2" diff --git a/yarn.lock b/yarn.lock index ac27990a8ae..76bce0cf9da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -822,16 +822,16 @@ resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== -"@tootallnate/once@1", "@tootallnate/once@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@tootallnate/once@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-3.0.0.tgz#d52238c9052d746c9689523e650160e70786bc9a" + integrity sha512-OAdBVB7rlwvLD+DiecSAyVKzKVmSfXbouCyM5I6wHGi4MGXIyFqErg1IvyJ7PI1e+GYZuZh7cCHV/c4LA8SKMw== + "@trysound/sax@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" @@ -1304,17 +1304,17 @@ bindings "^1.5.0" node-addon-api "^6.0.0" -"@vscode/proxy-agent@^0.15.0": - version "0.15.0" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.15.0.tgz#b8fb8b89180a71295a8f8682775f69ab1dcf6860" - integrity sha512-HpD4A9CUOwKbC6vLa0+MEsCo/qlgbue9U9s8Z7NzJDdf2YEGjUaYf9Mvj5T1LhJX20Hv1COvkGcc7zPhtIbgbA== +"@vscode/proxy-agent@^0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.16.0.tgz#32054387f7aaf26d1b5d53f553d53bfd8489eab8" + integrity sha512-b8yBHgdngDrP+9HPJtnPUJjPHd+zfEvOYoc8KioWJVs0rFVT2U77nFDVC70Mrrscf87ya2a/sPY32nTrwFfOQQ== dependencies: - "@tootallnate/once" "^1.1.2" - agent-base "^6.0.2" - debug "^4.3.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - socks-proxy-agent "^5.0.0" + "@tootallnate/once" "^3.0.0" + agent-base "^7.0.1" + debug "^4.3.4" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" + socks-proxy-agent "^8.0.1" optionalDependencies: "@vscode/windows-ca-certs" "^0.3.1" @@ -1644,12 +1644,12 @@ agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -agent-base@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== +agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.0.tgz#536802b76bc0b34aa50195eb2442276d613e3434" + integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg== dependencies: - debug "4" + debug "^4.3.4" ajv-formats@^2.1.1: version "2.1.1" @@ -3200,7 +3200,7 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -5379,15 +5379,6 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" @@ -5397,6 +5388,14 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-proxy-agent@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz#e9096c5afd071a3fce56e6252bb321583c124673" + integrity sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + http2-wrapper@^1.0.0-beta.5.2: version "1.0.3" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" @@ -5429,6 +5428,14 @@ https-proxy-agent@^5.0.1: agent-base "6" debug "4" +https-proxy-agent@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz#0277e28f13a07d45c663633841e20a40aaafe0ab" + integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ== + dependencies: + agent-base "^7.0.2" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -5591,10 +5598,10 @@ invert-kv@^2.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== -ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== is-absolute-url@^2.0.0: version "2.1.0" @@ -9010,10 +9017,10 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -smart-buffer@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" - integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== snapdragon-node@^2.0.1: version "2.1.1" @@ -9045,22 +9052,22 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -socks-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60" - integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA== +socks-proxy-agent@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz#ffc5859a66dac89b0c4dab90253b96705f3e7120" + integrity sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ== dependencies: - agent-base "6" - debug "4" - socks "^2.3.3" + agent-base "^7.0.1" + debug "^4.3.4" + socks "^2.7.1" -socks@^2.3.3: - version "2.6.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" - integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== +socks@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== dependencies: - ip "^1.1.5" - smart-buffer "^4.1.0" + ip "^2.0.0" + smart-buffer "^4.2.0" sort-keys-length@^1.0.0: version "1.0.1" From 8d4cd145ac6c9d99a10d1323cec1b50eb358e37a Mon Sep 17 00:00:00 2001 From: yshaojun Date: Mon, 17 Jul 2023 18:32:12 +0800 Subject: [PATCH 172/826] chore: use Set instead of Map --- .../contrib/unicodeHighlighter/browser/unicodeHighlighter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts index 122648ba83c..eea1de6f65b 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts @@ -429,7 +429,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa } const result: MarkdownHover[] = []; - const existedReason = new Map(); + const existedReason = new Set(); let index = 300; for (const d of lineDecorations) { @@ -484,7 +484,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa if (existedReason.has(reason)) { continue; } - existedReason.set(reason, true); + existedReason.add(reason); const adjustSettingsArgs: ShowExcludeOptionsArgs = { codePoint: codePoint, From 7c00c24205df7efb78361268c09297c3b0b50098 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 17 Jul 2023 14:30:08 +0200 Subject: [PATCH 173/826] hook up rich slash commands with UI --- .../api/common/extHostChatSlashCommand.ts | 2 +- .../contrib/chat/common/chatServiceImpl.ts | 49 ++++++++++++++++--- .../contrib/chat/common/chatSlashCommands.ts | 9 +++- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatSlashCommand.ts b/src/vs/workbench/api/common/extHostChatSlashCommand.ts index 1a0375199c4..9d2161171af 100644 --- a/src/vs/workbench/api/common/extHostChatSlashCommand.ts +++ b/src/vs/workbench/api/common/extHostChatSlashCommand.ts @@ -71,7 +71,7 @@ export class ExtHostChatSlashCommands implements ExtHostChatSlashCommandsShape { { history: context.history.map(typeConvert.ChatMessage.to) }, new Progress(p => { throwIfDone(); - this._proxy.$handleProgressChunk(requestId, { value: p.message.value }); + this._proxy.$handleProgressChunk(requestId, { content: p.message.value }); }), token ); diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 68a14aaf8b7..14cd0ef9003 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -17,12 +17,15 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; +import { Progress } from 'vs/platform/progress/common/progress'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatService, IChatUserActionEvent, ISlashCommand, ISlashCommandProvider, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatResponse, IChatService, IChatUserActionEvent, ISlashCommand, ISlashCommandProvider, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatSlashCommandService, IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; const serializedChatKey = 'interactive.sessions'; @@ -145,7 +148,8 @@ export class ChatService extends Disposable implements IChatService { @IInstantiationService private readonly instantiationService: IInstantiationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, ) { super(); @@ -449,7 +453,27 @@ export class ChatService extends Disposable implements IChatService { if (usedSlashCommand?.command) { this._onDidSubmitSlashCommand.fire({ slashCommand: usedSlashCommand.command, sessionId: model.sessionId }); } - let rawResponse = await provider.provideReply({ session: model.session!, message: resolvedCommand }, progressCallback, token); + + let rawResponse: IChatResponse | null | undefined; + + if ((typeof resolvedCommand === 'string' && typeof message === 'string' && this.chatSlashCommandService.hasCommand(resolvedCommand))) { + // contributed slash commands + // TODO: spell this out in the UI + const history: IChatMessage[] = []; + for (const request of model.getRequests()) { + if (typeof request.message !== 'string' || !request.response) { + continue; + } + history.push({ role: ChatMessageRole.User, content: request.message }); + history.push({ role: ChatMessageRole.Assistant, content: request.response?.response.value }); + } + await this.chatSlashCommandService.executeCommand(resolvedCommand, message.substring(resolvedCommand.length + 1).trimStart(), new Progress(p => progressCallback(p)), history, token); + rawResponse = { session: model.session! }; + + } else { + rawResponse = await provider.provideReply({ session: model.session!, message: resolvedCommand }, progressCallback, token); + } + if (token.isCancellationRequested) { return; } else { @@ -510,11 +534,15 @@ export class ChatService extends Disposable implements IChatService { private async handleSlashCommand(sessionId: string, command: string): Promise { const slashCommands = await this.getSlashCommands(sessionId, CancellationToken.None); for (const slashCommand of slashCommands ?? []) { - if (command.startsWith(`/${slashCommand.command}`) && slashCommand.provider) { - return await slashCommand.provider.resolveSlashCommand(command, CancellationToken.None) ?? command; + if (command.startsWith(`/${slashCommand.command}`)) { + if (slashCommand.provider) { + return await slashCommand.provider.resolveSlashCommand(command, CancellationToken.None) ?? command; + } else if (this.chatSlashCommandService.hasCommand(slashCommand.command)) { + return slashCommand.command; + } + } } - return command; } @@ -542,9 +570,16 @@ export class ChatService extends Disposable implements IChatService { .then(commands => commands?.map(c => ({ ...c, provider: p })))) ]); + const serviceResults = this.chatSlashCommandService.getCommands().map(data => { + return { + command: data.name, + detail: data.detail, + }; + }); + try { const slashCommands = (await providerResults).filter(c => !!c) as ISlashCommand[][]; - return withNullAsUndefined(slashCommands.flat()); + return slashCommands.flat().concat(serviceResults); } catch (e) { this.logService.error(e); diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts index 1b4cbcc31e6..587c62e6f8a 100644 --- a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -58,7 +58,7 @@ export interface IChatSlashData { } export interface IChatSlashFragment { - value: string; + content: string; } export type IChatSlashCallback = { (prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise }; @@ -71,6 +71,7 @@ export interface IChatSlashCommandService { registerSlashCallback(id: string, command: IChatSlashCallback): IDisposable; executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise; getCommands(): Array; + hasCommand(id: string): boolean; } type Tuple = { data: IChatSlashData; command?: IChatSlashCallback }; @@ -128,6 +129,10 @@ export class ChatSlashCommandService implements IChatSlashCommandService { return Array.from(this._commands.values(), v => v.data); } + hasCommand(id: string): boolean { + return this._commands.has(id); + } + async executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise { const data = this._commands.get(id); if (!data) { @@ -217,7 +222,7 @@ registerAction2(class extends Action2 { pick.cmd.id, prompt, new Progress(value => { - p.report({ message: value.value }); + p.report({ message: value.content }); console.log(value); }), [], From e5cdd7d0fd91d1bbadda2402415ce1d1dfbfe938 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 17 Jul 2023 06:28:37 -0700 Subject: [PATCH 174/826] Make Cargo.lock a readonly file in vscode --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5d2930c5a92..7eefe0c57f6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -36,6 +36,7 @@ "files.readonlyInclude": { "**/node_modules/**": true, "**/yarn.lock": true, + "**/Cargo.lock": true, "src/vs/workbench/workbench.web.main.css": true, "src/vs/workbench/workbench.desktop.main.css": true, "src/vs/workbench/workbench.desktop.main.nls.js": true, From fc0a8da96fd7010960c8f857f114ef90167f38d5 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 17 Jul 2023 08:32:25 -0700 Subject: [PATCH 175/826] fix #188081 --- .../browser/terminal.accessibility.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index 26042ec1cb1..9e364caf3da 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -92,7 +92,7 @@ registerTerminalAction({ { primary: KeyMod.Shift | KeyCode.Tab, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, ContextKeyExpr.or(terminalTabFocusModeContextKey, TerminalContextKeys.accessibleBufferFocus.negate())) + when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, TerminalContextKeys.focus, ContextKeyExpr.or(terminalTabFocusModeContextKey, TerminalContextKeys.accessibleBufferFocus.negate())) } ], run: async (c) => { From cc997383d8f34d1eb55f00c28abf6711a79d681e Mon Sep 17 00:00:00 2001 From: aamunger Date: Mon, 17 Jul 2023 10:38:18 -0700 Subject: [PATCH 176/826] store title passed from jupyter to use as tab name --- .../browser/interactive.contribution.ts | 1 + .../browser/interactiveEditorInput.ts | 21 ++++++++++--------- .../common/workingCopyBackupService.ts | 5 +++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index da64485f909..afa4414c3c0 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -392,6 +392,7 @@ registerAction2(class extends Action2 { counter++; } while (existingNotebookDocument.has(notebookUri.toString())); + InteractiveEditorInput.setName(notebookUri, title); logService.debug('Open new interactive window:', notebookUri.toString(), inputUri.toString()); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts index 49e3601838a..aece1b21e47 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts @@ -25,6 +25,14 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot return instantiationService.createInstance(InteractiveEditorInput, resource, inputResource, title, language); } + private static windowNames: Record = {}; + + static setName(notebookUri: URI, title: string | undefined) { + if (title) { + this.windowNames[notebookUri.path] = title; + } + } + static readonly ID: string = 'workbench.input.interactive'; public override get editorId(): string { @@ -35,7 +43,7 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot return InteractiveEditorInput.ID; } - private _initTitle?: string; + private name: string; get language() { return this._inputModelRef?.object.textEditorModel.getLanguageId() ?? this._initLanguage; @@ -91,7 +99,7 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot super(); this._notebookEditorInput = input; this._register(this._notebookEditorInput); - this._initTitle = title; + this.name = title ?? InteractiveEditorInput.windowNames[resource.path] ?? paths.basename(resource.path, paths.extname(resource.path)); this._initLanguage = languageId; this._resource = resource; this._inputResource = inputResource; @@ -209,14 +217,7 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot } override getName() { - if (this._initTitle) { - return this._initTitle; - } - - const p = this.primary.resource!.path; - const basename = paths.basename(p); - - return basename.substr(0, basename.length - paths.extname(p).length); + return this.name; } override isModified() { diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts index f9ec56ad11a..2486e142b71 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts @@ -134,8 +134,9 @@ export abstract class WorkingCopyBackupService implements IWorkingCopyBackupServ if (backupWorkspaceHome) { return new WorkingCopyBackupServiceImpl(backupWorkspaceHome, this.fileService, this.logService); } - - return new InMemoryWorkingCopyBackupService(); + else { + return new WorkingCopyBackupServiceImpl(URI.file('c:\\temp\\backup'), this.fileService, this.logService); + } } reinitialize(backupWorkspaceHome: URI | undefined): void { From 1bd2fc7937140bf342459ff895a87dff9b219eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=93=E8=89=AF?= <1204183885@qq.com> Date: Tue, 18 Jul 2023 01:42:13 +0800 Subject: [PATCH 177/826] fix: Close #187788, recovery tree view state (#187902) --- .../contrib/files/browser/views/explorerView.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 4572956371b..9e8b4ec5af8 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -173,7 +173,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { private viewHasSomeCollapsibleRootItem: IContextKey; private viewVisibleContextKey: IContextKey; - + private setTreeInputPromise: Promise | undefined; private horizontalScrolling: boolean | undefined; private dragHandler!: DelayedDragHandler; @@ -522,7 +522,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { // save view state this._register(this.storageService.onWillSaveState(() => { - this.storageService.store(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE, StorageTarget.MACHINE); + this.storeTreeViewState(); })); } @@ -540,6 +540,10 @@ export class ExplorerView extends ViewPane implements IExplorerView { } } + private storeTreeViewState() { + this.storageService.store(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE, StorageTarget.MACHINE); + } + private setContextKeys(stat: ExplorerItem | null | undefined): void { const folders = this.contextService.getWorkspace().folders; const resource = stat ? stat.resource : folders[folders.length - 1].uri; @@ -666,6 +670,11 @@ export class ExplorerView extends ViewPane implements IExplorerView { return Promise.resolve(undefined); } + // Wait for the last execution to complete before executing + if (this.setTreeInputPromise) { + await this.setTreeInputPromise; + } + const initialInputSetup = !this.tree.getInput(); if (initialInputSetup) { perf.mark('code/willResolveExplorer'); @@ -688,7 +697,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { } const previousInput = this.tree.getInput(); - const promise = this.tree.setInput(input, viewState).then(async () => { + const promise = this.setTreeInputPromise = this.tree.setInput(input, viewState).then(async () => { if (Array.isArray(input)) { if (!viewState || previousInput instanceof ExplorerItem) { // There is no view state for this workspace (we transitioned from a folder workspace?), expand up to five roots. @@ -883,6 +892,8 @@ export class ExplorerView extends ViewPane implements IExplorerView { const treeInputArray = Array.isArray(treeInput) ? treeInput : Array.from(treeInput.children.values()); // Has collapsible root when anything is expanded this.viewHasSomeCollapsibleRootItem.set(hasExpandedNode(this.tree, treeInputArray)); + // synchronize state to cache + this.storeTreeViewState(); } override dispose(): void { From bafd442c4e1406e5eee5261bc1e1d82174c054de Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 17 Jul 2023 10:56:27 -0700 Subject: [PATCH 178/826] cli: improve `code tunnel` with existing tunnels (#188091) - Apply the name/tunnel-name from the CLI to automatically (do a normal tag sync). Previously the CLI could host tunnels that were unusable unless the consumer did the tag setup, which they should not. - Allow "tunnel ID" to be specified in the new `.` format that devtunnels has adopted. --- cli/src/commands/args.rs | 1 + cli/src/commands/tunnels.rs | 35 ++++++++++++++++++--------- cli/src/tunnels/dev_tunnels.rs | 43 +++++++++++++++++++++++++++------- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index a97e1fc3870..e716e58b7c0 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -548,6 +548,7 @@ pub enum OutputFormat { #[derive(Args, Clone, Debug, Default)] pub struct ExistingTunnelArgs { /// Name you'd like to assign preexisting tunnel to use to connect the tunnel + /// Old option, new code sohuld just use `--name`. #[clap(long, hide = true)] pub tunnel_name: Option, diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index 24e349bb720..c098188135e 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -59,20 +59,31 @@ impl From for crate::auth::AuthProvider { } } -impl From for Option { - fn from(d: ExistingTunnelArgs) -> Option { - if let (Some(tunnel_id), Some(tunnel_name), Some(cluster), Some(host_token)) = - (d.tunnel_id, d.tunnel_name, d.cluster, d.host_token) - { +fn fulfill_existing_tunnel_args( + d: ExistingTunnelArgs, + name_arg: &Option, +) -> Option { + let tunnel_name = d.tunnel_name.or_else(|| name_arg.clone()); + + match (d.tunnel_id, d.cluster, d.host_token) { + (Some(tunnel_id), None, Some(host_token)) => { + let i = tunnel_id.find('.')?; Some(dev_tunnels::ExistingTunnel { - tunnel_id, + tunnel_id: tunnel_id[..i].to_string(), + cluster: tunnel_id[i + 1..].to_string(), tunnel_name, host_token, - cluster, }) - } else { - None } + + (Some(tunnel_id), Some(cluster), Some(host_token)) => Some(dev_tunnels::ExistingTunnel { + tunnel_id, + tunnel_name, + host_token, + cluster, + }), + + _ => None, } } @@ -412,8 +423,10 @@ async fn serve_with_csa( let auth = Auth::new(&paths, log.clone()); let mut dt = dev_tunnels::DevTunnels::new(&log, auth, &paths); loop { - let tunnel = if let Some(d) = gateway_args.tunnel.clone().into() { - dt.start_existing_tunnel(d).await + let tunnel = if let Some(t) = + fulfill_existing_tunnel_args(gateway_args.tunnel.clone(), &gateway_args.name) + { + dt.start_existing_tunnel(t).await } else { dt.start_new_launcher_tunnel(gateway_args.name.as_deref(), gateway_args.random_name) .await diff --git a/cli/src/tunnels/dev_tunnels.rs b/cli/src/tunnels/dev_tunnels.rs index c4ca9741b88..a04bdbcaf6d 100644 --- a/cli/src/tunnels/dev_tunnels.rs +++ b/cli/src/tunnels/dev_tunnels.rs @@ -229,7 +229,7 @@ lazy_static! { #[derive(Clone, Debug)] pub struct ExistingTunnel { /// Name you'd like to assign preexisting tunnel to use to connect to the VS Code Server - pub tunnel_name: String, + pub tunnel_name: Option, /// Token to authenticate and use preexisting tunnel pub host_token: String, @@ -393,7 +393,12 @@ impl DevTunnels { }; tunnel = self - .sync_tunnel_tags(&persisted.name, tunnel, &HOST_TUNNEL_REQUEST_OPTIONS) + .sync_tunnel_tags( + &self.client, + &persisted.name, + tunnel, + &HOST_TUNNEL_REQUEST_OPTIONS, + ) .await?; let locator = TunnelLocator::try_from(&tunnel).unwrap(); @@ -532,6 +537,7 @@ impl DevTunnels { /// other version tags. async fn sync_tunnel_tags( &self, + client: &TunnelManagementClient, name: &str, tunnel: Tunnel, options: &TunnelRequestOptions, @@ -558,7 +564,7 @@ impl DevTunnels { let result = spanf!( self.log, self.log.span("dev-tunnel.protocol-tag-update"), - self.client.update_tunnel(&tunnel_update, options) + client.update_tunnel(&tunnel_update, options) ); result.map_err(|e| wrap(e, "tunnel tag update failed").into()) @@ -639,6 +645,12 @@ impl DevTunnels { Ok(()) } + fn get_placeholder_name() -> String { + let mut n = clean_hostname_for_tunnel(&gethostname::gethostname().to_string_lossy()); + n.make_ascii_lowercase(); + n + } + async fn get_name_for_tunnel( &mut self, preferred_name: Option<&str>, @@ -670,10 +682,7 @@ impl DevTunnels { use_random_name = true; } - let mut placeholder_name = - clean_hostname_for_tunnel(&gethostname::gethostname().to_string_lossy()); - placeholder_name.make_ascii_lowercase(); - + let mut placeholder_name = Self::get_placeholder_name(); if !is_name_free(&placeholder_name) { for i in 2.. { let fixed_name = format!("{}{}", placeholder_name, i); @@ -715,7 +724,10 @@ impl DevTunnels { tunnel: ExistingTunnel, ) -> Result { let tunnel_details = PersistedTunnel { - name: tunnel.tunnel_name, + name: match tunnel.tunnel_name { + Some(n) => n, + None => Self::get_placeholder_name(), + }, id: tunnel.tunnel_id, cluster: tunnel.cluster, }; @@ -725,10 +737,23 @@ impl DevTunnels { tunnel.host_token.clone(), )); + let client = mgmt.into(); + self.sync_tunnel_tags( + &client, + &tunnel_details.name, + Tunnel { + cluster_id: Some(tunnel_details.cluster.clone()), + tunnel_id: Some(tunnel_details.id.clone()), + ..Default::default() + }, + &HOST_TUNNEL_REQUEST_OPTIONS, + ) + .await?; + self.start_tunnel( tunnel_details.locator(), &tunnel_details, - mgmt.into(), + client, StaticAccessTokenProvider::new(tunnel.host_token), ) .await From 1b291302df4068cad0516b8600e003c72c4a9b97 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 17 Jul 2023 19:57:00 +0200 Subject: [PATCH 179/826] Implement apply extension to all profiles (#188093) * Revert "remove the extension action to apply in all profiles #157492 (#187815)" This reverts commit 429d6d203af96da2edb38682ba8b9fd0ed173be2. * implement #157492 --- .../abstractExtensionManagementService.ts | 41 +++++++++++++- .../common/extensionManagement.ts | 1 + .../common/extensionManagementIpc.ts | 9 ++++ .../node/extensionManagementService.ts | 54 ++++++++++++++----- .../userDataSync/common/extensionsMerge.ts | 5 ++ .../userDataSync/common/extensionsSync.ts | 8 ++- .../userDataSync/common/userDataSync.ts | 2 + .../browser/extensions.contribution.ts | 18 +++++++ .../browser/extensionsWorkbenchService.ts | 9 ++-- .../extensionManagementChannelClient.ts | 8 +++ .../common/extensionManagementService.ts | 8 +++ .../common/webExtensionManagementService.ts | 22 +++++--- .../remoteExtensionManagementService.ts | 4 +- .../test/browser/workbenchTestServices.ts | 1 + 14 files changed, 162 insertions(+), 28 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 2ed53a4b0d7..96195aae781 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -23,6 +23,7 @@ import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, Target import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; export type ExtensionVerificationStatus = boolean | string; @@ -69,11 +70,15 @@ export abstract class AbstractExtensionManagementService extends Disposable impl protected _onDidUninstallExtension = this._register(new Emitter()); get onDidUninstallExtension() { return this._onDidUninstallExtension.event; } + protected readonly _onDidUpdateExtensionMetadata = this._register(new Emitter()); + get onDidUpdateExtensionMetadata() { return this._onDidUpdateExtensionMetadata.event; } + private readonly participants: IExtensionManagementParticipant[] = []; constructor( @IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService, @ITelemetryService protected readonly telemetryService: ITelemetryService, + @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, @ILogService protected readonly logService: ILogService, @IProductService protected readonly productService: IProductService, @IUserDataProfilesService protected readonly userDataProfilesService: IUserDataProfilesService, @@ -147,6 +152,40 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return this.uninstallExtension(extension, options); } + async toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise { + if (isApplicationScopedExtension(extension.manifest)) { + return extension; + } + + if (extension.isApplicationScoped) { + let local = await this.updateMetadata(extension, { isApplicationScoped: false }, this.userDataProfilesService.defaultProfile.extensionsResource); + if (!this.uriIdentityService.extUri.isEqual(fromProfileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) { + local = await this.copyExtension(extension, this.userDataProfilesService.defaultProfile.extensionsResource, fromProfileLocation); + } + + for (const profile of this.userDataProfilesService.profiles) { + const existing = (await this.getInstalled(ExtensionType.User, profile.extensionsResource)) + .find(e => areSameExtensions(e.identifier, extension.identifier)); + if (existing) { + this._onDidUpdateExtensionMetadata.fire(existing); + } else { + this._onDidUninstallExtension.fire({ identifier: extension.identifier, profileLocation: profile.extensionsResource }); + } + } + return local; + } + + else { + const local = this.uriIdentityService.extUri.isEqual(fromProfileLocation, this.userDataProfilesService.defaultProfile.extensionsResource) + ? await this.updateMetadata(extension, { isApplicationScoped: true }, this.userDataProfilesService.defaultProfile.extensionsResource) + : await this.copyExtension(extension, fromProfileLocation, this.userDataProfilesService.defaultProfile.extensionsResource, { isApplicationScoped: true }); + + this._onDidInstallExtensions.fire([{ identifier: local.identifier, operation: InstallOperation.Install, local, profileLocation: this.userDataProfilesService.defaultProfile.extensionsResource, applicationScoped: true }]); + return local; + } + + } + getExtensionsControlManifest(): Promise { const now = new Date().getTime(); @@ -705,12 +744,12 @@ export abstract class AbstractExtensionManagementService extends Disposable impl abstract reinstallFromGallery(extension: ILocalExtension): Promise; abstract cleanUp(): Promise; - abstract onDidUpdateExtensionMetadata: Event; abstract updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise; protected abstract getCurrentExtensionsManifestLocation(): URI; protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask; protected abstract createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask; + protected abstract copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata?: Partial): Promise; } export function joinErrors(errorOrErrors: (Error | string) | (Array)): Error { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 23df3ac6abd..9a9abb281fc 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -478,6 +478,7 @@ export interface IExtensionManagementService { installFromLocation(location: URI, profileLocation: URI): Promise; installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise; uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise; + toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise; reinstallFromGallery(extension: ILocalExtension): Promise; getInstalled(type?: ExtensionType, profileLocation?: URI): Promise; getExtensionsControlManifest(): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index c1d91312b45..fbefe7b1247 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -142,6 +142,10 @@ export class ExtensionManagementChannel implements IServerChannel { const extensions = await this.service.getInstalled(args[0], transformIncomingURI(args[1], uriTransformer)); return extensions.map(e => transformOutgoingExtension(e, uriTransformer)); } + case 'toggleAppliationScope': { + const extension = await this.service.toggleAppliationScope(transformIncomingExtension(args[0], uriTransformer), transformIncomingURI(args[1], uriTransformer)); + return transformOutgoingExtension(extension, uriTransformer); + } case 'copyExtensions': { return this.service.copyExtensions(transformIncomingURI(args[0], uriTransformer), transformIncomingURI(args[1], uriTransformer)); } @@ -277,6 +281,11 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt .then(extension => transformIncomingExtension(extension, null)); } + toggleAppliationScope(local: ILocalExtension, fromProfileLocation: URI): Promise { + return this.channel.call('toggleAppliationScope', [local, fromProfileLocation]) + .then(extension => transformIncomingExtension(extension, null)); + } + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { return this.channel.call('copyExtensions', [fromProfileLocation, toProfileLocation]); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 9ad1f4c6759..5852d66a9c4 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -32,7 +32,7 @@ import { Metadata, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; +import { IExtensionsProfileScannerService, IScannedProfileExtension } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { IExtensionsScannerService, IScannedExtension, ScanOptions } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader'; import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle'; @@ -71,9 +71,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi private readonly manifestCache: ExtensionsManifestCache; private readonly extensionsDownloader: ExtensionsDownloader; - private readonly _onDidUpdateExtensionMetadata = this._register(new Emitter()); - override readonly onDidUpdateExtensionMetadata = this._onDidUpdateExtensionMetadata.event; - private readonly installGalleryExtensionsTasks = new Map(); constructor( @@ -87,10 +84,10 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi @IInstantiationService instantiationService: IInstantiationService, @IFileService private readonly fileService: IFileService, @IProductService productService: IProductService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IUriIdentityService uriIdentityService: IUriIdentityService, @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService ) { - super(galleryService, telemetryService, logService, productService, userDataProfilesService); + super(galleryService, telemetryService, uriIdentityService, logService, productService, userDataProfilesService); const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle)); this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension))); this.manifestCache = this._register(new ExtensionsManifestCache(userDataProfilesService, fileService, uriIdentityService, this, this.logService)); @@ -227,6 +224,10 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return this.installFromGallery(galleryExtension); } + protected copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { + return this.extensionsScanner.copyExtension(extension, fromProfileLocation, toProfileLocation, metadata); + } + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation); } @@ -531,20 +532,25 @@ export class ExtensionsScanner extends Disposable { async scanMetadata(local: ILocalExtension, profileLocation?: URI): Promise { if (profileLocation) { - const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(profileLocation); - return extensions.find(e => areSameExtensions(e.identifier, local.identifier))?.metadata; + const extension = await this.getScannedExtension(local, profileLocation); + return extension?.metadata; } else { return this.extensionsScannerService.scanMetadata(local.location); } } + private async getScannedExtension(local: ILocalExtension, profileLocation: URI): Promise { + const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(profileLocation); + return extensions.find(e => areSameExtensions(e.identifier, local.identifier)); + } + async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise { if (profileLocation) { await this.extensionsProfileScannerService.updateMetadata([[local, metadata]], profileLocation); } else { await this.extensionsScannerService.updateMetadata(local.location, metadata); } - return this.scanLocalExtension(local.location, local.type); + return this.scanLocalExtension(local.location, local.type, profileLocation); } getUninstalledExtensions(): Promise> { @@ -573,6 +579,20 @@ export class ExtensionsScanner extends Disposable { await this.withUninstalledExtensions(uninstalled => delete uninstalled[ExtensionKey.create(extension).toString()]); } + async copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { + const source = await this.getScannedExtension(extension, fromProfileLocation); + const target = await this.getScannedExtension(extension, toProfileLocation); + metadata = { ...source?.metadata, ...metadata }; + + if (target) { + await this.extensionsProfileScannerService.updateMetadata([[extension, { ...target.metadata, ...metadata }]], toProfileLocation); + } else { + await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, metadata]], toProfileLocation); + } + + return this.scanLocalExtension(extension.location, extension.type, toProfileLocation); + } + async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { const fromExtensions = await this.scanExtensions(ExtensionType.User, fromProfileLocation); const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(fromExtensions @@ -633,10 +653,18 @@ export class ExtensionsScanner extends Disposable { } } - private async scanLocalExtension(location: URI, type: ExtensionType): Promise { - const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, type, { includeInvalid: true }); - if (scannedExtension) { - return this.toLocalExtension(scannedExtension); + private async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise { + if (profileLocation) { + const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ profileLocation }); + const scannedExtension = scannedExtensions.find(e => this.uriIdentityService.extUri.isEqual(e.location, location)); + if (scannedExtension) { + return this.toLocalExtension(scannedExtension); + } + } else { + const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, type, { includeInvalid: true }); + if (scannedExtension) { + return this.toLocalExtension(scannedExtension); + } } throw new Error(nls.localize('cannot read', "Cannot read the extension from {0}", location.path)); } diff --git a/src/vs/platform/userDataSync/common/extensionsMerge.ts b/src/vs/platform/userDataSync/common/extensionsMerge.ts index f6caa270272..49c0d238271 100644 --- a/src/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/src/vs/platform/userDataSync/common/extensionsMerge.ts @@ -278,6 +278,11 @@ function areSame(fromExtension: ISyncExtension, toExtension: ISyncExtension, che return false; } + if (fromExtension.isApplicationScoped !== toExtension.isApplicationScoped) { + /* extension application scope has changed */ + return false; + } + if (checkInstalledProperty && fromExtension.installed !== toExtension.installed) { /* extension installed property changed */ return false; diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index aff6b642585..aa22d90107d 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -18,7 +18,7 @@ import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagemen import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension, ExtensionManagementError, ExtensionManagementErrorCode, IGalleryExtension, DISABLED_EXTENSIONS_STORAGE_PATH, EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT, EXTENSION_INSTALL_SYNC_CONTEXT, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; -import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, IExtensionIdentifier, isApplicationScopedExtension } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -375,8 +375,11 @@ export class LocalExtensionsProvider { const disabledExtensions = extensionEnablementService.getDisabledExtensions(); return installedExtensions .map(extension => { - const { identifier, isBuiltin, manifest, preRelease, pinned } = extension; + const { identifier, isBuiltin, manifest, preRelease, pinned, isApplicationScoped } = extension; const syncExntesion: ILocalSyncExtension = { identifier, preRelease, version: manifest.version, pinned: !!pinned }; + if (!isApplicationScopedExtension(manifest)) { + syncExntesion.isApplicationScoped = isApplicationScoped; + } if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) { syncExntesion.disabled = true; } @@ -481,6 +484,7 @@ export class LocalExtensionsProvider { installGivenVersion: e.pinned && !!e.version, installPreReleaseVersion: e.preRelease, profileLocation: profile.extensionsResource, + isApplicationScoped: e.isApplicationScoped, context: { [EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT]: true, [EXTENSION_INSTALL_SYNC_CONTEXT]: true } } }); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index d64a2dff8e8..245552ec53a 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -337,6 +337,7 @@ export interface ILocalSyncExtension { preRelease: boolean; disabled?: boolean; installed?: boolean; + isApplicationScoped?: boolean; state?: IStringDictionary; } @@ -347,6 +348,7 @@ export interface IRemoteSyncExtension { preRelease?: boolean; disabled?: boolean; installed?: boolean; + isApplicationScoped?: boolean; state?: IStringDictionary; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 52c03ecba67..a705f12a9a3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1431,6 +1431,24 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } }); + this.registerExtensionAction({ + id: 'workbench.extensions.action.toggleApplyToAllProfiles', + title: { value: localize('workbench.extensions.action.toggleApplyToAllProfiles', "Apply Extension to all Profiles"), original: `Apply Extension to all Profiles` }, + toggled: ContextKeyExpr.has('isApplicationScopedExtension'), + menu: { + id: MenuId.ExtensionContext, + group: '2_configure', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('isDefaultApplicationScopedExtension').negate()), + order: 4 + }, + run: async (accessor: ServicesAccessor, id: string) => { + const extension = this.extensionsWorkbenchService.local.find(e => areSameExtensions({ id }, e.identifier)); + if (extension) { + return this.extensionsWorkbenchService.toggleApplyExtensionToAllProfiles(extension); + } + } + }); + this.registerExtensionAction({ id: 'workbench.extensions.action.ignoreRecommendation', title: { value: localize('workbench.extensions.action.ignoreRecommendation', "Ignore Recommendation"), original: `Ignore Recommendation` }, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index b333b339afe..de295f7e156 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -50,7 +50,7 @@ import { getLocale } from 'vs/platform/languagePacks/common/languagePacks'; import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; interface IExtensionStateProvider { (extension: Extension): T; @@ -775,7 +775,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @ILocaleService private readonly localeService: ILocaleService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IFileService private readonly fileService: IFileService, - @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, ) { super(); const preferPreReleasesValue = configurationService.getValue('_extensions.preferPreReleases'); @@ -1622,9 +1622,10 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } async toggleApplyExtensionToAllProfiles(extension: IExtension): Promise { - if (extension.local && !isApplicationScopedExtension(extension.local.manifest)) { - await this.extensionManagementService.updateMetadata(extension.local, { isApplicationScoped: !extension.local.isApplicationScoped }, this.userDataProfilesService.defaultProfile.extensionsResource); + if (!extension.local || isApplicationScopedExtension(extension.local.manifest)) { + return; } + await this.extensionManagementService.toggleAppliationScope(extension.local, this.userDataProfileService.currentProfile.extensionsResource); } private isInstalledExtensionSynced(extension: ILocalExtension): boolean { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts index aa017e86fb3..305332829c9 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts @@ -89,6 +89,14 @@ export abstract class ProfileAwareExtensionManagementChannelClient extends BaseE return super.updateMetadata(local, metadata, await this.getProfileLocation(extensionsProfileResource)); } + override async toggleAppliationScope(local: ILocalExtension, fromProfileLocation: URI): Promise { + return super.toggleAppliationScope(local, await this.getProfileLocation(fromProfileLocation)); + } + + override async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { + return super.copyExtensions(await this.getProfileLocation(fromProfileLocation), await this.getProfileLocation(toProfileLocation)); + } + private async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { const previousProfileLocation = await this.getProfileLocation(e.previous.extensionsResource); const currentProfileLocation = await this.getProfileLocation(e.profile.extensionsResource); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 1b378e1b24a..8001988f5ea 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -560,6 +560,14 @@ export class ExtensionManagementService extends Disposable implements IWorkbench await Promise.allSettled(this.servers.map(server => server.extensionManagementService.cleanUp())); } + toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise { + const server = this.getServer(extension); + if (server) { + return server.extensionManagementService.toggleAppliationScope(extension, fromProfileLocation); + } + throw new Error('Not Supported'); + } + copyExtensions(from: URI, to: URI): Promise { if (this.extensionManagementServerService.remoteExtensionManagementServer) { throw new Error('Not Supported'); diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index d26b6ed0bdd..f100b353488 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -45,13 +45,9 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe get onProfileAwareDidUninstallExtension() { return super.onDidUninstallExtension; } override get onDidUninstallExtension() { return Event.filter(this.onProfileAwareDidUninstallExtension, e => this.filterEvent(e), this.disposables); } - private readonly _onDidChangeProfile = this._register(new Emitter<{ readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }>()); readonly onDidChangeProfile = this._onDidChangeProfile.event; - private readonly _onDidUpdateExtensionMetadata = this._register(new Emitter()); - override readonly onDidUpdateExtensionMetadata = this._onDidUpdateExtensionMetadata.event; - constructor( @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @ITelemetryService telemetryService: ITelemetryService, @@ -61,9 +57,9 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IProductService productService: IProductService, @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(extensionGalleryService, telemetryService, logService, productService, userDataProfilesService); + super(extensionGalleryService, telemetryService, uriIdentityService, logService, productService, userDataProfilesService); this._register(userDataProfileService.onDidChangeCurrentProfile(e => { if (!this.uriIdentityService.extUri.isEqual(e.previous.extensionsResource, e.profile.extensionsResource)) { e.join(this.whenProfileChanged(e)); @@ -123,6 +119,20 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe return this.install(location, { profileLocation }); } + protected async copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { + const target = await this.webExtensionsScannerService.scanExistingExtension(extension.location, extension.type, toProfileLocation); + const source = await this.webExtensionsScannerService.scanExistingExtension(extension.location, extension.type, fromProfileLocation); + metadata = { ...source?.metadata, ...metadata }; + + let scanned; + if (target) { + scanned = await this.webExtensionsScannerService.updateMetadata(extension, { ...target.metadata, ...metadata }, toProfileLocation); + } else { + scanned = await this.webExtensionsScannerService.addExtension(extension.location, metadata, toProfileLocation); + } + return toLocalExtension(scanned); + } + async installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise { const result: ILocalExtension[] = []; const extensionsToInstall = (await this.webExtensionsScannerService.scanUserExtensions(fromProfileLocation)) diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index 30e29d6784d..476712eaf1f 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -15,7 +15,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IExtensionManagementServer, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { Promises } from 'vs/base/common/async'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IFileService } from 'vs/platform/files/common/files'; @@ -25,7 +25,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -export class NativeRemoteExtensionManagementService extends RemoteExtensionManagementService implements IProfileAwareExtensionManagementService { +export class NativeRemoteExtensionManagementService extends RemoteExtensionManagementService { constructor( channel: IChannel, diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index c892739221a..b3507192720 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -2038,6 +2038,7 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens throw new Error('Method not implemented.'); } copyExtensions(): Promise { throw new Error('Not Supported'); } + toggleAppliationScope(): Promise { throw new Error('Not Supported'); } installExtensionsFromProfile(): Promise { throw new Error('Not Supported'); } whenProfileChanged(from: IUserDataProfile, to: IUserDataProfile): Promise { throw new Error('Not Supported'); } } From 581b805cd028889070ff5ce5dddbef410dd5a24d Mon Sep 17 00:00:00 2001 From: aamunger Date: Mon, 17 Jul 2023 11:07:04 -0700 Subject: [PATCH 180/826] whoopsie --- .../services/workingCopy/common/workingCopyBackupService.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts index 2486e142b71..f9ec56ad11a 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts @@ -134,9 +134,8 @@ export abstract class WorkingCopyBackupService implements IWorkingCopyBackupServ if (backupWorkspaceHome) { return new WorkingCopyBackupServiceImpl(backupWorkspaceHome, this.fileService, this.logService); } - else { - return new WorkingCopyBackupServiceImpl(URI.file('c:\\temp\\backup'), this.fileService, this.logService); - } + + return new InMemoryWorkingCopyBackupService(); } reinitialize(backupWorkspaceHome: URI | undefined): void { From 60fb53a583c52cb3f8da516b300eb0493ced1c24 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 17 Jul 2023 13:12:37 -0700 Subject: [PATCH 181/826] use () instead of symbol --- src/vs/editor/common/languages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 3a08292c8d6..246d32bddd6 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1179,7 +1179,7 @@ export const symbolKindNames: { [symbol: number]: string } = { * @internal */ export function getAriaLabelForSymbol(symbolName: string, kind: SymbolKind): string { - return localize('symbolAriaLabel', '{0} Symbol: {1}', symbolName, symbolKindNames[kind]); + return localize('symbolAriaLabel', '{0} ({1})', symbolName, symbolKindNames[kind]); } export const enum SymbolTag { From ac27273e482b3fc470315d372e8999271c750295 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 17 Jul 2023 14:12:10 -0700 Subject: [PATCH 182/826] Try to revoke tokens that are getting deleted Best effort. Fixes https://github.com/microsoft/vscode/issues/152055 --- .../extension-browser.webpack.config.js | 3 +- .../src/browser/buffer.ts | 8 +++ .../src/common/logger.ts | 3 + extensions/github-authentication/src/flows.ts | 3 +- .../github-authentication/src/github.ts | 1 + .../github-authentication/src/githubServer.ts | 62 ++++++++++++++++++- .../github-authentication/src/node/buffer.ts | 8 +++ 7 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 extensions/github-authentication/src/browser/buffer.ts create mode 100644 extensions/github-authentication/src/node/buffer.ts diff --git a/extensions/github-authentication/extension-browser.webpack.config.js b/extensions/github-authentication/extension-browser.webpack.config.js index 37b207eb056..f109e203569 100644 --- a/extensions/github-authentication/extension-browser.webpack.config.js +++ b/extensions/github-authentication/extension-browser.webpack.config.js @@ -21,7 +21,8 @@ module.exports = withBrowserDefaults({ 'uuid': path.resolve(__dirname, 'node_modules/uuid/dist/esm-browser/index.js'), './node/authServer': path.resolve(__dirname, 'src/browser/authServer'), './node/crypto': path.resolve(__dirname, 'src/browser/crypto'), - './node/fetch': path.resolve(__dirname, 'src/browser/fetch') + './node/fetch': path.resolve(__dirname, 'src/browser/fetch'), + './node/buffer': path.resolve(__dirname, 'src/browser/buffer'), } } }); diff --git a/extensions/github-authentication/src/browser/buffer.ts b/extensions/github-authentication/src/browser/buffer.ts new file mode 100644 index 00000000000..7192f5f104a --- /dev/null +++ b/extensions/github-authentication/src/browser/buffer.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function base64Encode(text: string): string { + return btoa(text); +} diff --git a/extensions/github-authentication/src/common/logger.ts b/extensions/github-authentication/src/common/logger.ts index 84225bd707f..cf90c4176a9 100644 --- a/extensions/github-authentication/src/common/logger.ts +++ b/extensions/github-authentication/src/common/logger.ts @@ -26,4 +26,7 @@ export class Log { this.output.error(message); } + public warn(message: string): void { + this.output.warn(message); + } } diff --git a/extensions/github-authentication/src/flows.ts b/extensions/github-authentication/src/flows.ts index f3f9277bdc1..5bc9d095385 100644 --- a/extensions/github-authentication/src/flows.ts +++ b/extensions/github-authentication/src/flows.ts @@ -201,7 +201,8 @@ const allFlows: IFlow[] = [ supportsGitHubEnterpriseServer: false, supportsHostedGitHubEnterprise: true, supportsRemoteExtensionHost: true, - supportsWebWorkerExtensionHost: true, + // Web worker can't open a port to listen for the redirect + supportsWebWorkerExtensionHost: false, // exchanging a code for a token requires a client secret supportsNoClientSecret: false, supportsSupportedClients: true, diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index c710cbe4f2f..71aa17bd5cc 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -363,6 +363,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid sessions.splice(sessionIndex, 1); await this.storeSessions(sessions); + await this._githubServer.logout(session); this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); } else { diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 7ac5cd8c577..0729c4c5077 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -12,6 +12,8 @@ import { crypto } from './node/crypto'; import { fetching } from './node/fetch'; import { ExtensionHost, GitHubTarget, getFlows } from './flows'; import { NETWORK_ERROR, USER_CANCELLATION_ERROR } from './common/errors'; +import { Config } from './config'; +import { base64Encode } from './node/buffer'; // This is the error message that we throw if the login was cancelled for any reason. Extensions // calling `getSession` can handle this error to know that the user cancelled the login. @@ -22,6 +24,7 @@ const REDIRECT_URL_INSIDERS = 'https://insiders.vscode.dev/redirect'; export interface IGitHubServer { login(scopes: string): Promise; + logout(session: vscode.AuthenticationSession): Promise; getUserInfo(token: string): Promise<{ id: string; accountName: string }>; sendAdditionalTelemetryInfo(session: vscode.AuthenticationSession): Promise; friendlyName: string; @@ -78,9 +81,14 @@ export class GitHubServer implements IGitHubServer { } // TODO@joaomoreno TODO@TylerLeonhardt + private _isNoCorsEnvironment: boolean | undefined; private async isNoCorsEnvironment(): Promise { + if (this._isNoCorsEnvironment !== undefined) { + return this._isNoCorsEnvironment; + } const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/dummy`)); - return (uri.scheme === 'https' && /^((insiders\.)?vscode|github)\./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority)); + this._isNoCorsEnvironment = (uri.scheme === 'https' && /^((insiders\.)?vscode|github)\./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority)); + return this._isNoCorsEnvironment; } public async login(scopes: string): Promise { @@ -144,6 +152,58 @@ export class GitHubServer implements IGitHubServer { throw new Error(userCancelled ? CANCELLATION_ERROR : 'No auth flow succeeded.'); } + public async logout(session: vscode.AuthenticationSession): Promise { + this._logger.trace(`Deleting session (${session.id}) from server...`); + + if (!Config.gitHubClientSecret) { + this._logger.warn('No client secret configured for GitHub authentication. The token has been deleted with best effort on this system, but we are unable to delete the token on server without the client secret.'); + return; + } + + // Only attempt to delete OAuth tokens. They are always prefixed with `gho_`. + // https://docs.github.com/en/rest/apps/oauth-applications#about-oauth-apps-and-oauth-authorizations-of-github-apps + if (!session.accessToken.startsWith('gho_')) { + this._logger.warn('The token being deleted is not an OAuth token. It has been deleted locally, but we cannot delete it on server.'); + return; + } + + if (!isSupportedTarget(this._type, this._ghesUri)) { + this._logger.trace('GitHub.com and GitHub hosted GitHub Enterprise are the only options that support deleting tokens on the server. Skipping.'); + return; + } + + const authHeader = 'Basic ' + base64Encode(`${Config.gitHubClientId}:${Config.gitHubClientSecret}`); + const uri = this.getServerUri(`/applications/${Config.gitHubClientId}/token`); + + try { + // Defined here: https://docs.github.com/en/rest/apps/oauth-applications?apiVersion=2022-11-28#delete-an-app-token + const result = await fetching(uri.toString(true), { + method: 'DELETE', + headers: { + Accept: 'application/vnd.github+json', + Authorization: authHeader, + 'X-GitHub-Api-Version': '2022-11-28', + 'User-Agent': `${vscode.env.appName} (${vscode.env.appHost})` + }, + body: JSON.stringify({ access_token: session.accessToken }), + }); + + if (result.status === 204) { + this._logger.trace(`Successfully deleted token from session (${session.id}) from server.`); + return; + } + + try { + const body = await result.text(); + throw new Error(body); + } catch (e) { + throw new Error(`${result.status} ${result.statusText}`); + } + } catch (e) { + this._logger.warn('Failed to delete token from server.' + e.message ?? e); + } + } + private getServerUri(path: string = '') { const apiUri = this.baseUri; // github.com and Hosted GitHub Enterprise instances diff --git a/extensions/github-authentication/src/node/buffer.ts b/extensions/github-authentication/src/node/buffer.ts new file mode 100644 index 00000000000..8e6208aa22a --- /dev/null +++ b/extensions/github-authentication/src/node/buffer.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function base64Encode(text: string): string { + return Buffer.from(text, 'binary').toString('base64'); +} From e77c16cf6b217f813f3caaa7528e31b15bff231a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 17 Jul 2023 14:22:44 -0700 Subject: [PATCH 183/826] tidy --- .../browser/accessibility.contribution.ts | 40 ++++++++++++------- .../accessibility/browser/accessibleView.ts | 1 + 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index d391b388ac6..9675240039f 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -22,7 +22,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover'; -import { withNullAsUndefined } from 'vs/base/common/types'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; registerAccessibilityConfiguration(); @@ -101,32 +101,44 @@ workbenchRegistry.registerWorkbenchContribution(EditorAccessibilityHelpContribut class HoverAccessibleViewContribution extends Disposable { static ID: 'hoverAccessibleViewContribution'; + private _options: IAccessibleViewOptions = { + ariaLabel: localize('hoverAccessibleView', "Hover Accessible View"), language: 'typescript', type: AccessibleViewType.View + }; constructor() { super(); this._register(AccessibleViewAction.addImplementation(90, 'hover', accessor => { const accessibleViewService = accessor.get(IAccessibleViewService); const codeEditorService = accessor.get(ICodeEditorService); const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); - if (!editor) { - return false; - } - const controller = ModesHoverController.get(editor); - const content = withNullAsUndefined(controller?.getWidgetContent()); - if (!controller || !content) { + const editorHoverContent = editor ? ModesHoverController.get(editor)?.getWidgetContent() ?? undefined : undefined; + if (!editorHoverContent) { return false; } accessibleViewService.show({ verbositySettingKey: 'hover', - provideContent() { return content; }, - onClose() { - controller.focus(); - }, - options: { - ariaLabel: localize('hoverAccessibleView', "Hover Accessible View"), language: 'typescript', type: AccessibleViewType.View - } + provideContent() { return editorHoverContent; }, + onClose() { }, + options: this._options }); return true; }, EditorContextKeys.hoverFocused)); + this._register(AccessibleViewAction.addImplementation(90, 'extension-hover', accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + const contextViewService = accessor.get(IContextViewService); + const contextViewElement = contextViewService.getContextViewElement(); + const extensionHoverContent = contextViewElement?.textContent ?? undefined; + if (contextViewElement.classList.contains('accessible-view-container') || !extensionHoverContent) { + // The accessible view, itself, uses the context view service to display the text. We don't want to read that. + return false; + } + accessibleViewService.show({ + verbositySettingKey: 'hover', + provideContent() { return extensionHoverContent; }, + onClose() { }, + options: this._options + }); + return true; + })); } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 8754d601121..f6bfc202d91 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -113,6 +113,7 @@ class AccessibleView extends Disposable { const delegate: IContextViewDelegate = { getAnchor: () => { return { x: (window.innerWidth / 2) - (DEFAULT.WIDTH / 2), y: DEFAULT.TOP }; }, render: (container) => { + container.classList.add('accessible-view-container'); return this._render(provider, container); }, onHide: () => { From 2d6e67f34eaa10910603d2923ffdd03fb53bbdea Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 17 Jul 2023 14:26:31 -0700 Subject: [PATCH 184/826] increase priority for editor since it has a context key --- .../contrib/accessibility/browser/accessibility.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 9675240039f..e84811c0e05 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -106,7 +106,7 @@ class HoverAccessibleViewContribution extends Disposable { }; constructor() { super(); - this._register(AccessibleViewAction.addImplementation(90, 'hover', accessor => { + this._register(AccessibleViewAction.addImplementation(95, 'hover', accessor => { const accessibleViewService = accessor.get(IAccessibleViewService); const codeEditorService = accessor.get(ICodeEditorService); const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); From 58718c99056fc6f516f77d96918cf99495de15a9 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 17 Jul 2023 14:56:09 -0700 Subject: [PATCH 185/826] Update pandas detection. (#188113) --- .../notebook/common/services/notebookSimpleWorker.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts index 1b8d4055433..b0b8c5153ab 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts @@ -286,10 +286,14 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable continue; } + if (cell.language !== 'python') { + continue; + } + const lineCount = cell.textBuffer.getLineCount(); const maxLineCount = Math.min(lineCount, 20); const range = new Range(1, 1, maxLineCount, cell.textBuffer.getLineLength(maxLineCount) + 1); - const searchParams = new SearchParams('import\\s*pandas', true, false, null); + const searchParams = new SearchParams('import\\s*pandas|from\\s*pandas', true, false, null); const searchData = searchParams.parseSearchRequest(); if (!searchData) { From 4390ebc406732e5b4eed22a1d7aa4fc0ef4a9460 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Mon, 17 Jul 2023 15:13:06 -0700 Subject: [PATCH 186/826] Format loader and add more comments (#188115) --- src/vs/base/worker/workerMain.ts | 2 +- src/vs/loader.js | 3652 +++++++++++++++--------------- 2 files changed, 1827 insertions(+), 1827 deletions(-) diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index 65df427d79e..ec18c5ca0f4 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -54,7 +54,7 @@ try { const func = ( trustedTypesPolicy - ? globalThis.eval(trustedTypesPolicy.createScript('', 'true')) + ? globalThis.eval(trustedTypesPolicy.createScript('', 'true')) // CodeQL [SM01632] fetch + eval is used on the web worker instead of importScripts if possible because importScripts is synchronous and we observed deadlocks on Safari : new Function('true') // CodeQL [SM01632] fetch + eval is used on the web worker instead of importScripts if possible because importScripts is synchronous and we observed deadlocks on Safari ); func.call(globalThis); diff --git a/src/vs/loader.js b/src/vs/loader.js index cebfe6da858..c2d38dfca0e 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -22,60 +22,60 @@ const _amdLoaderGlobal = this; const _commonjsGlobal = typeof global === 'object' ? global : {}; var AMDLoader; (function (AMDLoader) { - AMDLoader.global = _amdLoaderGlobal; - class Environment { - get isWindows() { - this._detect(); - return this._isWindows; - } - get isNode() { - this._detect(); - return this._isNode; - } - get isElectronRenderer() { - this._detect(); - return this._isElectronRenderer; - } - get isWebWorker() { - this._detect(); - return this._isWebWorker; - } - get isElectronNodeIntegrationWebWorker() { - this._detect(); - return this._isElectronNodeIntegrationWebWorker; - } - constructor() { - this._detected = false; - this._isWindows = false; - this._isNode = false; - this._isElectronRenderer = false; - this._isWebWorker = false; - this._isElectronNodeIntegrationWebWorker = false; - } - _detect() { - if (this._detected) { - return; - } - this._detected = true; - this._isWindows = Environment._isWindows(); - this._isNode = (typeof module !== 'undefined' && !!module.exports); - this._isElectronRenderer = (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.electron !== 'undefined' && process.type === 'renderer'); - this._isWebWorker = (typeof AMDLoader.global.importScripts === 'function'); - this._isElectronNodeIntegrationWebWorker = this._isWebWorker && (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.electron !== 'undefined' && process.type === 'worker'); - } - static _isWindows() { - if (typeof navigator !== 'undefined') { - if (navigator.userAgent && navigator.userAgent.indexOf('Windows') >= 0) { - return true; - } - } - if (typeof process !== 'undefined') { - return (process.platform === 'win32'); - } - return false; - } - } - AMDLoader.Environment = Environment; + AMDLoader.global = _amdLoaderGlobal; + class Environment { + get isWindows() { + this._detect(); + return this._isWindows; + } + get isNode() { + this._detect(); + return this._isNode; + } + get isElectronRenderer() { + this._detect(); + return this._isElectronRenderer; + } + get isWebWorker() { + this._detect(); + return this._isWebWorker; + } + get isElectronNodeIntegrationWebWorker() { + this._detect(); + return this._isElectronNodeIntegrationWebWorker; + } + constructor() { + this._detected = false; + this._isWindows = false; + this._isNode = false; + this._isElectronRenderer = false; + this._isWebWorker = false; + this._isElectronNodeIntegrationWebWorker = false; + } + _detect() { + if (this._detected) { + return; + } + this._detected = true; + this._isWindows = Environment._isWindows(); + this._isNode = (typeof module !== 'undefined' && !!module.exports); + this._isElectronRenderer = (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.electron !== 'undefined' && process.type === 'renderer'); + this._isWebWorker = (typeof AMDLoader.global.importScripts === 'function'); + this._isElectronNodeIntegrationWebWorker = this._isWebWorker && (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.electron !== 'undefined' && process.type === 'worker'); + } + static _isWindows() { + if (typeof navigator !== 'undefined') { + if (navigator.userAgent && navigator.userAgent.indexOf('Windows') >= 0) { + return true; + } + } + if (typeof process !== 'undefined') { + return (process.platform === 'win32'); + } + return false; + } + } + AMDLoader.Environment = Environment; })(AMDLoader || (AMDLoader = {})); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. @@ -83,36 +83,36 @@ var AMDLoader; *--------------------------------------------------------------------------------------------*/ var AMDLoader; (function (AMDLoader) { - class LoaderEvent { - constructor(type, detail, timestamp) { - this.type = type; - this.detail = detail; - this.timestamp = timestamp; - } - } - AMDLoader.LoaderEvent = LoaderEvent; - class LoaderEventRecorder { - constructor(loaderAvailableTimestamp) { - this._events = [new LoaderEvent(1 /* LoaderEventType.LoaderAvailable */, '', loaderAvailableTimestamp)]; - } - record(type, detail) { - this._events.push(new LoaderEvent(type, detail, AMDLoader.Utilities.getHighPerformanceTimestamp())); - } - getEvents() { - return this._events; - } - } - AMDLoader.LoaderEventRecorder = LoaderEventRecorder; - class NullLoaderEventRecorder { - record(type, detail) { - // Nothing to do - } - getEvents() { - return []; - } - } - NullLoaderEventRecorder.INSTANCE = new NullLoaderEventRecorder(); - AMDLoader.NullLoaderEventRecorder = NullLoaderEventRecorder; + class LoaderEvent { + constructor(type, detail, timestamp) { + this.type = type; + this.detail = detail; + this.timestamp = timestamp; + } + } + AMDLoader.LoaderEvent = LoaderEvent; + class LoaderEventRecorder { + constructor(loaderAvailableTimestamp) { + this._events = [new LoaderEvent(1 /* LoaderEventType.LoaderAvailable */, '', loaderAvailableTimestamp)]; + } + record(type, detail) { + this._events.push(new LoaderEvent(type, detail, AMDLoader.Utilities.getHighPerformanceTimestamp())); + } + getEvents() { + return this._events; + } + } + AMDLoader.LoaderEventRecorder = LoaderEventRecorder; + class NullLoaderEventRecorder { + record(type, detail) { + // Nothing to do + } + getEvents() { + return []; + } + } + NullLoaderEventRecorder.INSTANCE = new NullLoaderEventRecorder(); + AMDLoader.NullLoaderEventRecorder = NullLoaderEventRecorder; })(AMDLoader || (AMDLoader = {})); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. @@ -120,99 +120,99 @@ var AMDLoader; *--------------------------------------------------------------------------------------------*/ var AMDLoader; (function (AMDLoader) { - class Utilities { - /** - * This method does not take care of / vs \ - */ - static fileUriToFilePath(isWindows, uri) { - uri = decodeURI(uri).replace(/%23/g, '#'); - if (isWindows) { - if (/^file:\/\/\//.test(uri)) { - // This is a URI without a hostname => return only the path segment - return uri.substr(8); - } - if (/^file:\/\//.test(uri)) { - return uri.substr(5); - } - } - else { - if (/^file:\/\//.test(uri)) { - return uri.substr(7); - } - } - // Not sure... - return uri; - } - static startsWith(haystack, needle) { - return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; - } - static endsWith(haystack, needle) { - return haystack.length >= needle.length && haystack.substr(haystack.length - needle.length) === needle; - } - // only check for "?" before "#" to ensure that there is a real Query-String - static containsQueryString(url) { - return /^[^\#]*\?/gi.test(url); - } - /** - * Does `url` start with http:// or https:// or file:// or / ? - */ - static isAbsolutePath(url) { - return /^((http:\/\/)|(https:\/\/)|(file:\/\/)|(\/))/.test(url); - } - static forEachProperty(obj, callback) { - if (obj) { - let key; - for (key in obj) { - if (obj.hasOwnProperty(key)) { - callback(key, obj[key]); - } - } - } - } - static isEmpty(obj) { - let isEmpty = true; - Utilities.forEachProperty(obj, () => { - isEmpty = false; - }); - return isEmpty; - } - static recursiveClone(obj) { - if (!obj || typeof obj !== 'object' || obj instanceof RegExp) { - return obj; - } - if (!Array.isArray(obj) && Object.getPrototypeOf(obj) !== Object.prototype) { - // only clone "simple" objects - return obj; - } - let result = Array.isArray(obj) ? [] : {}; - Utilities.forEachProperty(obj, (key, value) => { - if (value && typeof value === 'object') { - result[key] = Utilities.recursiveClone(value); - } - else { - result[key] = value; - } - }); - return result; - } - static generateAnonymousModule() { - return '===anonymous' + (Utilities.NEXT_ANONYMOUS_ID++) + '==='; - } - static isAnonymousModule(id) { - return Utilities.startsWith(id, '===anonymous'); - } - static getHighPerformanceTimestamp() { - if (!this.PERFORMANCE_NOW_PROBED) { - this.PERFORMANCE_NOW_PROBED = true; - this.HAS_PERFORMANCE_NOW = (AMDLoader.global.performance && typeof AMDLoader.global.performance.now === 'function'); - } - return (this.HAS_PERFORMANCE_NOW ? AMDLoader.global.performance.now() : Date.now()); - } - } - Utilities.NEXT_ANONYMOUS_ID = 1; - Utilities.PERFORMANCE_NOW_PROBED = false; - Utilities.HAS_PERFORMANCE_NOW = false; - AMDLoader.Utilities = Utilities; + class Utilities { + /** + * This method does not take care of / vs \ + */ + static fileUriToFilePath(isWindows, uri) { + uri = decodeURI(uri).replace(/%23/g, '#'); + if (isWindows) { + if (/^file:\/\/\//.test(uri)) { + // This is a URI without a hostname => return only the path segment + return uri.substr(8); + } + if (/^file:\/\//.test(uri)) { + return uri.substr(5); + } + } + else { + if (/^file:\/\//.test(uri)) { + return uri.substr(7); + } + } + // Not sure... + return uri; + } + static startsWith(haystack, needle) { + return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; + } + static endsWith(haystack, needle) { + return haystack.length >= needle.length && haystack.substr(haystack.length - needle.length) === needle; + } + // only check for "?" before "#" to ensure that there is a real Query-String + static containsQueryString(url) { + return /^[^\#]*\?/gi.test(url); + } + /** + * Does `url` start with http:// or https:// or file:// or / ? + */ + static isAbsolutePath(url) { + return /^((http:\/\/)|(https:\/\/)|(file:\/\/)|(\/))/.test(url); + } + static forEachProperty(obj, callback) { + if (obj) { + let key; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + callback(key, obj[key]); + } + } + } + } + static isEmpty(obj) { + let isEmpty = true; + Utilities.forEachProperty(obj, () => { + isEmpty = false; + }); + return isEmpty; + } + static recursiveClone(obj) { + if (!obj || typeof obj !== 'object' || obj instanceof RegExp) { + return obj; + } + if (!Array.isArray(obj) && Object.getPrototypeOf(obj) !== Object.prototype) { + // only clone "simple" objects + return obj; + } + let result = Array.isArray(obj) ? [] : {}; + Utilities.forEachProperty(obj, (key, value) => { + if (value && typeof value === 'object') { + result[key] = Utilities.recursiveClone(value); + } + else { + result[key] = value; + } + }); + return result; + } + static generateAnonymousModule() { + return '===anonymous' + (Utilities.NEXT_ANONYMOUS_ID++) + '==='; + } + static isAnonymousModule(id) { + return Utilities.startsWith(id, '===anonymous'); + } + static getHighPerformanceTimestamp() { + if (!this.PERFORMANCE_NOW_PROBED) { + this.PERFORMANCE_NOW_PROBED = true; + this.HAS_PERFORMANCE_NOW = (AMDLoader.global.performance && typeof AMDLoader.global.performance.now === 'function'); + } + return (this.HAS_PERFORMANCE_NOW ? AMDLoader.global.performance.now() : Date.now()); + } + } + Utilities.NEXT_ANONYMOUS_ID = 1; + Utilities.PERFORMANCE_NOW_PROBED = false; + Utilities.HAS_PERFORMANCE_NOW = false; + AMDLoader.Utilities = Utilities; })(AMDLoader || (AMDLoader = {})); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. @@ -220,318 +220,318 @@ var AMDLoader; *--------------------------------------------------------------------------------------------*/ var AMDLoader; (function (AMDLoader) { - function ensureError(err) { - if (err instanceof Error) { - return err; - } - const result = new Error(err.message || String(err) || 'Unknown Error'); - if (err.stack) { - result.stack = err.stack; - } - return result; - } - AMDLoader.ensureError = ensureError; - ; - class ConfigurationOptionsUtil { - /** - * Ensure configuration options make sense - */ - static validateConfigurationOptions(options) { - function defaultOnError(err) { - if (err.phase === 'loading') { - console.error('Loading "' + err.moduleId + '" failed'); - console.error(err); - console.error('Here are the modules that depend on it:'); - console.error(err.neededBy); - return; - } - if (err.phase === 'factory') { - console.error('The factory function of "' + err.moduleId + '" has thrown an exception'); - console.error(err); - console.error('Here are the modules that depend on it:'); - console.error(err.neededBy); - return; - } - } - options = options || {}; - if (typeof options.baseUrl !== 'string') { - options.baseUrl = ''; - } - if (typeof options.isBuild !== 'boolean') { - options.isBuild = false; - } - if (typeof options.paths !== 'object') { - options.paths = {}; - } - if (typeof options.config !== 'object') { - options.config = {}; - } - if (typeof options.catchError === 'undefined') { - options.catchError = false; - } - if (typeof options.recordStats === 'undefined') { - options.recordStats = false; - } - if (typeof options.urlArgs !== 'string') { - options.urlArgs = ''; - } - if (typeof options.onError !== 'function') { - options.onError = defaultOnError; - } - if (!Array.isArray(options.ignoreDuplicateModules)) { - options.ignoreDuplicateModules = []; - } - if (options.baseUrl.length > 0) { - if (!AMDLoader.Utilities.endsWith(options.baseUrl, '/')) { - options.baseUrl += '/'; - } - } - if (typeof options.cspNonce !== 'string') { - options.cspNonce = ''; - } - if (typeof options.preferScriptTags === 'undefined') { - options.preferScriptTags = false; - } - if (options.nodeCachedData && typeof options.nodeCachedData === 'object') { - if (typeof options.nodeCachedData.seed !== 'string') { - options.nodeCachedData.seed = 'seed'; - } - if (typeof options.nodeCachedData.writeDelay !== 'number' || options.nodeCachedData.writeDelay < 0) { - options.nodeCachedData.writeDelay = 1000 * 7; - } - if (!options.nodeCachedData.path || typeof options.nodeCachedData.path !== 'string') { - const err = ensureError(new Error('INVALID cached data configuration, \'path\' MUST be set')); - err.phase = 'configuration'; - options.onError(err); - options.nodeCachedData = undefined; - } - } - return options; - } - static mergeConfigurationOptions(overwrite = null, base = null) { - let result = AMDLoader.Utilities.recursiveClone(base || {}); - // Merge known properties and overwrite the unknown ones - AMDLoader.Utilities.forEachProperty(overwrite, (key, value) => { - if (key === 'ignoreDuplicateModules' && typeof result.ignoreDuplicateModules !== 'undefined') { - result.ignoreDuplicateModules = result.ignoreDuplicateModules.concat(value); - } - else if (key === 'paths' && typeof result.paths !== 'undefined') { - AMDLoader.Utilities.forEachProperty(value, (key2, value2) => result.paths[key2] = value2); - } - else if (key === 'config' && typeof result.config !== 'undefined') { - AMDLoader.Utilities.forEachProperty(value, (key2, value2) => result.config[key2] = value2); - } - else { - result[key] = AMDLoader.Utilities.recursiveClone(value); - } - }); - return ConfigurationOptionsUtil.validateConfigurationOptions(result); - } - } - AMDLoader.ConfigurationOptionsUtil = ConfigurationOptionsUtil; - class Configuration { - constructor(env, options) { - this._env = env; - this.options = ConfigurationOptionsUtil.mergeConfigurationOptions(options); - this._createIgnoreDuplicateModulesMap(); - this._createSortedPathsRules(); - if (this.options.baseUrl === '') { - if (this.options.nodeRequire && this.options.nodeRequire.main && this.options.nodeRequire.main.filename && this._env.isNode) { - let nodeMain = this.options.nodeRequire.main.filename; - let dirnameIndex = Math.max(nodeMain.lastIndexOf('/'), nodeMain.lastIndexOf('\\')); - this.options.baseUrl = nodeMain.substring(0, dirnameIndex + 1); - } - } - } - _createIgnoreDuplicateModulesMap() { - // Build a map out of the ignoreDuplicateModules array - this.ignoreDuplicateModulesMap = {}; - for (let i = 0; i < this.options.ignoreDuplicateModules.length; i++) { - this.ignoreDuplicateModulesMap[this.options.ignoreDuplicateModules[i]] = true; - } - } - _createSortedPathsRules() { - // Create an array our of the paths rules, sorted descending by length to - // result in a more specific -> less specific order - this.sortedPathsRules = []; - AMDLoader.Utilities.forEachProperty(this.options.paths, (from, to) => { - if (!Array.isArray(to)) { - this.sortedPathsRules.push({ - from: from, - to: [to] - }); - } - else { - this.sortedPathsRules.push({ - from: from, - to: to - }); - } - }); - this.sortedPathsRules.sort((a, b) => { - return b.from.length - a.from.length; - }); - } - /** - * Clone current configuration and overwrite options selectively. - * @param options The selective options to overwrite with. - * @result A new configuration - */ - cloneAndMerge(options) { - return new Configuration(this._env, ConfigurationOptionsUtil.mergeConfigurationOptions(options, this.options)); - } - /** - * Get current options bag. Useful for passing it forward to plugins. - */ - getOptionsLiteral() { - return this.options; - } - _applyPaths(moduleId) { - let pathRule; - for (let i = 0, len = this.sortedPathsRules.length; i < len; i++) { - pathRule = this.sortedPathsRules[i]; - if (AMDLoader.Utilities.startsWith(moduleId, pathRule.from)) { - let result = []; - for (let j = 0, lenJ = pathRule.to.length; j < lenJ; j++) { - result.push(pathRule.to[j] + moduleId.substr(pathRule.from.length)); - } - return result; - } - } - return [moduleId]; - } - _addUrlArgsToUrl(url) { - if (AMDLoader.Utilities.containsQueryString(url)) { - return url + '&' + this.options.urlArgs; - } - else { - return url + '?' + this.options.urlArgs; - } - } - _addUrlArgsIfNecessaryToUrl(url) { - if (this.options.urlArgs) { - return this._addUrlArgsToUrl(url); - } - return url; - } - _addUrlArgsIfNecessaryToUrls(urls) { - if (this.options.urlArgs) { - for (let i = 0, len = urls.length; i < len; i++) { - urls[i] = this._addUrlArgsToUrl(urls[i]); - } - } - return urls; - } - /** - * Transform a module id to a location. Appends .js to module ids - */ - moduleIdToPaths(moduleId) { - if (this._env.isNode) { - const isNodeModule = (this.options.amdModulesPattern instanceof RegExp - && !this.options.amdModulesPattern.test(moduleId)); - if (isNodeModule) { - // This is a node module... - if (this.isBuild()) { - // ...and we are at build time, drop it - return ['empty:']; - } - else { - // ...and at runtime we create a `shortcut`-path - return ['node|' + moduleId]; - } - } - } - let result = moduleId; - let results; - if (!AMDLoader.Utilities.endsWith(result, '.js') && !AMDLoader.Utilities.isAbsolutePath(result)) { - results = this._applyPaths(result); - for (let i = 0, len = results.length; i < len; i++) { - if (this.isBuild() && results[i] === 'empty:') { - continue; - } - if (!AMDLoader.Utilities.isAbsolutePath(results[i])) { - results[i] = this.options.baseUrl + results[i]; - } - if (!AMDLoader.Utilities.endsWith(results[i], '.js') && !AMDLoader.Utilities.containsQueryString(results[i])) { - results[i] = results[i] + '.js'; - } - } - } - else { - if (!AMDLoader.Utilities.endsWith(result, '.js') && !AMDLoader.Utilities.containsQueryString(result)) { - result = result + '.js'; - } - results = [result]; - } - return this._addUrlArgsIfNecessaryToUrls(results); - } - /** - * Transform a module id or url to a location. - */ - requireToUrl(url) { - let result = url; - if (!AMDLoader.Utilities.isAbsolutePath(result)) { - result = this._applyPaths(result)[0]; - if (!AMDLoader.Utilities.isAbsolutePath(result)) { - result = this.options.baseUrl + result; - } - } - return this._addUrlArgsIfNecessaryToUrl(result); - } - /** - * Flag to indicate if current execution is as part of a build. - */ - isBuild() { - return this.options.isBuild; - } - shouldInvokeFactory(strModuleId) { - if (!this.options.isBuild) { - // outside of a build, all factories should be invoked - return true; - } - // during a build, only explicitly marked or anonymous modules get their factories invoked - if (AMDLoader.Utilities.isAnonymousModule(strModuleId)) { - return true; - } - if (this.options.buildForceInvokeFactory && this.options.buildForceInvokeFactory[strModuleId]) { - return true; - } - return false; - } - /** - * Test if module `moduleId` is expected to be defined multiple times - */ - isDuplicateMessageIgnoredFor(moduleId) { - return this.ignoreDuplicateModulesMap.hasOwnProperty(moduleId); - } - /** - * Get the configuration settings for the provided module id - */ - getConfigForModule(moduleId) { - if (this.options.config) { - return this.options.config[moduleId]; - } - } - /** - * Should errors be caught when executing module factories? - */ - shouldCatchError() { - return this.options.catchError; - } - /** - * Should statistics be recorded? - */ - shouldRecordStats() { - return this.options.recordStats; - } - /** - * Forward an error to the error handler. - */ - onError(err) { - this.options.onError(err); - } - } - AMDLoader.Configuration = Configuration; + function ensureError(err) { + if (err instanceof Error) { + return err; + } + const result = new Error(err.message || String(err) || 'Unknown Error'); + if (err.stack) { + result.stack = err.stack; + } + return result; + } + AMDLoader.ensureError = ensureError; + ; + class ConfigurationOptionsUtil { + /** + * Ensure configuration options make sense + */ + static validateConfigurationOptions(options) { + function defaultOnError(err) { + if (err.phase === 'loading') { + console.error('Loading "' + err.moduleId + '" failed'); + console.error(err); + console.error('Here are the modules that depend on it:'); + console.error(err.neededBy); + return; + } + if (err.phase === 'factory') { + console.error('The factory function of "' + err.moduleId + '" has thrown an exception'); + console.error(err); + console.error('Here are the modules that depend on it:'); + console.error(err.neededBy); + return; + } + } + options = options || {}; + if (typeof options.baseUrl !== 'string') { + options.baseUrl = ''; + } + if (typeof options.isBuild !== 'boolean') { + options.isBuild = false; + } + if (typeof options.paths !== 'object') { + options.paths = {}; + } + if (typeof options.config !== 'object') { + options.config = {}; + } + if (typeof options.catchError === 'undefined') { + options.catchError = false; + } + if (typeof options.recordStats === 'undefined') { + options.recordStats = false; + } + if (typeof options.urlArgs !== 'string') { + options.urlArgs = ''; + } + if (typeof options.onError !== 'function') { + options.onError = defaultOnError; + } + if (!Array.isArray(options.ignoreDuplicateModules)) { + options.ignoreDuplicateModules = []; + } + if (options.baseUrl.length > 0) { + if (!AMDLoader.Utilities.endsWith(options.baseUrl, '/')) { + options.baseUrl += '/'; + } + } + if (typeof options.cspNonce !== 'string') { + options.cspNonce = ''; + } + if (typeof options.preferScriptTags === 'undefined') { + options.preferScriptTags = false; + } + if (options.nodeCachedData && typeof options.nodeCachedData === 'object') { + if (typeof options.nodeCachedData.seed !== 'string') { + options.nodeCachedData.seed = 'seed'; + } + if (typeof options.nodeCachedData.writeDelay !== 'number' || options.nodeCachedData.writeDelay < 0) { + options.nodeCachedData.writeDelay = 1000 * 7; + } + if (!options.nodeCachedData.path || typeof options.nodeCachedData.path !== 'string') { + const err = ensureError(new Error('INVALID cached data configuration, \'path\' MUST be set')); + err.phase = 'configuration'; + options.onError(err); + options.nodeCachedData = undefined; + } + } + return options; + } + static mergeConfigurationOptions(overwrite = null, base = null) { + let result = AMDLoader.Utilities.recursiveClone(base || {}); + // Merge known properties and overwrite the unknown ones + AMDLoader.Utilities.forEachProperty(overwrite, (key, value) => { + if (key === 'ignoreDuplicateModules' && typeof result.ignoreDuplicateModules !== 'undefined') { + result.ignoreDuplicateModules = result.ignoreDuplicateModules.concat(value); + } + else if (key === 'paths' && typeof result.paths !== 'undefined') { + AMDLoader.Utilities.forEachProperty(value, (key2, value2) => result.paths[key2] = value2); + } + else if (key === 'config' && typeof result.config !== 'undefined') { + AMDLoader.Utilities.forEachProperty(value, (key2, value2) => result.config[key2] = value2); + } + else { + result[key] = AMDLoader.Utilities.recursiveClone(value); + } + }); + return ConfigurationOptionsUtil.validateConfigurationOptions(result); + } + } + AMDLoader.ConfigurationOptionsUtil = ConfigurationOptionsUtil; + class Configuration { + constructor(env, options) { + this._env = env; + this.options = ConfigurationOptionsUtil.mergeConfigurationOptions(options); + this._createIgnoreDuplicateModulesMap(); + this._createSortedPathsRules(); + if (this.options.baseUrl === '') { + if (this.options.nodeRequire && this.options.nodeRequire.main && this.options.nodeRequire.main.filename && this._env.isNode) { + let nodeMain = this.options.nodeRequire.main.filename; + let dirnameIndex = Math.max(nodeMain.lastIndexOf('/'), nodeMain.lastIndexOf('\\')); + this.options.baseUrl = nodeMain.substring(0, dirnameIndex + 1); + } + } + } + _createIgnoreDuplicateModulesMap() { + // Build a map out of the ignoreDuplicateModules array + this.ignoreDuplicateModulesMap = {}; + for (let i = 0; i < this.options.ignoreDuplicateModules.length; i++) { + this.ignoreDuplicateModulesMap[this.options.ignoreDuplicateModules[i]] = true; + } + } + _createSortedPathsRules() { + // Create an array our of the paths rules, sorted descending by length to + // result in a more specific -> less specific order + this.sortedPathsRules = []; + AMDLoader.Utilities.forEachProperty(this.options.paths, (from, to) => { + if (!Array.isArray(to)) { + this.sortedPathsRules.push({ + from: from, + to: [to] + }); + } + else { + this.sortedPathsRules.push({ + from: from, + to: to + }); + } + }); + this.sortedPathsRules.sort((a, b) => { + return b.from.length - a.from.length; + }); + } + /** + * Clone current configuration and overwrite options selectively. + * @param options The selective options to overwrite with. + * @result A new configuration + */ + cloneAndMerge(options) { + return new Configuration(this._env, ConfigurationOptionsUtil.mergeConfigurationOptions(options, this.options)); + } + /** + * Get current options bag. Useful for passing it forward to plugins. + */ + getOptionsLiteral() { + return this.options; + } + _applyPaths(moduleId) { + let pathRule; + for (let i = 0, len = this.sortedPathsRules.length; i < len; i++) { + pathRule = this.sortedPathsRules[i]; + if (AMDLoader.Utilities.startsWith(moduleId, pathRule.from)) { + let result = []; + for (let j = 0, lenJ = pathRule.to.length; j < lenJ; j++) { + result.push(pathRule.to[j] + moduleId.substr(pathRule.from.length)); + } + return result; + } + } + return [moduleId]; + } + _addUrlArgsToUrl(url) { + if (AMDLoader.Utilities.containsQueryString(url)) { + return url + '&' + this.options.urlArgs; + } + else { + return url + '?' + this.options.urlArgs; + } + } + _addUrlArgsIfNecessaryToUrl(url) { + if (this.options.urlArgs) { + return this._addUrlArgsToUrl(url); + } + return url; + } + _addUrlArgsIfNecessaryToUrls(urls) { + if (this.options.urlArgs) { + for (let i = 0, len = urls.length; i < len; i++) { + urls[i] = this._addUrlArgsToUrl(urls[i]); + } + } + return urls; + } + /** + * Transform a module id to a location. Appends .js to module ids + */ + moduleIdToPaths(moduleId) { + if (this._env.isNode) { + const isNodeModule = (this.options.amdModulesPattern instanceof RegExp + && !this.options.amdModulesPattern.test(moduleId)); + if (isNodeModule) { + // This is a node module... + if (this.isBuild()) { + // ...and we are at build time, drop it + return ['empty:']; + } + else { + // ...and at runtime we create a `shortcut`-path + return ['node|' + moduleId]; + } + } + } + let result = moduleId; + let results; + if (!AMDLoader.Utilities.endsWith(result, '.js') && !AMDLoader.Utilities.isAbsolutePath(result)) { + results = this._applyPaths(result); + for (let i = 0, len = results.length; i < len; i++) { + if (this.isBuild() && results[i] === 'empty:') { + continue; + } + if (!AMDLoader.Utilities.isAbsolutePath(results[i])) { + results[i] = this.options.baseUrl + results[i]; + } + if (!AMDLoader.Utilities.endsWith(results[i], '.js') && !AMDLoader.Utilities.containsQueryString(results[i])) { + results[i] = results[i] + '.js'; + } + } + } + else { + if (!AMDLoader.Utilities.endsWith(result, '.js') && !AMDLoader.Utilities.containsQueryString(result)) { + result = result + '.js'; + } + results = [result]; + } + return this._addUrlArgsIfNecessaryToUrls(results); + } + /** + * Transform a module id or url to a location. + */ + requireToUrl(url) { + let result = url; + if (!AMDLoader.Utilities.isAbsolutePath(result)) { + result = this._applyPaths(result)[0]; + if (!AMDLoader.Utilities.isAbsolutePath(result)) { + result = this.options.baseUrl + result; + } + } + return this._addUrlArgsIfNecessaryToUrl(result); + } + /** + * Flag to indicate if current execution is as part of a build. + */ + isBuild() { + return this.options.isBuild; + } + shouldInvokeFactory(strModuleId) { + if (!this.options.isBuild) { + // outside of a build, all factories should be invoked + return true; + } + // during a build, only explicitly marked or anonymous modules get their factories invoked + if (AMDLoader.Utilities.isAnonymousModule(strModuleId)) { + return true; + } + if (this.options.buildForceInvokeFactory && this.options.buildForceInvokeFactory[strModuleId]) { + return true; + } + return false; + } + /** + * Test if module `moduleId` is expected to be defined multiple times + */ + isDuplicateMessageIgnoredFor(moduleId) { + return this.ignoreDuplicateModulesMap.hasOwnProperty(moduleId); + } + /** + * Get the configuration settings for the provided module id + */ + getConfigForModule(moduleId) { + if (this.options.config) { + return this.options.config[moduleId]; + } + } + /** + * Should errors be caught when executing module factories? + */ + shouldCatchError() { + return this.options.catchError; + } + /** + * Should statistics be recorded? + */ + shouldRecordStats() { + return this.options.recordStats; + } + /** + * Forward an error to the error handler. + */ + onError(err) { + this.options.onError(err); + } + } + AMDLoader.Configuration = Configuration; })(AMDLoader || (AMDLoader = {})); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. @@ -539,503 +539,503 @@ var AMDLoader; *--------------------------------------------------------------------------------------------*/ var AMDLoader; (function (AMDLoader) { - /** - * Load `scriptSrc` only once (avoid multiple