diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 90e97b22599..3e48f05b3de 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -157,7 +157,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:daviddossett -author:bhavyaus -author:justschen -author:benibenj" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:*out-of-scope -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:daviddossett -author:bhavyaus -author:justschen -author:benibenj -author:luabud" }, { "kind": 1, @@ -167,7 +167,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -label:*not-reproducible" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -label:*not-reproducible -label:*out-of-scope" }, { "kind": 1, diff --git a/src/vs/editor/common/services/textResourceConfigurationService.ts b/src/vs/editor/common/services/textResourceConfigurationService.ts index 89acdd09e8b..39390cbd033 100644 --- a/src/vs/editor/common/services/textResourceConfigurationService.ts +++ b/src/vs/editor/common/services/textResourceConfigurationService.ts @@ -43,26 +43,8 @@ export class TextResourceConfigurationService extends Disposable implements ITex if (configurationTarget === undefined) { configurationTarget = this.deriveConfigurationTarget(configurationValue, language); } - switch (configurationTarget) { - case ConfigurationTarget.MEMORY: - return this._updateValue(key, value, configurationTarget, configurationValue.memory?.override, resource, language); - case ConfigurationTarget.WORKSPACE_FOLDER: - return this._updateValue(key, value, configurationTarget, configurationValue.workspaceFolder?.override, resource, language); - case ConfigurationTarget.WORKSPACE: - return this._updateValue(key, value, configurationTarget, configurationValue.workspace?.override, resource, language); - case ConfigurationTarget.USER_REMOTE: - return this._updateValue(key, value, configurationTarget, configurationValue.userRemote?.override, resource, language); - default: - return this._updateValue(key, value, configurationTarget, configurationValue.userLocal?.override, resource, language); - } - } - - private _updateValue(key: string, value: any, configurationTarget: ConfigurationTarget, overriddenValue: any | undefined, resource: URI, language: string | null): Promise { - if (language && overriddenValue !== undefined) { - return this.configurationService.updateValue(key, value, { resource, overrideIdentifier: language }, configurationTarget); - } else { - return this.configurationService.updateValue(key, value, { resource }, configurationTarget); - } + const overrideIdentifier = language && configurationValue.overrideIdentifiers?.includes(language) ? language : undefined; + return this.configurationService.updateValue(key, value, { resource, overrideIdentifier }, configurationTarget); } private deriveConfigurationTarget(configurationValue: IConfigurationValue, language: string | null): ConfigurationTarget { diff --git a/src/vs/editor/contrib/codeAction/browser/codeAction.ts b/src/vs/editor/contrib/codeAction/browser/codeAction.ts index c665dd778fe..048d3cb55c9 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeAction.ts @@ -273,7 +273,7 @@ export async function applyCodeAction( codeActionKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind (refactor, quickfix) of the applied code action' }; codeActionIsPreferred: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Was the code action marked as being a preferred action?' }; reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to trigger apply code action.' }; - owner: 'mjbvz'; + owner: 'justschen'; comment: 'Event used to gain insights into which code actions are being triggered'; }; diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index c325594629d..2d7972c19ee 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -39,6 +39,7 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic import { CodeActionAutoApply, CodeActionFilter, CodeActionItem, CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/common/types'; import { CodeActionModel, CodeActionsState } from 'vs/editor/contrib/codeAction/browser/codeActionModel'; import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; interface IActionShowOptions { @@ -79,6 +80,7 @@ export class CodeActionController extends Disposable implements IEditorContribut @IConfigurationService private readonly _configurationService: IConfigurationService, @IActionWidgetService private readonly _actionWidgetService: IActionWidgetService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { super(); @@ -105,6 +107,29 @@ export class CodeActionController extends Disposable implements IEditorContribut } private async showCodeActionsFromLightbulb(actions: CodeActionSet, at: IAnchor | IPosition): Promise { + + // Telemetry for showing code actions from lightbulb. Shows us how often it was clicked. + type ShowCodeActionListEvent = { + codeActionListLength: number; + codeActions: string[]; + codeActionProviders: string[]; + }; + + type ShowListEventClassification = { + codeActionListLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The length of the code action list from the lightbulb widget.' }; + codeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The title of code actions in this menu.' }; + codeActionProviders: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The provider of code actions in this menu.' }; + owner: 'justschen'; + comment: 'Event used to gain insights into what code actions are being shown'; + }; + + this._telemetryService.publicLog2('codeAction.showCodeActionsFromLightbulb', { + codeActionListLength: actions.validActions.length, + codeActions: actions.validActions.map(action => action.action.title), + codeActionProviders: actions.validActions.map(action => action.provider?.displayName ?? ''), + }); + + if (actions.allAIFixes && actions.validActions.length === 1) { const actionItem = actions.validActions[0]; const command = actionItem.action.command; @@ -280,13 +305,32 @@ export class CodeActionController extends Disposable implements IEditorContribut const delegate: IActionListDelegate = { onSelect: async (action: CodeActionItem, preview?: boolean) => { - this._applyCodeAction(action, /* retrigger */ true, !!preview, ApplyCodeActionReason.FromCodeActions); - this._actionWidgetService.hide(); + this._applyCodeAction(action, /* retrigger */ true, !!preview, options.fromLightbulb ? ApplyCodeActionReason.FromAILightbulb : ApplyCodeActionReason.FromCodeActions); + this._actionWidgetService.hide(false); currentDecorations.clear(); }, - onHide: () => { + onHide: (didCancel?) => { this._editor?.focus(); currentDecorations.clear(); + // Telemetry for showing code actions here. only log on `showLightbulb`. Logs when code action list is quit out. + if (options.fromLightbulb && didCancel !== undefined) { + type ShowCodeActionListEvent = { + codeActionListLength: number; + didCancel: boolean; + }; + + type ShowListEventClassification = { + codeActionListLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The length of the code action list when quit out. Can be from any code action menu.' }; + didCancel: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the code action was cancelled or selected.' }; + owner: 'justschen'; + comment: 'Event used to gain insights into how many valid code actions are being shown'; + }; + + this._telemetryService.publicLog2('codeAction.showCodeActionList.onHide', { + codeActionListLength: actions.validActions.length, + didCancel: didCancel, + }); + } }, onHover: async (action: CodeActionItem, token: CancellationToken) => { if (token.isCancellationRequested) { diff --git a/src/vs/editor/contrib/hover/browser/hoverController.ts b/src/vs/editor/contrib/hover/browser/hoverController.ts index c4b56e02c7f..890aaf673ed 100644 --- a/src/vs/editor/contrib/hover/browser/hoverController.ts +++ b/src/vs/editor/contrib/hover/browser/hoverController.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SHOW_OR_FOCUS_HOVER_ACTION_ID } from 'vs/editor/contrib/hover/browser/hoverActionIds'; +import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID, SHOW_OR_FOCUS_HOVER_ACTION_ID } from 'vs/editor/contrib/hover/browser/hoverActionIds'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -332,10 +332,12 @@ export class HoverController extends Disposable implements IEditorContribution { // 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 = ( + const shouldKeepHoverVisible = ( resolvedKeyboardEvent.kind === ResultKind.MoreChordsNeeded || (resolvedKeyboardEvent.kind === ResultKind.KbFound - && resolvedKeyboardEvent.commandId === SHOW_OR_FOCUS_HOVER_ACTION_ID + && (resolvedKeyboardEvent.commandId === SHOW_OR_FOCUS_HOVER_ACTION_ID + || resolvedKeyboardEvent.commandId === INCREASE_HOVER_VERBOSITY_ACTION_ID + || resolvedKeyboardEvent.commandId === DECREASE_HOVER_VERBOSITY_ACTION_ID) && this._contentWidget?.isVisible ) ); @@ -345,7 +347,7 @@ export class HoverController extends Disposable implements IEditorContribution { || e.keyCode === KeyCode.Alt || e.keyCode === KeyCode.Meta || e.keyCode === KeyCode.Shift - || mightTriggerFocus + || shouldKeepHoverVisible ) { // Do not hide hover when a modifier key is pressed return; diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 6296625880f..ef5783aa500 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -359,6 +359,10 @@ class RenameController implements IEditorContribution { timeBeforeFirstInputFieldEdit?: number; /** provided only if kind = 'accepted' */ wantsPreview?: boolean; + /** provided only if kind = 'accepted' */ + nRenameSuggestionsInvocations?: number; + /** provided only if kind = 'accepted' */ + hadAutomaticRenameSuggestionsInvocation?: boolean; }; type RenameInvokedClassification = { @@ -373,6 +377,8 @@ class RenameController implements IEditorContribution { nRenameSuggestions?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of rename suggestions user has got' }; timeBeforeFirstInputFieldEdit?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Milliseconds before user edits the input field for the first time' }; wantsPreview?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If user wanted preview.' }; + nRenameSuggestionsInvocations?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of times rename suggestions were invoked' }; + hadAutomaticRenameSuggestionsInvocation?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether rename suggestions were invoked automatically' }; }; const value: RenameInvokedEvent = @@ -391,6 +397,8 @@ class RenameController implements IEditorContribution { nRenameSuggestions: inputFieldResult.stats.nRenameSuggestions, timeBeforeFirstInputFieldEdit: inputFieldResult.stats.timeBeforeFirstInputFieldEdit, wantsPreview: inputFieldResult.wantsPreview, + nRenameSuggestionsInvocations: inputFieldResult.stats.nRenameSuggestionsInvocations, + hadAutomaticRenameSuggestionsInvocation: inputFieldResult.stats.hadAutomaticRenameSuggestionsInvocation, }; this._telemetryService.publicLog2('renameInvokedEvent', value); diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index 8be9eb5af53..03257f28aa3 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -76,6 +76,8 @@ export type RenameWidgetStats = { nRenameSuggestions: number; source: NewNameSource; timeBeforeFirstInputFieldEdit: number | undefined; + nRenameSuggestionsInvocations: number; + hadAutomaticRenameSuggestionsInvocation: boolean; }; export type RenameWidgetResult = { @@ -141,6 +143,10 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable */ private _timeBeforeFirstInputFieldEdit: number | undefined; + private _nRenameSuggestionsInvocations: number; + + private _hadAutomaticRenameSuggestionsInvocation: boolean; + private _renameCandidateProvidersCts: CancellationTokenSource | undefined; private _renameCts: CancellationTokenSource | undefined; @@ -159,6 +165,10 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable this._isEditingRenameCandidate = false; + this._nRenameSuggestionsInvocations = 0; + + this._hadAutomaticRenameSuggestionsInvocation = false; + this._candidates = new Set(); this._beforeFirstInputFieldEditSW = new StopWatch(); @@ -387,6 +397,10 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable const disposeOnDone = new DisposableStore(); + this._nRenameSuggestionsInvocations = 0; + + this._hadAutomaticRenameSuggestionsInvocation = false; + if (requestRenameCandidates === undefined) { this._inputWithButton.button.style.display = 'none'; } else { @@ -497,6 +511,8 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable source, nRenameSuggestions, timeBeforeFirstInputFieldEdit: this._timeBeforeFirstInputFieldEdit, + nRenameSuggestionsInvocations: this._nRenameSuggestionsInvocations, + hadAutomaticRenameSuggestionsInvocation: this._hadAutomaticRenameSuggestionsInvocation, } }); }; @@ -533,6 +549,12 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable return; } + if (!isManuallyTriggered) { + this._hadAutomaticRenameSuggestionsInvocation = true; + } + + this._nRenameSuggestionsInvocations += 1; + this._inputWithButton.setStopButton(); this._updateRenameCandidates(candidates, currentName, this._renameCts.token); diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 10a329134bc..31ee2f5038b 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -55,7 +55,7 @@ import { ConsoleLogger, ILogService } from 'vs/platform/log/common/log'; import { IWorkspaceTrustManagementService, IWorkspaceTrustTransitionParticipant, IWorkspaceTrustUriInfo } from 'vs/platform/workspace/common/workspaceTrust'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { IContextMenuService, IContextViewDelegate, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService, IContextViewDelegate, IContextViewService, IOpenContextView } from 'vs/platform/contextview/browser/contextView'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; import { LanguageService } from 'vs/editor/common/services/languageService'; import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; @@ -989,7 +989,7 @@ class StandaloneContextViewService extends ContextViewService { super(layoutService); } - override showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IDisposable { + override showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IOpenContextView { if (!container) { const codeEditor = this._codeEditorService.getFocusedCodeEditor() || this._codeEditorService.getActiveCodeEditor(); if (codeEditor) { diff --git a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts index a40b185fd28..11410512dc6 100644 --- a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts +++ b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts @@ -44,13 +44,13 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes without target and overrides when no language is defined', async () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: undefined }, ConfigurationTarget.USER_LOCAL]); }); test('updateValue writes with target and without overrides when no language is defined', async () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.USER_LOCAL); - assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: undefined }, ConfigurationTarget.USER_LOCAL]); }); test('updateValue writes into given memory target without overrides', async () => { @@ -63,7 +63,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.MEMORY); - assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.MEMORY]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: undefined }, ConfigurationTarget.MEMORY]); }); test('updateValue writes into given workspace target without overrides', async () => { @@ -76,7 +76,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.WORKSPACE); - assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: undefined }, ConfigurationTarget.WORKSPACE]); }); test('updateValue writes into given user target without overrides', async () => { @@ -89,7 +89,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.USER); - assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: undefined }, ConfigurationTarget.USER]); }); test('updateValue writes into given workspace folder target with overrides', async () => { @@ -98,6 +98,7 @@ suite('TextResourceConfigurationService - Update', () => { default: { value: '1' }, userLocal: { value: '2' }, workspaceFolder: { value: '2', override: '1' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -115,7 +116,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE_FOLDER]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: undefined }, ConfigurationTarget.WORKSPACE_FOLDER]); }); test('updateValue writes into derived workspace folder target with overrides', async () => { @@ -125,6 +126,7 @@ suite('TextResourceConfigurationService - Update', () => { userLocal: { value: '2' }, workspace: { value: '2', override: '1' }, workspaceFolder: { value: '2', override: '2' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -142,7 +144,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: undefined }, ConfigurationTarget.WORKSPACE]); }); test('updateValue writes into derived workspace target with overrides', async () => { @@ -151,6 +153,7 @@ suite('TextResourceConfigurationService - Update', () => { default: { value: '1' }, userLocal: { value: '2' }, workspace: { value: '2', override: '2' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -165,6 +168,7 @@ suite('TextResourceConfigurationService - Update', () => { userLocal: { value: '2' }, workspace: { value: '2', override: '2' }, workspaceFolder: { value: '2' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -182,7 +186,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_REMOTE]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: undefined }, ConfigurationTarget.USER_REMOTE]); }); test('updateValue writes into derived user remote target with overrides', async () => { @@ -191,6 +195,7 @@ suite('TextResourceConfigurationService - Update', () => { default: { value: '1' }, userLocal: { value: '2' }, userRemote: { value: '2', override: '3' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -204,7 +209,8 @@ suite('TextResourceConfigurationService - Update', () => { default: { value: '1' }, userLocal: { value: '2' }, userRemote: { value: '2', override: '3' }, - workspace: { value: '3' } + workspace: { value: '3' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -219,7 +225,8 @@ suite('TextResourceConfigurationService - Update', () => { userLocal: { value: '2', override: '1' }, userRemote: { value: '2', override: '3' }, workspace: { value: '3' }, - workspaceFolder: { value: '3' } + workspaceFolder: { value: '3' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -236,7 +243,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: undefined }, ConfigurationTarget.USER_LOCAL]); }); test('updateValue writes into derived user target with overrides', async () => { @@ -244,6 +251,7 @@ suite('TextResourceConfigurationService - Update', () => { configurationValue = { default: { value: '1' }, userLocal: { value: '2', override: '3' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -256,7 +264,8 @@ suite('TextResourceConfigurationService - Update', () => { configurationValue = { default: { value: '1' }, userLocal: { value: '2', override: '3' }, - userRemote: { value: '3' } + userRemote: { value: '3' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -269,7 +278,8 @@ suite('TextResourceConfigurationService - Update', () => { configurationValue = { default: { value: '1' }, userLocal: { value: '2', override: '3' }, - workspaceValue: { value: '3' } + workspaceValue: { value: '3' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -283,7 +293,21 @@ suite('TextResourceConfigurationService - Update', () => { default: { value: '1', override: '3' }, userLocal: { value: '2', override: '3' }, userRemote: { value: '3' }, - workspaceFolderValue: { value: '3' } + workspaceFolderValue: { value: '3' }, + overrideIdentifiers: [language] + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', '2'); + assert.deepStrictEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); + }); + + test('updateValue writes into derived user target when overridden in default and not in user', async () => { + language = 'a'; + configurationValue = { + default: { value: '1', override: '3' }, + userLocal: { value: '2' }, + overrideIdentifiers: [language] }; const resource = URI.file('someFile'); @@ -299,7 +323,7 @@ suite('TextResourceConfigurationService - Update', () => { const resource = URI.file('someFile'); await testObject.updateValue(resource, 'a', 'b'); - assert.deepStrictEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + assert.deepStrictEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: undefined }, ConfigurationTarget.USER_LOCAL]); }); }); diff --git a/src/vs/platform/actionWidget/browser/actionWidget.ts b/src/vs/platform/actionWidget/browser/actionWidget.ts index d9fb3fbb3f6..e7b26382eae 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.ts +++ b/src/vs/platform/actionWidget/browser/actionWidget.ts @@ -36,7 +36,7 @@ export interface IActionWidgetService { show(user: string, supportsPreview: boolean, items: readonly IActionListItem[], delegate: IActionListDelegate, anchor: IAnchor, container: HTMLElement | undefined, actionBarActions?: readonly IAction[]): void; - hide(): void; + hide(didCancel?: boolean): void; readonly isVisible: boolean; } @@ -87,8 +87,8 @@ class ActionWidgetService extends Disposable implements IActionWidgetService { this._list?.value?.focusNext(); } - hide() { - this._list.value?.hide(); + hide(didCancel?: boolean) { + this._list.value?.hide(didCancel); this._list.clear(); } @@ -139,7 +139,7 @@ class ActionWidgetService extends Disposable implements IActionWidgetService { widget.style.width = `${width}px`; const focusTracker = renderDisposables.add(dom.trackFocus(element)); - renderDisposables.add(focusTracker.onDidBlur(() => this.hide())); + renderDisposables.add(focusTracker.onDidBlur(() => this.hide(true))); return renderDisposables; } @@ -179,7 +179,7 @@ registerAction2(class extends Action2 { } run(accessor: ServicesAccessor): void { - accessor.get(IActionWidgetService).hide(); + accessor.get(IActionWidgetService).hide(true); } }); diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index 87c58811d8d..7bd31c59886 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -19,7 +19,7 @@ export interface IContextViewService extends IContextViewProvider { readonly _serviceBrand: undefined; - showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IDisposable; + showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IOpenContextView; hideContextView(data?: any): void; getContextViewElement(): HTMLElement; layout(): void; @@ -48,6 +48,10 @@ export interface IContextViewDelegate { layer?: number; // Default: 0 } +export interface IOpenContextView { + close: () => void; +} + export const IContextMenuService = createDecorator('contextMenuService'); export interface IContextMenuService { diff --git a/src/vs/platform/contextview/browser/contextViewService.ts b/src/vs/platform/contextview/browser/contextViewService.ts index 372e162a638..dee2144251e 100644 --- a/src/vs/platform/contextview/browser/contextViewService.ts +++ b/src/vs/platform/contextview/browser/contextViewService.ts @@ -4,15 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { ContextView, ContextViewDOMPosition, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { Disposable, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { IContextViewDelegate, IContextViewService } from './contextView'; +import { IContextViewDelegate, IContextViewService, IOpenContextView } from './contextView'; import { getWindow } from 'vs/base/browser/dom'; - export class ContextViewHandler extends Disposable implements IContextViewProvider { - private readonly currentViewDisposable = this._register(new MutableDisposable()); + private openContextView: IOpenContextView | undefined; protected readonly contextView = this._register(new ContextView(this.layoutService.mainContainer, ContextViewDOMPosition.ABSOLUTE)); constructor( @@ -26,7 +25,7 @@ export class ContextViewHandler extends Disposable implements IContextViewProvid // ContextView - showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IDisposable { + showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IOpenContextView { let domPosition: ContextViewDOMPosition; if (container) { if (container === this.layoutService.getContainer(getWindow(container))) { @@ -44,14 +43,16 @@ export class ContextViewHandler extends Disposable implements IContextViewProvid this.contextView.show(delegate); - const disposable = toDisposable(() => { - if (this.currentViewDisposable === disposable) { - this.hideContextView(); + const openContextView: IOpenContextView = { + close: () => { + if (this.openContextView === openContextView) { + this.hideContextView(); + } } - }); + }; - this.currentViewDisposable.value = disposable; - return disposable; + this.openContextView = openContextView; + return openContextView; } layout(): void { @@ -60,6 +61,7 @@ export class ContextViewHandler extends Disposable implements IContextViewProvid hideContextView(data?: any): void { this.contextView.hide(data); + this.openContextView = undefined; } } diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index a2040014745..9241f4170ab 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -146,7 +146,7 @@ export class ExtensionManagementCLI { if (areSameExtensions(oldVersion.identifier, newVersion.identifier) && gt(newVersion.version, oldVersion.manifest.version)) { extensionsToUpdate.push({ extension: newVersion, - options: { operation: InstallOperation.Update, installPreReleaseVersion: oldVersion.isPreReleaseVersion } + options: { operation: InstallOperation.Update, installPreReleaseVersion: oldVersion.preRelease, profileLocation } }); } } diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index d8af9368dcd..a30b4026dbb 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -138,7 +138,8 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA description: dynamicProps.description, extensionId: extension, extensionDisplayName: extensionDescription?.displayName ?? extension.value, - extensionPublisher: extensionDescription?.publisherDisplayName ?? extension.value, + extensionPublisherId: extensionDescription?.publisher ?? '', // extensionDescription _should_ be present at this point, since this extension is active and registering agents + extensionPublisherDisplayName: extensionDescription?.publisherDisplayName, metadata: revive(metadata), slashCommands: [], locations: [ChatAgentLocation.Panel] // TODO all dynamic participants are panel only? diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 2ea75f8b775..ba5e9aa7c50 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -355,7 +355,12 @@ class ExtHostTreeView extends Disposable { this.dataProvider = options.treeDataProvider; this.dndController = options.dragAndDropController; if (this.dataProvider.onDidChangeTreeData) { - this._register(this.dataProvider.onDidChangeTreeData(elementOrElements => this._onDidChangeData.fire({ message: false, element: elementOrElements }))); + this._register(this.dataProvider.onDidChangeTreeData(elementOrElements => { + if (Array.isArray(elementOrElements) && elementOrElements.length === 0) { + return; + } + this._onDidChangeData.fire({ message: false, element: elementOrElements }); + })); } let refreshingPromise: Promise | null; diff --git a/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts b/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts index 7a782b9ee3e..1a84d021384 100644 --- a/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { localize2, localize } from 'vs/nls'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { localize, localize2 } from 'vs/nls'; +import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; -import { TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveCompareEditorCanSwapContext } from 'vs/workbench/common/contextkeys'; +import { ActiveCompareEditorCanSwapContext, TextCompareEditorActiveContext, TextCompareEditorVisibleContext } from 'vs/workbench/common/contextkeys'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -104,17 +104,27 @@ export function registerDiffEditorCommands(): void { } function toggleDiffSideBySide(accessor: ServicesAccessor): void { - const configurationService = accessor.get(IConfigurationService); + const configService = accessor.get(ITextResourceConfigurationService); + const activeTextDiffEditor = getActiveTextDiffEditor(accessor); - const newValue = !configurationService.getValue('diffEditor.renderSideBySide'); - configurationService.updateValue('diffEditor.renderSideBySide', newValue); + const m = activeTextDiffEditor?.getControl()?.getModifiedEditor()?.getModel(); + if (!m) { return; } + + const key = 'diffEditor.renderSideBySide'; + const val = configService.getValue(m.uri, key); + configService.updateValue(m.uri, key, !val); } function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void { - const configurationService = accessor.get(IConfigurationService); + const configService = accessor.get(ITextResourceConfigurationService); + const activeTextDiffEditor = getActiveTextDiffEditor(accessor); - const newValue = !configurationService.getValue('diffEditor.ignoreTrimWhitespace'); - configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue); + const m = activeTextDiffEditor?.getControl()?.getModifiedEditor()?.getModel(); + if (!m) { return; } + + const key = 'diffEditor.ignoreTrimWhitespace'; + const val = configService.getValue(m.uri, key); + configService.updateValue(m.uri, key, !val); } async function swapDiffSides(accessor: ServicesAccessor): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chatAgentHover.ts b/src/vs/workbench/contrib/chat/browser/chatAgentHover.ts index d05851047dc..9a66865acf4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAgentHover.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAgentHover.ts @@ -108,7 +108,7 @@ export class ChatAgentHover extends Disposable { this.name.textContent = `@${agent.name}`; this.extensionName.textContent = agent.extensionDisplayName; - this.publisherName.textContent = agent.extensionPublisher; + this.publisherName.textContent = agent.extensionPublisherDisplayName ?? agent.extensionPublisherId; const description = agent.description && !agent.description.endsWith('.') ? `${agent.description}. ` : diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts index e9ecee0fef1..ad7c537bd21 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts @@ -42,7 +42,7 @@ export class ChatMarkdownDecorationsRenderer { let text = part.text; const isDupe = this.chatAgentService.getAgentsByName(part.agent.name).length > 1; if (isDupe) { - text += ` (${part.agent.extensionPublisher})`; + text += ` (${part.agent.extensionPublisherDisplayName})`; } result += `[${text}](${agentRefUrl}?${encodeURIComponent(part.agent.id)})`; diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts index d9c61a4f675..e419f12a0ff 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts @@ -202,7 +202,8 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { providerDescriptor.id, { extensionId: extension.description.identifier, - extensionPublisher: extension.description.publisherDisplayName ?? extension.description.publisher, // May not be present in OSS + extensionPublisherDisplayName: extension.description.publisherDisplayName ?? extension.description.publisher, // May not be present in OSS + extensionPublisherId: extension.description.publisher, extensionDisplayName: extension.description.displayName ?? extension.description.name, id: providerDescriptor.id, description: providerDescriptor.description, diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 936afbe9a5e..15d476c39b8 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -213,7 +213,7 @@ class InputEditorDecorations extends Disposable { const textDecorations: IDecorationOptions[] | undefined = []; if (agentPart) { const isDupe = !!this.chatAgentService.getAgents().find(other => other.name === agentPart.agent.name && other.id !== agentPart.agent.id); - const publisher = isDupe ? `(${agentPart.agent.extensionPublisher}) ` : ''; + const publisher = isDupe ? `(${agentPart.agent.extensionPublisherDisplayName}) ` : ''; const agentHover = `${publisher}${agentPart.agent.description}`; textDecorations.push({ range: agentPart.editorRange, hoverMessage: new MarkdownString(agentHover) }); if (agentSubcommandPart) { @@ -361,7 +361,7 @@ class AgentCompletions extends Disposable { return { // Leading space is important because detail has no space at the start by design label: isDupe ? - { label: withAt, description: a.description, detail: ` (${a.extensionPublisher})` } : + { label: withAt, description: a.description, detail: ` (${a.extensionPublisherDisplayName})` } : withAt, insertText: `${withAt} `, detail: a.description, @@ -452,7 +452,7 @@ class AgentCompletions extends Disposable { return { label: isDupe ? - { label: agentLabel, description: agent.description, detail: ` (${agent.extensionPublisher})` } : + { label: agentLabel, description: agent.description, detail: ` (${agent.extensionPublisherDisplayName})` } : agentLabel, detail, filterText: `${chatSubcommandLeader}${agent.name}`, diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index c31ad2d2505..97a01f4c47b 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -59,7 +59,8 @@ export interface IChatAgentData { name: string; description?: string; extensionId: ExtensionIdentifier; - extensionPublisher: string; + extensionPublisherId: string; + extensionPublisherDisplayName?: string; extensionDisplayName: string; /** The agent invoked when no agent is specified */ isDefault?: boolean; @@ -325,7 +326,8 @@ export class MergedChatAgent implements IChatAgent { get name(): string { return this.data.name ?? ''; } get description(): string { return this.data.description ?? ''; } get extensionId(): ExtensionIdentifier { return this.data.extensionId; } - get extensionPublisher(): string { return this.data.extensionPublisher; } + get extensionPublisherId(): string { return this.data.extensionPublisherId; } + get extensionPublisherDisplayName() { return this.data.extensionPublisherDisplayName; } get extensionDisplayName(): string { return this.data.extensionDisplayName; } get isDefault(): boolean | undefined { return this.data.isDefault; } get metadata(): IChatAgentMetadata { return this.data.metadata; } @@ -445,7 +447,7 @@ export class ChatAgentNameService implements IChatAgentNameService { return true; } - return allowList.some(id => equalsIgnoreCase(id, id.includes('.') ? chatAgentData.extensionId.value : chatAgentData.extensionPublisher)); + return allowList.some(id => equalsIgnoreCase(id, id.includes('.') ? chatAgentData.extensionId.value : chatAgentData.extensionPublisherId)); }); } diff --git a/src/vs/workbench/contrib/chat/common/chatColors.ts b/src/vs/workbench/contrib/chat/common/chatColors.ts index 385c0cf20c4..3c4cce05e01 100644 --- a/src/vs/workbench/contrib/chat/common/chatColors.ts +++ b/src/vs/workbench/contrib/chat/common/chatColors.ts @@ -21,7 +21,7 @@ export const chatRequestBackground = registerColor( export const chatSlashCommandBackground = registerColor( 'chat.slashCommandBackground', - { dark: '#34414B', light: '#D2ECFF', hcDark: Color.white, hcLight: badgeBackground }, + { dark: '#34414b8f', light: '#d2ecff99', hcDark: Color.white, hcLight: badgeBackground }, localize('chat.slashCommandBackground', 'The background color of a chat slash command.') ); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 0234ee8e8c6..b34f7797087 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -672,6 +672,16 @@ export class ChatModel extends Disposable implements IChatModel { ...(raw as any), name: (raw as any).id, }; + + // Fill in required fields that may be missing from old data + if (!('extensionPublisherId' in agent)) { + agent.extensionPublisherId = agent.extensionPublisher ?? ''; + } + + if (!('extensionDisplayName' in agent)) { + agent.extensionDisplayName = ''; + } + return revive(agent); } @@ -847,7 +857,7 @@ export class ChatModel extends Disposable implements IChatModel { followups: r.response?.followups, isCanceled: r.response?.isCanceled, vote: r.response?.vote, - agent: r.response?.agent, + agent: r.response?.agent ? { ...r.response.agent } : undefined, slashCommand: r.response?.slashCommand, usedContext: r.response?.usedContext, contentReferences: r.response?.contentReferences diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap index 54bf6a2906c..2bdf072855b 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap @@ -32,8 +32,9 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", + extensionPublisherId: "", locations: [ "panel" ], metadata: { }, slashCommands: [ diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap index efbb2badd19..99377413491 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap @@ -32,8 +32,9 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", + extensionPublisherId: "", locations: [ "panel" ], metadata: { }, slashCommands: [ diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap index d725181eaa2..79c66573c25 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap @@ -18,8 +18,9 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", + extensionPublisherId: "", locations: [ "panel" ], metadata: { }, slashCommands: [ diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap index aa6133d3e85..9aaa5755d16 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap @@ -18,8 +18,9 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", + extensionPublisherId: "", locations: [ "panel" ], metadata: { }, slashCommands: [ diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap index 23f7d49220b..6e9d8ffe8e4 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap @@ -18,8 +18,9 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", + extensionPublisherId: "", locations: [ "panel" ], metadata: { }, slashCommands: [ diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap index 1b96c2fdd14..86bcbd48b28 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap @@ -18,8 +18,9 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", + extensionPublisherId: "", locations: [ "panel" ], metadata: { }, slashCommands: [ diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap index d000f55d962..8f0bb2e8da1 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap @@ -18,8 +18,9 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", + extensionPublisherId: "", locations: [ "panel" ], metadata: { }, slashCommands: [ diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap index b5d87ac99f1..d441c9b2021 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap @@ -28,7 +28,8 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherId: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", locations: [ "panel" ], metadata: { }, @@ -65,7 +66,8 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherId: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", locations: [ "panel" ], metadata: { }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap index fd23cf87ef6..c207dd0e8aa 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap @@ -27,7 +27,8 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherId: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", locations: [ "panel" ], metadata: { @@ -68,7 +69,8 @@ value: "nullExtensionDescription", _lower: "nullextensiondescription" }, - extensionPublisher: "", + extensionPublisherId: "", + extensionPublisherDisplayName: "", extensionDisplayName: "", locations: [ "panel" ], metadata: { diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 4e1a265bebe..d96fd480651 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -115,7 +115,7 @@ suite('ChatRequestParser', () => { }); const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => { - return { id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, extensionPublisher: '', extensionDisplayName: '', locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands } satisfies IChatAgentData; + return { id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, extensionPublisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands } satisfies IChatAgentData; }; test('agent with subcommand after text', async () => { diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 2dd2cea5493..03efc208a54 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -35,7 +35,8 @@ const chatAgentWithUsedContext: IChatAgent = { id: chatAgentWithUsedContextId, name: chatAgentWithUsedContextId, extensionId: nullExtensionDescription.identifier, - extensionPublisher: '', + extensionPublisherDisplayName: '', + extensionPublisherId: '', extensionDisplayName: '', locations: [ChatAgentLocation.Panel], metadata: {}, @@ -91,8 +92,8 @@ suite('ChatService', () => { return {}; }, } satisfies IChatAgentImplementation; - testDisposables.add(chatAgentService.registerAgent('testAgent', { name: 'testAgent', id: 'testAgent', isDefault: true, extensionId: nullExtensionDescription.identifier, extensionPublisher: '', extensionDisplayName: '', locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands: [] })); - testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContextId, { name: chatAgentWithUsedContextId, id: chatAgentWithUsedContextId, extensionId: nullExtensionDescription.identifier, extensionPublisher: '', extensionDisplayName: '', locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands: [] })); + testDisposables.add(chatAgentService.registerAgent('testAgent', { name: 'testAgent', id: 'testAgent', isDefault: true, extensionId: nullExtensionDescription.identifier, extensionPublisherId: '', extensionPublisherDisplayName: '', extensionDisplayName: '', locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands: [] })); + testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContextId, { name: chatAgentWithUsedContextId, id: chatAgentWithUsedContextId, extensionId: nullExtensionDescription.identifier, extensionPublisherId: '', extensionPublisherDisplayName: '', extensionDisplayName: '', locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands: [] })); testDisposables.add(chatAgentService.registerAgentImplementation('testAgent', agent)); chatAgentService.updateAgent('testAgent', { requester: { name: 'test' }, fullName: 'test' }); }); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index b098da25e3f..10b5b660fd4 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -29,6 +29,7 @@ suite('VoiceChat', () => { extensionId: ExtensionIdentifier = nullExtensionDescription.identifier; extensionPublisher = ''; extensionDisplayName = ''; + extensionPublisherId = ''; locations: ChatAgentLocation[] = [ChatAgentLocation.Panel]; public readonly name: string; constructor(readonly id: string, readonly slashCommands: IChatAgentCommand[]) { diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 31aee0ee41c..b808f8b5a7c 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -36,8 +36,8 @@ .codicon-debug-breakpoint-conditional.codicon-debug-stackframe::after, .codicon-debug-breakpoint.codicon-debug-stackframe-focused::after, .codicon-debug-breakpoint.codicon-debug-stackframe::after { - content: var(--vscode-icon-circle-small-filled-content); - font-family: var(--vscode-icon-circle-small-filled-font-family); + content: var(--vscode-icon-debug-stackframe-dot-content); + font-family: var(--vscode-icon-debug-stackframe-dot-font-family); position: absolute; } diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index 6faf7e875c2..1ee623c22a0 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -421,10 +421,12 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { data.msgContainer.appendChild($('span', undefined, `${feature.label}: `)); data.msgContainer.appendChild($('span', undefined, ...renderLabelWithIcons(`$(${status.severity === Severity.Error ? errorIcon.id : warningIcon.id}) ${status.message}`))); } - if (accessData?.current) { - const element = $('span', undefined, nls.localize('requests count', "{0} Requests: {1} (Session)", feature.label, accessData.current.count)); - const title = nls.localize('requests count title', "Last request was {0}. Overall Requests: {1}", fromNow(accessData.current.lastAccessed, true, true), accessData.totalCount); - data.elementDisposables.push(this._hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), element, title)); + if (accessData?.totalCount > 0) { + const element = $('span', undefined, `${nls.localize('requests count', "{0} Requests: {1} (Overall)", feature.label, accessData.totalCount)}${accessData.current ? nls.localize('session requests count', ", {0} (Session)", accessData.current.count) : ''}`); + if (accessData.current) { + const title = nls.localize('requests count title', "Last request was {0}.", fromNow(accessData.current.lastAccessed, true, true)); + data.elementDisposables.push(this._hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), element, title)); + } data.msgContainer.appendChild(element); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index f14ef3beac4..f6451dd55c0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -989,7 +989,8 @@ export class ExtensionsListView extends ViewPane { // Get All types of recommendations, trimmed to show a max of 8 at any given time private async getAllRecommendationsModel(options: IQueryOptions, token: CancellationToken): Promise> { - const local = (await this.extensionsWorkbenchService.queryLocal(this.options.server)).map(e => e.identifier.id.toLowerCase()); + const localExtensions = await this.extensionsWorkbenchService.queryLocal(this.options.server); + const localExtensionIds = localExtensions.map(e => e.identifier.id.toLowerCase()); const allRecommendations = distinct( flatten(await Promise.all([ @@ -998,10 +999,32 @@ export class ExtensionsListView extends ViewPane { this.extensionRecommendationsService.getImportantRecommendations(), this.extensionRecommendationsService.getFileBasedRecommendations(), this.extensionRecommendationsService.getOtherRecommendations() - ])).filter(extensionId => !isString(extensionId) || !local.includes(extensionId.toLowerCase()))); + ])).filter(extensionId => { + if (isString(extensionId)) { + return !localExtensionIds.includes(extensionId.toLowerCase()); + } + return !localExtensions.some(localExtension => localExtension.local && this.uriIdentityService.extUri.isEqual(localExtension.local.location, extensionId)); + })); const installableRecommendations = await this.getInstallableRecommendations(allRecommendations, { ...options, source: 'recommendations-all', sortBy: undefined }, token); - return new PagedModel(installableRecommendations.slice(0, 8)); + + const result: IExtension[] = []; + for (let i = 0; i < installableRecommendations.length && result.length < 8; i++) { + const recommendation = allRecommendations[i]; + if (isString(recommendation)) { + const extension = installableRecommendations.find(extension => areSameExtensions(extension.identifier, { id: recommendation })); + if (extension) { + result.push(extension); + } + } else { + const extension = installableRecommendations.find(extension => extension.resourceExtension && this.uriIdentityService.extUri.isEqual(extension.resourceExtension.location, recommendation)); + if (extension) { + result.push(extension); + } + } + } + + return new PagedModel(result); } private async searchRecommendations(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 3844c320ab0..7282aa13c87 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -318,8 +318,9 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { id: _bridgeAgentId, name: 'editor', extensionId: nullExtensionDescription.identifier, - extensionPublisher: '', + extensionPublisherDisplayName: '', extensionDisplayName: '', + extensionPublisherId: '', isDefault: true, locations: [ChatAgentLocation.Editor], get slashCommands(): IChatAgentCommand[] { 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 29e837f9dd3..057adbf7622 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -180,8 +180,9 @@ suite('InteractiveChatController', function () { store.add(chatAgentService.registerDynamicAgent({ extensionId: nullExtensionDescription.identifier, - extensionPublisher: '', + extensionPublisherDisplayName: '', extensionDisplayName: '', + extensionPublisherId: '', id: 'testAgent', name: 'testAgent', isDefault: true, diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index 586c0259dd7..043ea8c80ab 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -127,8 +127,9 @@ suite('InlineChatSession', function () { instaService.get(IChatAgentService).registerDynamicAgent({ extensionId: nullExtensionDescription.identifier, - extensionPublisher: '', + extensionPublisherDisplayName: '', extensionDisplayName: '', + extensionPublisherId: '', id: 'testAgent', name: 'testAgent', isDefault: true, diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 5cb67118ef3..8b37d2ede81 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -55,6 +55,7 @@ import { MarkersTable } from 'vs/workbench/contrib/markers/browser/markersTable' import { Markers, MarkersContextKeys, MarkersViewMode } from 'vs/workbench/contrib/markers/common/markers'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterable> { return Iterable.map(resourceMarkers.markers, m => { @@ -209,9 +210,15 @@ export class MarkersView extends FilterViewPane implements IMarkersView { parent.classList.add('markers-panel'); this._register(dom.addDisposableListener(parent, 'keydown', e => { - if (this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { - this.focusFilter(); + const event = new StandardKeyboardEvent(e); + if (!this.keybindingService.mightProducePrintableCharacter(event)) { + return; } + const result = this.keybindingService.softDispatch(event, event.target); + if (result.kind === ResultKind.MoreChordsNeeded || result.kind === ResultKind.KbFound) { + return; + } + this.focusFilter(); })); const panelContainer = dom.append(parent, dom.$('.markers-panel-container')); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 677ce5b72be..f640be27a6f 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -365,9 +365,10 @@ registerAction2(class extends NotebookAction { interface IInsertCellWithChatArgs extends INotebookActionContext { input?: string; autoSend?: boolean; + source?: string; } -async function startChat(accessor: ServicesAccessor, context: INotebookActionContext, index: number, input?: string, autoSend?: boolean) { +async function startChat(accessor: ServicesAccessor, context: INotebookActionContext, index: number, input?: string, autoSend?: boolean, source?: string) { const configurationService = accessor.get(IConfigurationService); const commandService = accessor.get(ICommandService); @@ -375,11 +376,13 @@ async function startChat(accessor: ServicesAccessor, context: INotebookActionCon context.notebookEditor.focusContainer(); NotebookChatController.get(context.notebookEditor)?.run(index, input, autoSend); } else if (configurationService.getValue(NotebookSetting.cellGenerate)) { - const newCell = await insertNewCell(accessor, context, CellKind.Code, 'below', true); - if (newCell) { - newCell.enableAutoLanguageDetection(); - await context.notebookEditor.revealFirstLineIfOutsideViewport(newCell); - const codeEditor = context.notebookEditor.codeEditors.find(ce => ce[0] === newCell)?.[1]; + const activeCell = context.notebookEditor.getActiveCell(); + const targetCell = activeCell?.getTextLength() === 0 && source !== 'insertToolbar' ? activeCell : (await insertNewCell(accessor, context, CellKind.Code, 'below', true)); + + if (targetCell) { + targetCell.enableAutoLanguageDetection(); + await context.notebookEditor.revealFirstLineIfOutsideViewport(targetCell); + const codeEditor = context.notebookEditor.codeEditors.find(ce => ce[0] === targetCell)?.[1]; if (codeEditor) { codeEditor.focus(); commandService.executeCommand('inlineChat.start'); @@ -497,7 +500,7 @@ registerAction2(class extends NotebookAction { async runWithContext(accessor: ServicesAccessor, context: IInsertCellWithChatArgs) { const index = Math.max(0, context.cell ? context.notebookEditor.getCellIndex(context.cell) + 1 : 0); - await startChat(accessor, context, index, context.input, context.autoSend); + await startChat(accessor, context, index, context.input, context.autoSend, context.source); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index 250cb9824ec..733ac19d6bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -84,6 +84,7 @@ export class BetweenCellToolbar extends CellOverlayPart { ui: true, cell: element, notebookEditor: this._notebookEditor, + source: 'insertToolbar', $mid: MarshalledId.NotebookCellActionContext }; this.updateInternalLayoutNow(element); @@ -205,6 +206,7 @@ export class CellTitleToolbarPart extends CellOverlayPart { ui: true, cell: element, notebookEditor: this._notebookEditor, + source: 'cellToolbar', $mid: MarshalledId.NotebookCellActionContext }); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index 969127bc917..6348c0d359f 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -320,7 +320,8 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { const context = { ui: true, - notebookEditor: this.notebookEditor + notebookEditor: this.notebookEditor, + source: 'notebookToolbar' }; const actionProvider = (action: IAction, options: IActionViewItemOptions) => { diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 7537f80f534..47a1db3d92c 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -58,6 +58,10 @@ import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/ac import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickDiffService, QuickDiff } from 'vs/workbench/contrib/scm/common/quickDiff'; import { IQuickDiffSelectItem, SwitchQuickDiffBaseAction, SwitchQuickDiffViewItem } from 'vs/workbench/contrib/scm/browser/dirtyDiffSwitcher'; +import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; +import { IEditorControl } from 'vs/workbench/common/editor'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; class DiffActionRunner extends ActionRunner { @@ -1649,8 +1653,28 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor this.enabled = false; } + private getVisibleEditorControls(): IEditorControl[] { + const controls: IEditorControl[] = []; + const addControl = (control: IEditorControl | undefined) => { + if (control) { + controls.push(control); + } + }; + + for (const editorPane of this.editorService.visibleEditorPanes) { + if (editorPane instanceof TextDiffEditor || editorPane instanceof TextFileEditor) { + addControl(editorPane.getControl()); + } else if (editorPane instanceof SideBySideEditor) { + addControl(editorPane.getPrimaryEditorPane()?.getControl()); + addControl(editorPane.getSecondaryEditorPane()?.getControl()); + } + } + return controls; + } + private onEditorsChanged(): void { - for (const editor of this.editorService.visibleTextEditorControls) { + const visibleControls = this.getVisibleEditorControls(); + for (const editor of visibleControls) { if (isCodeEditor(editor)) { const textModel = editor.getModel(); const controller = DirtyDiffController.get(editor); @@ -1676,7 +1700,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor for (const [uri, item] of this.items) { for (const editorId of item.keys()) { - if (!this.editorService.visibleTextEditorControls.find(editor => isCodeEditor(editor) && editor.getModel()?.uri.toString() === uri.toString() && editor.getId() === editorId)) { + if (!this.getVisibleEditorControls().find(editor => isCodeEditor(editor) && editor.getModel()?.uri.toString() === uri.toString() && editor.getId() === editorId)) { if (item.has(editorId)) { const dirtyDiffItem = item.get(editorId); dirtyDiffItem?.dispose(); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 52befadc91e..7ffaa617d4a 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -16,7 +16,7 @@ import { ResourceLabels, IResourceLabel, IFileLabelOptions } from 'vs/workbench/ import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IContextViewService, IContextMenuService, IOpenContextView } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -2286,7 +2286,7 @@ class SCMInputWidget { private readonly repositoryDisposables = new DisposableStore(); private validation: IInputValidation | undefined; - private validationDisposable: IDisposable = Disposable.None; + private validationContextView: IOpenContextView | undefined; private validationHasFocus: boolean = false; private _validationTimer: any; @@ -2652,7 +2652,7 @@ class SCMInputWidget { const disposables = new DisposableStore(); - this.validationDisposable = this.contextViewService.showContextView({ + this.validationContextView = this.contextViewService.showContextView({ getAnchor: () => this.element, render: container => { this.element.style.borderBottomLeftRadius = '0'; @@ -2733,7 +2733,8 @@ class SCMInputWidget { } clearValidation(): void { - this.validationDisposable.dispose(); + this.validationContextView?.close(); + this.validationContextView = undefined; this.validationHasFocus = false; } diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index d56ea16e7e7..dfbe243f525 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -331,7 +331,7 @@ export class NativeWindow extends BaseWindow { // Allow to update security settings around protocol handlers ipcRenderer.on('vscode:disablePromptForProtocolHandling', (event: unknown, kind: 'local' | 'remote') => { const setting = kind === 'local' ? 'security.promptForLocalFileProtocolHandling' : 'security.promptForRemoteFileProtocolHandling'; - this.configurationService.updateValue(setting, false, ConfigurationTarget.APPLICATION); + this.configurationService.updateValue(setting, false); }); // Window Zoom diff --git a/src/vs/workbench/services/editor/common/customEditorLabelService.ts b/src/vs/workbench/services/editor/common/customEditorLabelService.ts index 648b1c9c079..3eb55a67968 100644 --- a/src/vs/workbench/services/editor/common/customEditorLabelService.ts +++ b/src/vs/workbench/services/editor/common/customEditorLabelService.ts @@ -6,8 +6,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ParsedPattern, parse as parseGlob } from 'vs/base/common/glob'; import { Disposable } from 'vs/base/common/lifecycle'; -import { isAbsolute, parse as parsePath, ParsedPath } from 'vs/base/common/path'; -import { dirname, relativePath as getRelativePath } from 'vs/base/common/resources'; +import { isAbsolute, parse as parsePath, ParsedPath, dirname } from 'vs/base/common/path'; +import { dirname as resourceDirname, relativePath as getRelativePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -139,7 +139,7 @@ export class CustomEditorLabelService extends Disposable implements ICustomEdito for (const pattern of this.patterns) { let relevantPath: string; if (root && !pattern.isAbsolutePath) { - relevantPath = relativePath ?? getRelativePath(dirname(root.uri), resource) ?? resource.path; + relevantPath = relativePath ?? getRelativePath(resourceDirname(root.uri), resource) ?? resource.path; } else { relevantPath = resource.path; } @@ -164,7 +164,7 @@ export class CustomEditorLabelService extends Disposable implements ICustomEdito return parsedPath.ext.slice(1); default: { // dirname and dirname(arg) const n = variable === 'dirname' ? 0 : parseInt(arg); - const nthDir = this.getNthDirname(relevantPath, parsedPath.name, n); + const nthDir = this.getNthDirname(dirname(relevantPath), n); if (nthDir) { return nthDir; } @@ -175,7 +175,7 @@ export class CustomEditorLabelService extends Disposable implements ICustomEdito }); } - private getNthDirname(path: string, filename: string, n: number): string | undefined { + private getNthDirname(path: string, n: number): string | undefined { // grand-parent/parent/filename.ext1.ext2 -> [grand-parent, parent] path = path.startsWith('/') ? path.slice(1) : path; const pathFragments = path.split('/'); @@ -189,7 +189,7 @@ export class CustomEditorLabelService extends Disposable implements ICustomEdito nth = length - 1 - n - 1; // -1 for the filename, -1 for 0-based index } - const nthDir = nth === pathFragments.length - 1 ? filename : pathFragments[nth]; + const nthDir = pathFragments[nth]; if (nthDir === undefined || nthDir === '') { return undefined; } diff --git a/src/vs/workbench/services/language/common/languageService.ts b/src/vs/workbench/services/language/common/languageService.ts index dd1a54c75ee..a5644c2b309 100644 --- a/src/vs/workbench/services/language/common/languageService.ts +++ b/src/vs/workbench/services/language/common/languageService.ts @@ -15,7 +15,7 @@ import { FILES_ASSOCIATIONS_CONFIG, IFilesConfiguration } from 'vs/platform/file import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExtensionDescription, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; @@ -131,14 +131,18 @@ class LanguageTableRenderer extends Disposable implements IExtensionFeatureTable render(manifest: IExtensionManifest): IRenderedData { const contributes = manifest.contributes; const rawLanguages = contributes?.languages || []; - const languages = rawLanguages.map(l => ({ - id: l.id, - name: (l.aliases || [])[0] || l.id, - extensions: l.extensions || [], - hasGrammar: false, - hasSnippets: false - })); - + const languages: { id: string; name: string; extensions: string[]; hasGrammar: boolean; hasSnippets: boolean }[] = []; + for (const l of rawLanguages) { + if (isValidLanguageExtensionPoint(l)) { + languages.push({ + id: l.id, + name: (l.aliases || [])[0] || l.id, + extensions: l.extensions || [], + hasGrammar: false, + hasSnippets: false + }); + } + } const byId = index(languages, l => l.id); const grammars = contributes?.grammars || []; @@ -234,7 +238,7 @@ export class WorkbenchLanguageService extends LanguageService { for (let j = 0, lenJ = extension.value.length; j < lenJ; j++) { const ext = extension.value[j]; - if (isValidLanguageExtensionPoint(ext, extension.description, extension.collector)) { + if (isValidLanguageExtensionPoint(ext, extension.collector)) { let configuration: URI | undefined = undefined; if (ext.configuration) { configuration = joinPath(extension.description.extensionLocation, ext.configuration); @@ -314,42 +318,42 @@ function isUndefinedOrStringArray(value: string[]): boolean { return value.every(item => typeof item === 'string'); } -function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, extension: IExtensionDescription, collector: ExtensionMessageCollector): boolean { +function isValidLanguageExtensionPoint(value: any, collector?: ExtensionMessageCollector): value is IRawLanguageExtensionPoint { if (!value) { - collector.error(localize('invalid.empty', "Empty value for `contributes.{0}`", languagesExtPoint.name)); + collector?.error(localize('invalid.empty', "Empty value for `contributes.{0}`", languagesExtPoint.name)); return false; } if (typeof value.id !== 'string') { - collector.error(localize('require.id', "property `{0}` is mandatory and must be of type `string`", 'id')); + collector?.error(localize('require.id', "property `{0}` is mandatory and must be of type `string`", 'id')); return false; } if (!isUndefinedOrStringArray(value.extensions)) { - collector.error(localize('opt.extensions', "property `{0}` can be omitted and must be of type `string[]`", 'extensions')); + collector?.error(localize('opt.extensions', "property `{0}` can be omitted and must be of type `string[]`", 'extensions')); return false; } if (!isUndefinedOrStringArray(value.filenames)) { - collector.error(localize('opt.filenames', "property `{0}` can be omitted and must be of type `string[]`", 'filenames')); + collector?.error(localize('opt.filenames', "property `{0}` can be omitted and must be of type `string[]`", 'filenames')); return false; } if (typeof value.firstLine !== 'undefined' && typeof value.firstLine !== 'string') { - collector.error(localize('opt.firstLine', "property `{0}` can be omitted and must be of type `string`", 'firstLine')); + collector?.error(localize('opt.firstLine', "property `{0}` can be omitted and must be of type `string`", 'firstLine')); return false; } if (typeof value.configuration !== 'undefined' && typeof value.configuration !== 'string') { - collector.error(localize('opt.configuration', "property `{0}` can be omitted and must be of type `string`", 'configuration')); + collector?.error(localize('opt.configuration', "property `{0}` can be omitted and must be of type `string`", 'configuration')); return false; } if (!isUndefinedOrStringArray(value.aliases)) { - collector.error(localize('opt.aliases', "property `{0}` can be omitted and must be of type `string[]`", 'aliases')); + collector?.error(localize('opt.aliases', "property `{0}` can be omitted and must be of type `string[]`", 'aliases')); return false; } if (!isUndefinedOrStringArray(value.mimetypes)) { - collector.error(localize('opt.mimetypes', "property `{0}` can be omitted and must be of type `string[]`", 'mimetypes')); + collector?.error(localize('opt.mimetypes', "property `{0}` can be omitted and must be of type `string[]`", 'mimetypes')); return false; } if (typeof value.icon !== 'undefined') { if (typeof value.icon !== 'object' || typeof value.icon.light !== 'string' || typeof value.icon.dark !== 'string') { - collector.error(localize('opt.icon', "property `{0}` can be omitted and must be of type `object` with properties `{1}` and `{2}` of type `string`", 'icon', 'light', 'dark')); + collector?.error(localize('opt.icon', "property `{0}` can be omitted and must be of type `object` with properties `{1}` and `{2}` of type `string`", 'icon', 'light', 'dark')); return false; } }