Merge branch 'main' into fix/regexless-windows-prompt-detection

This commit is contained in:
Chapman Pendery
2024-04-25 11:24:43 -07:00
committed by GitHub
50 changed files with 359 additions and 159 deletions
+2 -2
View File
@@ -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,
@@ -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<void> {
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<any>, language: string | null): ConfigurationTarget {
@@ -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';
};
@@ -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<void> {
// 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<ShowCodeActionListEvent, ShowListEventClassification>('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<CodeActionItem> = {
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<ShowCodeActionListEvent, ShowListEventClassification>('codeAction.showCodeActionList.onHide', {
codeActionListLength: actions.validActions.length,
didCancel: didCancel,
});
}
},
onHover: async (action: CodeActionItem, token: CancellationToken) => {
if (token.isCancellationRequested) {
@@ -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;
@@ -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, RenameInvokedClassification>('renameInvokedEvent', value);
@@ -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);
@@ -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) {
@@ -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]);
});
});
@@ -36,7 +36,7 @@ export interface IActionWidgetService {
show<T>(user: string, supportsPreview: boolean, items: readonly IActionListItem<T>[], delegate: IActionListDelegate<T>, 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);
}
});
@@ -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<IContextMenuService>('contextMenuService');
export interface IContextMenuService {
@@ -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<IDisposable>());
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;
}
}
@@ -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 }
});
}
}
@@ -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?
@@ -355,7 +355,12 @@ class ExtHostTreeView<T> 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<void> | null;
@@ -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<void> {
@@ -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}. ` :
@@ -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)})`;
@@ -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,
@@ -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 <CompletionItem>{
// 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}`,
@@ -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));
});
}
@@ -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.')
);
@@ -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
@@ -32,8 +32,9 @@
value: "nullExtensionDescription",
_lower: "nullextensiondescription"
},
extensionPublisher: "",
extensionPublisherDisplayName: "",
extensionDisplayName: "",
extensionPublisherId: "",
locations: [ "panel" ],
metadata: { },
slashCommands: [
@@ -32,8 +32,9 @@
value: "nullExtensionDescription",
_lower: "nullextensiondescription"
},
extensionPublisher: "",
extensionPublisherDisplayName: "",
extensionDisplayName: "",
extensionPublisherId: "",
locations: [ "panel" ],
metadata: { },
slashCommands: [
@@ -18,8 +18,9 @@
value: "nullExtensionDescription",
_lower: "nullextensiondescription"
},
extensionPublisher: "",
extensionPublisherDisplayName: "",
extensionDisplayName: "",
extensionPublisherId: "",
locations: [ "panel" ],
metadata: { },
slashCommands: [
@@ -18,8 +18,9 @@
value: "nullExtensionDescription",
_lower: "nullextensiondescription"
},
extensionPublisher: "",
extensionPublisherDisplayName: "",
extensionDisplayName: "",
extensionPublisherId: "",
locations: [ "panel" ],
metadata: { },
slashCommands: [
@@ -18,8 +18,9 @@
value: "nullExtensionDescription",
_lower: "nullextensiondescription"
},
extensionPublisher: "",
extensionPublisherDisplayName: "",
extensionDisplayName: "",
extensionPublisherId: "",
locations: [ "panel" ],
metadata: { },
slashCommands: [
@@ -18,8 +18,9 @@
value: "nullExtensionDescription",
_lower: "nullextensiondescription"
},
extensionPublisher: "",
extensionPublisherDisplayName: "",
extensionDisplayName: "",
extensionPublisherId: "",
locations: [ "panel" ],
metadata: { },
slashCommands: [
@@ -18,8 +18,9 @@
value: "nullExtensionDescription",
_lower: "nullextensiondescription"
},
extensionPublisher: "",
extensionPublisherDisplayName: "",
extensionDisplayName: "",
extensionPublisherId: "",
locations: [ "panel" ],
metadata: { },
slashCommands: [
@@ -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: { },
@@ -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: {
@@ -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 () => {
@@ -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' });
});
@@ -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[]) {
@@ -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;
}
@@ -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);
}
@@ -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<IPagedModel<IExtension>> {
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<IPagedModel<IExtension>> {
@@ -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[] {
@@ -180,8 +180,9 @@ suite('InteractiveChatController', function () {
store.add(chatAgentService.registerDynamicAgent({
extensionId: nullExtensionDescription.identifier,
extensionPublisher: '',
extensionPublisherDisplayName: '',
extensionDisplayName: '',
extensionPublisherId: '',
id: 'testAgent',
name: 'testAgent',
isDefault: true,
@@ -127,8 +127,9 @@ suite('InlineChatSession', function () {
instaService.get(IChatAgentService).registerDynamicAgent({
extensionId: nullExtensionDescription.identifier,
extensionPublisher: '',
extensionPublisherDisplayName: '',
extensionDisplayName: '',
extensionPublisherId: '',
id: 'testAgent',
name: 'testAgent',
isDefault: true,
@@ -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<ITreeElement<MarkerElement>> {
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'));
@@ -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<boolean>(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);
}
});
@@ -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
});
}
@@ -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) => {
@@ -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();
@@ -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;
}
+1 -1
View File
@@ -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
@@ -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;
}
@@ -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<ITableData> {
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;
}
}