mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
Merge branch 'main' into mrleemurray/inadequate-hamster-teal
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
pr: none
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include: ["main"]
|
||||
trigger: none
|
||||
|
||||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
|
||||
@@ -824,6 +824,18 @@ export const codeTunnelSubcommands: Fig.Subcommand[] = [
|
||||
template: 'filepaths',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ['--maximize'],
|
||||
description: 'Maximize the chat session view.',
|
||||
},
|
||||
{
|
||||
name: ['-r', '--reuse-window'],
|
||||
description: 'Force to use the last active window for the chat session',
|
||||
},
|
||||
{
|
||||
name: ['-n', '--new-window'],
|
||||
description: 'Force to open an empty window for the chat session',
|
||||
},
|
||||
{
|
||||
name: ['-h', '--help'],
|
||||
description: 'Print usage',
|
||||
|
||||
@@ -72,7 +72,7 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] {
|
||||
const categoryOptions = ['azure', 'data science', 'debuggers', 'extension packs', 'education', 'formatters', 'keymaps', 'language packs', 'linters', 'machine learning', 'notebooks', 'programming languages', 'scm providers', 'snippets', 'testing', 'themes', 'visualization', 'other'];
|
||||
const logOptions = ['critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'];
|
||||
const syncOptions = ['on', 'off'];
|
||||
const chatOptions = ['--add-file <file>', '--help', '--mode <mode>', '-a <file>', '-h', '-m <mode>'];
|
||||
const chatOptions = ['--add-file <file>', '--help', '--maximize', '--mode <mode>', '--new-window', '--reuse-window', '-a <file>', '-h', '-m <mode>', '-n', '-r'];
|
||||
|
||||
const typingTests: ITestSpec[] = [];
|
||||
for (let i = 1; i < executable.length; i++) {
|
||||
@@ -281,7 +281,7 @@ export function createCodeTunnelTestSpecs(executable: string): ITestSpec[] {
|
||||
{ input: `${executable} tunnel unregister |`, expectedCompletions: [...commonFlags] },
|
||||
{ input: `${executable} tunnel service |`, expectedCompletions: [...commonFlags, 'help', 'install', 'log', 'uninstall'] },
|
||||
{ input: `${executable} tunnel help |`, expectedCompletions: helpSubcommands },
|
||||
{ input: `${executable} chat |`, expectedCompletions: ['--mode <mode>', '--add-file <file>', '--help', '-m <mode>', '-a <file>', '-h'] },
|
||||
{ input: `${executable} chat |`, expectedCompletions: ['--mode <mode>', '--add-file <file>', '--help', '--maximize', '--new-window', '--reuse-window', '-m <mode>', '-a <file>', '-h', '-n', '-r'] },
|
||||
{ input: `${executable} chat --mode |`, expectedCompletions: ['agent', 'ask', 'edit'] },
|
||||
{ input: `${executable} chat --add-file |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } },
|
||||
{ input: `${executable} serve-web |`, expectedCompletions: serveWebSubcommandsAndFlags },
|
||||
|
||||
@@ -727,7 +727,7 @@ function completeSingleLinePattern(token: marked.Tokens.Text | marked.Tokens.Par
|
||||
}
|
||||
|
||||
// Contains the start of link text, and no following tokens contain the link target
|
||||
else if (lastLine.match(/(^|\s)\[\w*/)) {
|
||||
else if (lastLine.match(/(^|\s)\[\w*[^\]]*$/)) {
|
||||
return completeLinkText(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -958,6 +958,14 @@ suite('MarkdownRenderer', () => {
|
||||
assert.deepStrictEqual(newTokens, tokens);
|
||||
});
|
||||
|
||||
test('square braces in text', () => {
|
||||
const incomplete = 'hello [what] is going on';
|
||||
const tokens = marked.marked.lexer(incomplete);
|
||||
const newTokens = fillInIncompleteTokens(tokens);
|
||||
|
||||
assert.deepStrictEqual(newTokens, tokens);
|
||||
});
|
||||
|
||||
test('complete link', () => {
|
||||
const incomplete = 'text [link](http://microsoft.com)';
|
||||
const tokens = marked.marked.lexer(incomplete);
|
||||
|
||||
@@ -507,9 +507,18 @@ class CodeMain {
|
||||
}
|
||||
|
||||
if (args.chat) {
|
||||
// If we are started with chat subcommand, the current working
|
||||
// directory is always the path to open
|
||||
args._ = [cwd()];
|
||||
if (args.chat['new-window']) {
|
||||
// Apply `--new-window` flag to the main arguments
|
||||
args['new-window'] = true;
|
||||
} else if (args.chat['reuse-window']) {
|
||||
// Apply `--reuse-window` flag to the main arguments
|
||||
args['reuse-window'] = true;
|
||||
} else {
|
||||
// Unless we are started with specific instructions about
|
||||
// new windows or reusing existing ones, always take the
|
||||
// current working directory as workspace to open.
|
||||
args._ = [cwd()];
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
|
||||
@@ -28,6 +28,9 @@ export interface NativeParsedArgs {
|
||||
_: string[];
|
||||
'add-file'?: string[];
|
||||
mode?: string;
|
||||
maximize?: boolean;
|
||||
'reuse-window'?: boolean;
|
||||
'new-window'?: boolean;
|
||||
help?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -55,7 +55,10 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'_': { type: 'string[]', description: localize('prompt', "The prompt to use as chat.") },
|
||||
'mode': { type: 'string', cat: 'o', alias: 'm', args: 'mode', description: localize('chatMode', "The mode to use for the chat session. Available options: 'ask', 'edit', 'agent', or the identifier of a custom mode. Defaults to 'agent'.") },
|
||||
'add-file': { type: 'string[]', cat: 'o', alias: 'a', args: 'path', description: localize('addFile', "Add files as context to the chat session.") },
|
||||
'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") }
|
||||
'maximize': { type: 'boolean', cat: 'o', description: localize('chatMaximize', "Maximize the chat session view.") },
|
||||
'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindowForChat', "Force to use the last active window for the chat session.") },
|
||||
'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindowForChat', "Force to open an empty window for the chat session.") },
|
||||
'help': { type: 'boolean', alias: 'h', description: localize('help', "Print usage.") }
|
||||
}
|
||||
},
|
||||
'serve-web': {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { DeferredPromise, RunOnceScheduler, timeout, Delayer } from '../../../ba
|
||||
import { CancellationToken } from '../../../base/common/cancellation.js';
|
||||
import { toErrorMessage } from '../../../base/common/errorMessage.js';
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { Disposable, IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js';
|
||||
import { FileAccess, Schemas } from '../../../base/common/network.js';
|
||||
import { getMarks, mark } from '../../../base/common/performance.js';
|
||||
import { isBigSurOrNewer, isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js';
|
||||
@@ -84,6 +84,29 @@ const enum ReadyState {
|
||||
READY
|
||||
}
|
||||
|
||||
class DockBadgeManager {
|
||||
|
||||
static readonly INSTANCE = new DockBadgeManager();
|
||||
|
||||
private readonly windows = new Set<number>();
|
||||
|
||||
acquireBadge(window: IBaseWindow): IDisposable {
|
||||
this.windows.add(window.id);
|
||||
|
||||
electron.app.setBadgeCount(isLinux ? 1 /* only numbers supported */ : undefined /* generic dot */);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this.windows.delete(window.id);
|
||||
|
||||
if (this.windows.size === 0) {
|
||||
electron.app.setBadgeCount(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseWindow extends Disposable implements IBaseWindow {
|
||||
|
||||
//#region Events
|
||||
@@ -325,18 +348,18 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow {
|
||||
|
||||
case FocusMode.Notify:
|
||||
if (isMacintosh) {
|
||||
this.setFocusNotificationBadge(undefined /* generic dot */);
|
||||
this.showFocusNotificationBadge();
|
||||
|
||||
// On macOS we have direct API to bounce the dock icon
|
||||
electron.app.dock?.bounce('informational');
|
||||
} else if (isWindows) {
|
||||
this.setFocusNotificationBadge(undefined /* generic dot */);
|
||||
this.showFocusNotificationBadge();
|
||||
|
||||
// On Windows, calling focus() will bounce the taskbar icon
|
||||
// https://github.com/electron/electron/issues/2867
|
||||
this.win?.focus();
|
||||
} else if (isLinux) {
|
||||
this.setFocusNotificationBadge(1 /* only number supported */);
|
||||
this.showFocusNotificationBadge();
|
||||
|
||||
// On Linux, there seems to be no way to bounce the taskbar icon
|
||||
// as calling focus() will actually steal focus away.
|
||||
@@ -352,18 +375,16 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow {
|
||||
}
|
||||
}
|
||||
|
||||
private hasFocusNotificationBadge = false;
|
||||
private readonly focusNotificationBadgeDisposable = this._register(new MutableDisposable());
|
||||
|
||||
private setFocusNotificationBadge(count?: number): void {
|
||||
electron.app.setBadgeCount(count);
|
||||
this.hasFocusNotificationBadge = true;
|
||||
private showFocusNotificationBadge(): void {
|
||||
if (!this.focusNotificationBadgeDisposable.value) {
|
||||
this.focusNotificationBadgeDisposable.value = DockBadgeManager.INSTANCE.acquireBadge(this);
|
||||
}
|
||||
}
|
||||
|
||||
private clearFocusNotificationBadge(): void {
|
||||
if (this.hasFocusNotificationBadge) {
|
||||
electron.app.setBadgeCount(0);
|
||||
this.hasFocusNotificationBadge = false;
|
||||
}
|
||||
this.focusNotificationBadgeDisposable.clear();
|
||||
}
|
||||
|
||||
private doFocusWindow() {
|
||||
|
||||
@@ -633,10 +633,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService, coreExperimentationService: ICoreExperimentationService): void {
|
||||
this._mainContainerDimension = getClientArea(this.parent, DEFAULT_WINDOW_DIMENSIONS); // running with fallback to ensure no error is thrown (https://github.com/microsoft/vscode/issues/240242)
|
||||
|
||||
this.stateModel = new LayoutStateModel(this.storageService, this.configurationService, this.contextService, coreExperimentationService, this.environmentService);
|
||||
this.stateModel = new LayoutStateModel(this.storageService, this.configurationService, this.contextService, coreExperimentationService);
|
||||
this.stateModel.load({
|
||||
mainContainerDimension: this._mainContainerDimension,
|
||||
resetLayout: Boolean(this.layoutOptions?.resetLayout)
|
||||
resetLayout: Boolean(this.layoutOptions?.resetLayout),
|
||||
isAuxiliaryBarEmpty: this.viewDescriptorService
|
||||
.getViewContainersByLocation(ViewContainerLocation.AuxiliaryBar)
|
||||
.find(viewContainer => this.hasViews(viewContainer.id))?.id !== undefined
|
||||
});
|
||||
|
||||
this._register(this.stateModel.onDidChangeState(change => {
|
||||
@@ -2789,6 +2792,7 @@ enum LegacyWorkbenchLayoutSettings {
|
||||
interface ILayoutStateLoadConfiguration {
|
||||
readonly mainContainerDimension: IDimension;
|
||||
readonly resetLayout: boolean;
|
||||
readonly isAuxiliaryBarEmpty: boolean;
|
||||
}
|
||||
|
||||
class LayoutStateModel extends Disposable {
|
||||
@@ -2804,8 +2808,7 @@ class LayoutStateModel extends Disposable {
|
||||
private readonly storageService: IStorageService,
|
||||
private readonly configurationService: IConfigurationService,
|
||||
private readonly contextService: IWorkspaceContextService,
|
||||
private readonly coreExperimentationService: ICoreExperimentationService,
|
||||
private readonly environmentService: IBrowserWorkbenchEnvironmentService
|
||||
private readonly coreExperimentationService: ICoreExperimentationService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -2868,9 +2871,8 @@ class LayoutStateModel extends Disposable {
|
||||
LayoutStateKeys.SIDEBAR_HIDDEN.defaultValue = workbenchState === WorkbenchState.EMPTY;
|
||||
LayoutStateKeys.AUXILIARYBAR_SIZE.defaultValue = Math.min(300, mainContainerDimension.width / 4);
|
||||
LayoutStateKeys.AUXILIARYBAR_HIDDEN.defaultValue = (() => {
|
||||
const configuration = this.configurationService.inspect(WorkbenchLayoutSettings.AUXILIARYBAR_DEFAULT_VISIBILITY);
|
||||
if (configuration.defaultValue !== 'hidden' && isWeb && !this.environmentService.remoteAuthority) {
|
||||
return true; // TODO@bpasero revisit this when Chat is available in serverless web
|
||||
if (configuration.isAuxiliaryBarEmpty) {
|
||||
return true; // require a view in the auxiliary bar to show it by default
|
||||
}
|
||||
|
||||
switch (this.configurationService.getValue(WorkbenchLayoutSettings.AUXILIARYBAR_DEFAULT_VISIBILITY)) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
|
||||
import { IExtensionManifest, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';
|
||||
import { IExtensionManifest } from '../../../../platform/extensions/common/extensions.js';
|
||||
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
|
||||
import { Registry } from '../../../../platform/registry/common/platform.js';
|
||||
import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js';
|
||||
@@ -20,7 +20,6 @@ import { IAuthenticationUsageService } from '../../../services/authentication/br
|
||||
import { ManageAccountPreferencesForMcpServerAction } from './actions/manageAccountPreferencesForMcpServerAction.js';
|
||||
import { ManageTrustedMcpServersForAccountAction } from './actions/manageTrustedMcpServersForAccountAction.js';
|
||||
import { RemoveDynamicAuthenticationProvidersAction } from './actions/manageDynamicAuthenticationProvidersAction.js';
|
||||
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
|
||||
import { IAuthenticationQueryService } from '../../../services/authentication/common/authenticationQuery.js';
|
||||
import { IMcpRegistry } from '../../mcp/common/mcpRegistryTypes.js';
|
||||
import { autorun } from '../../../../base/common/observable.js';
|
||||
@@ -116,61 +115,61 @@ class AuthenticationUsageContribution implements IWorkbenchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
class AuthenticationExtensionsContribution extends Disposable implements IWorkbenchContribution {
|
||||
static ID = 'workbench.contrib.authenticationExtensions';
|
||||
// class AuthenticationExtensionsContribution extends Disposable implements IWorkbenchContribution {
|
||||
// static ID = 'workbench.contrib.authenticationExtensions';
|
||||
|
||||
constructor(
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@IAuthenticationQueryService private readonly _authenticationQueryService: IAuthenticationQueryService,
|
||||
@IAuthenticationService private readonly _authenticationService: IAuthenticationService
|
||||
) {
|
||||
super();
|
||||
void this.run();
|
||||
this._register(this._extensionService.onDidChangeExtensions(this._onDidChangeExtensions, this));
|
||||
this._register(
|
||||
Event.any(
|
||||
this._authenticationService.onDidChangeDeclaredProviders,
|
||||
this._authenticationService.onDidRegisterAuthenticationProvider
|
||||
)(() => this._cleanupRemovedExtensions())
|
||||
);
|
||||
}
|
||||
// constructor(
|
||||
// @IExtensionService private readonly _extensionService: IExtensionService,
|
||||
// @IAuthenticationQueryService private readonly _authenticationQueryService: IAuthenticationQueryService,
|
||||
// @IAuthenticationService private readonly _authenticationService: IAuthenticationService
|
||||
// ) {
|
||||
// super();
|
||||
// void this.run();
|
||||
// this._register(this._extensionService.onDidChangeExtensions(this._onDidChangeExtensions, this));
|
||||
// this._register(
|
||||
// Event.any(
|
||||
// this._authenticationService.onDidChangeDeclaredProviders,
|
||||
// this._authenticationService.onDidRegisterAuthenticationProvider
|
||||
// )(() => this._cleanupRemovedExtensions())
|
||||
// );
|
||||
// }
|
||||
|
||||
async run(): Promise<void> {
|
||||
await this._extensionService.whenInstalledExtensionsRegistered();
|
||||
this._cleanupRemovedExtensions();
|
||||
}
|
||||
// async run(): Promise<void> {
|
||||
// await this._extensionService.whenInstalledExtensionsRegistered();
|
||||
// this._cleanupRemovedExtensions();
|
||||
// }
|
||||
|
||||
private _onDidChangeExtensions(delta: { readonly added: readonly IExtensionDescription[]; readonly removed: readonly IExtensionDescription[] }): void {
|
||||
if (delta.removed.length > 0) {
|
||||
this._cleanupRemovedExtensions(delta.removed);
|
||||
}
|
||||
}
|
||||
// private _onDidChangeExtensions(delta: { readonly added: readonly IExtensionDescription[]; readonly removed: readonly IExtensionDescription[] }): void {
|
||||
// if (delta.removed.length > 0) {
|
||||
// this._cleanupRemovedExtensions(delta.removed);
|
||||
// }
|
||||
// }
|
||||
|
||||
private _cleanupRemovedExtensions(removedExtensions?: readonly IExtensionDescription[]): void {
|
||||
const extensionIdsToRemove = removedExtensions
|
||||
? new Set(removedExtensions.map(e => e.identifier.value))
|
||||
: new Set(this._extensionService.extensions.map(e => e.identifier.value));
|
||||
// private _cleanupRemovedExtensions(removedExtensions?: readonly IExtensionDescription[]): void {
|
||||
// const extensionIdsToRemove = removedExtensions
|
||||
// ? new Set(removedExtensions.map(e => e.identifier.value))
|
||||
// : new Set(this._extensionService.extensions.map(e => e.identifier.value));
|
||||
|
||||
// If we are cleaning up specific removed extensions, we only remove those.
|
||||
const isTargetedCleanup = !!removedExtensions;
|
||||
// // If we are cleaning up specific removed extensions, we only remove those.
|
||||
// const isTargetedCleanup = !!removedExtensions;
|
||||
|
||||
const providerIds = this._authenticationQueryService.getProviderIds();
|
||||
for (const providerId of providerIds) {
|
||||
this._authenticationQueryService.provider(providerId).forEachAccount(account => {
|
||||
account.extensions().forEach(extension => {
|
||||
const shouldRemove = isTargetedCleanup
|
||||
? extensionIdsToRemove.has(extension.extensionId)
|
||||
: !extensionIdsToRemove.has(extension.extensionId);
|
||||
// const providerIds = this._authenticationQueryService.getProviderIds();
|
||||
// for (const providerId of providerIds) {
|
||||
// this._authenticationQueryService.provider(providerId).forEachAccount(account => {
|
||||
// account.extensions().forEach(extension => {
|
||||
// const shouldRemove = isTargetedCleanup
|
||||
// ? extensionIdsToRemove.has(extension.extensionId)
|
||||
// : !extensionIdsToRemove.has(extension.extensionId);
|
||||
|
||||
if (shouldRemove) {
|
||||
extension.removeUsage();
|
||||
extension.setAccessAllowed(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (shouldRemove) {
|
||||
// extension.removeUsage();
|
||||
// extension.setAccessAllowed(false);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
class AuthenticationMcpContribution extends Disposable implements IWorkbenchContribution {
|
||||
static ID = 'workbench.contrib.authenticationMcp';
|
||||
@@ -216,5 +215,5 @@ class AuthenticationMcpContribution extends Disposable implements IWorkbenchCont
|
||||
|
||||
registerWorkbenchContribution2(AuthenticationContribution.ID, AuthenticationContribution, WorkbenchPhase.AfterRestored);
|
||||
registerWorkbenchContribution2(AuthenticationUsageContribution.ID, AuthenticationUsageContribution, WorkbenchPhase.Eventually);
|
||||
registerWorkbenchContribution2(AuthenticationExtensionsContribution.ID, AuthenticationExtensionsContribution, WorkbenchPhase.Eventually);
|
||||
// registerWorkbenchContribution2(AuthenticationExtensionsContribution.ID, AuthenticationExtensionsContribution, WorkbenchPhase.Eventually);
|
||||
registerWorkbenchContribution2(AuthenticationMcpContribution.ID, AuthenticationMcpContribution, WorkbenchPhase.Eventually);
|
||||
|
||||
@@ -643,6 +643,12 @@ export class CreateRemoteAgentJobAction extends Action2 {
|
||||
chatModel.acceptResponseProgress(addedRequest, { content, kind: 'markdownContent' });
|
||||
chatModel.setResponse(addedRequest, {});
|
||||
chatModel.completeResponse(addedRequest);
|
||||
|
||||
// Clear chat (start a new chat)
|
||||
if (resultMarkdown) {
|
||||
widget.clear();
|
||||
}
|
||||
|
||||
} finally {
|
||||
remoteJobCreatingKey.set(false);
|
||||
}
|
||||
|
||||
@@ -262,9 +262,8 @@ export function registerChatTitleActions() {
|
||||
|
||||
chatService.resendRequest(request!, {
|
||||
userSelectedModelId: languageModelId,
|
||||
userSelectedTools: widget?.getUserSelectedTools(),
|
||||
attempt: (request?.attempt ?? -1) + 1,
|
||||
mode: widget?.input.currentModeKind,
|
||||
...widget?.getModeRequestOptions(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import { IChatAgentCommand, IChatAgentData } from '../common/chatAgents.js';
|
||||
import { IChatResponseModel } from '../common/chatModel.js';
|
||||
import { IParsedChatRequest } from '../common/chatParserTypes.js';
|
||||
import { CHAT_PROVIDER_ID } from '../common/chatParticipantContribTypes.js';
|
||||
import { IChatSendRequestOptions } from '../common/chatService.js';
|
||||
import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel } from '../common/chatViewModel.js';
|
||||
import { ChatAgentLocation, ChatModeKind } from '../common/constants.js';
|
||||
import { ChatAttachmentModel } from './chatAttachmentModel.js';
|
||||
@@ -208,7 +209,7 @@ export interface IChatWidget {
|
||||
focusLastMessage(): void;
|
||||
focusInput(): void;
|
||||
hasInputFocus(): boolean;
|
||||
getUserSelectedTools(): Record<string, boolean> | undefined;
|
||||
getModeRequestOptions(): Partial<IChatSendRequestOptions>;
|
||||
getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined;
|
||||
getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[];
|
||||
getFileTreeInfosForResponse(response: IChatResponseViewModel): IChatFileTreeInfo[];
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ export class ChatConfirmationContentPart extends Disposable implements IChatCont
|
||||
const widget = chatWidgetService.getWidgetBySessionId(element.sessionId);
|
||||
options.userSelectedModelId = widget?.input.currentLanguageModel;
|
||||
options.mode = widget?.input.currentModeKind;
|
||||
options.userSelectedTools = widget?.getUserSelectedTools();
|
||||
Object.assign(options, widget?.getModeRequestOptions());
|
||||
|
||||
if (await this.chatService.sendRequest(element.sessionId, prompt, options)) {
|
||||
confirmation.isUsed = true;
|
||||
|
||||
@@ -61,8 +61,7 @@ export class ChatErrorConfirmationContentPart extends Disposable implements ICha
|
||||
options.confirmation = buttonData.label;
|
||||
const widget = chatWidgetService.getWidgetBySessionId(element.sessionId);
|
||||
options.userSelectedModelId = widget?.input.currentLanguageModel;
|
||||
options.userSelectedTools = widget?.getUserSelectedTools();
|
||||
options.mode = widget?.input.currentModeKind;
|
||||
Object.assign(options, widget?.getModeRequestOptions());
|
||||
if (await chatService.sendRequest(element.sessionId, prompt, options)) {
|
||||
this._onDidChangeHeight.fire();
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ export class ChatSelectedTools extends Disposable {
|
||||
let currentMap = this._sessionStates.get(currentMode.id);
|
||||
let defaultEnablement = false;
|
||||
if (!currentMap && currentMode.kind === ChatModeKind.Agent && currentMode.customTools) {
|
||||
currentMap = this._toolsService.toToolAndToolSetEnablementMap(new Set(currentMode.customTools.read(r)));
|
||||
currentMap = this._toolsService.toToolAndToolSetEnablementMap(currentMode.customTools.read(r));
|
||||
}
|
||||
if (!currentMap) {
|
||||
currentMap = this._selectedTools.read(r);
|
||||
|
||||
@@ -327,9 +327,9 @@ class SetupAgent extends Disposable implements IChatAgentImplementation {
|
||||
}
|
||||
|
||||
await chatService.resendRequest(requestModel, {
|
||||
...widget?.getModeRequestOptions(),
|
||||
mode,
|
||||
userSelectedModelId: languageModel,
|
||||
userSelectedTools: widget?.getUserSelectedTools()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
private isRequestPaused: IContextKey<boolean>;
|
||||
private canRequestBePaused: IContextKey<boolean>;
|
||||
private agentInInput: IContextKey<boolean>;
|
||||
private currentRequest: Promise<void> | undefined;
|
||||
|
||||
|
||||
private _visible = false;
|
||||
@@ -1626,6 +1627,11 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
}
|
||||
|
||||
this.chatService.cancelCurrentRequestForSession(this.viewModel.sessionId);
|
||||
if (this.currentRequest) {
|
||||
// We have to wait the current request to be properly cancelled so that it has a chance to update the model with its result metadata.
|
||||
// This is awkward, it's basically a limitation of the chat provider-based agent.
|
||||
await Promise.race([this.currentRequest, timeout(1000)]);
|
||||
}
|
||||
|
||||
this.input.validateAgentMode();
|
||||
|
||||
@@ -1640,21 +1646,20 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
}
|
||||
|
||||
const result = await this.chatService.sendRequest(this.viewModel.sessionId, requestInputs.input, {
|
||||
mode: this.input.currentModeKind,
|
||||
userSelectedModelId: this.input.currentLanguageModel,
|
||||
location: this.location,
|
||||
locationData: this._location.resolveData?.(),
|
||||
parserContext: { selectedAgent: this._lastSelectedAgent, mode: this.input.currentModeKind },
|
||||
attachedContext: requestInputs.attachedContext.asArray(),
|
||||
noCommandDetection: options?.noCommandDetection,
|
||||
userSelectedTools: this.getUserSelectedTools(),
|
||||
...this.getModeRequestOptions(),
|
||||
modeInstructions: this.input.currentModeObs.get().body?.get()
|
||||
});
|
||||
|
||||
if (result) {
|
||||
this.input.acceptInput(isUserQuery);
|
||||
this._onDidSubmitAgent.fire({ agent: result.agent, slashCommand: result.slashCommand });
|
||||
result.responseCompletePromise.then(() => {
|
||||
this.currentRequest = result.responseCompletePromise.then(() => {
|
||||
const responses = this.viewModel?.getItems().filter(isResponseVM);
|
||||
const lastResponse = responses?.[responses.length - 1];
|
||||
this.chatAccessibilityService.acceptResponse(lastResponse, requestId, options?.isVoiceInput);
|
||||
@@ -1665,6 +1670,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
this.input.setValue(question, false);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentRequest = undefined;
|
||||
});
|
||||
|
||||
if (this.viewModel?.editing) {
|
||||
@@ -1909,7 +1916,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
|
||||
// if not tools to enable are present, we are done
|
||||
if (tools !== undefined && this.input.currentModeKind === ChatModeKind.Agent) {
|
||||
const enablementMap = this.toolsService.toToolAndToolSetEnablementMap(new Set(tools));
|
||||
const enablementMap = this.toolsService.toToolAndToolSetEnablementMap(tools);
|
||||
this.input.selectedToolsModel.set(enablementMap, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -484,13 +484,19 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
|
||||
return result;
|
||||
}
|
||||
|
||||
toToolAndToolSetEnablementMap(toolOrToolSetNames: Set<string>): Map<ToolSet | IToolData, boolean> {
|
||||
/**
|
||||
* Create a map that contains all tools and toolsets with their enablement state.
|
||||
* @param toolOrToolSetNames A list of tool or toolset names to check for enablement. If undefined, all tools and toolsets are enabled.
|
||||
* @returns A map of tool or toolset instances to their enablement state.
|
||||
*/
|
||||
toToolAndToolSetEnablementMap(enabledToolOrToolSetNames: readonly string[] | undefined): Map<ToolSet | IToolData, boolean> {
|
||||
const toolOrToolSetNames = enabledToolOrToolSetNames ? new Set(enabledToolOrToolSetNames) : undefined;
|
||||
const result = new Map<ToolSet | IToolData, boolean>();
|
||||
for (const tool of this._tools.values()) {
|
||||
result.set(tool.data, tool.data.toolReferenceName !== undefined && toolOrToolSetNames.has(tool.data.toolReferenceName));
|
||||
result.set(tool.data, tool.data.toolReferenceName !== undefined && (toolOrToolSetNames === undefined || toolOrToolSetNames.has(tool.data.toolReferenceName)));
|
||||
}
|
||||
for (const toolSet of this._toolSets) {
|
||||
result.set(toolSet, toolOrToolSetNames.has(toolSet.referenceName));
|
||||
result.set(toolSet, (toolOrToolSetNames === undefined || toolOrToolSetNames.has(toolSet.referenceName)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider
|
||||
|
||||
private async updateTools(model: ITextModel, tools: PromptToolsMetadata) {
|
||||
|
||||
const selectedToolsNow = tools.value ? this.languageModelToolsService.toToolAndToolSetEnablementMap(new Set(tools.value)) : new Map();
|
||||
const selectedToolsNow = tools.value ? this.languageModelToolsService.toToolAndToolSetEnablementMap(tools.value) : new Map();
|
||||
const newSelectedAfter = await this.instantiationService.invokeFunction(showToolsPicker, localize('placeholder', "Select tools"), undefined, selectedToolsNow);
|
||||
if (!newSelectedAfter) {
|
||||
return;
|
||||
|
||||
@@ -185,7 +185,7 @@ export class ChatModeService extends Disposable implements IChatModeService {
|
||||
];
|
||||
|
||||
if (this.chatAgentService.hasToolsAgent) {
|
||||
builtinModes.push(ChatMode.Agent);
|
||||
builtinModes.unshift(ChatMode.Agent);
|
||||
}
|
||||
builtinModes.push(ChatMode.Edit);
|
||||
return builtinModes;
|
||||
|
||||
@@ -275,7 +275,7 @@ export interface ILanguageModelToolsService {
|
||||
resetToolAutoConfirmation(): void;
|
||||
cancelToolCallsForRequest(requestId: string): void;
|
||||
toToolEnablementMap(toolOrToolSetNames: Set<string>): Record<string, boolean>;
|
||||
toToolAndToolSetEnablementMap(toolOrToolSetNames: Set<string>): IToolAndToolSetEnablementMap;
|
||||
toToolAndToolSetEnablementMap(toolOrToolSetNames: readonly string[] | undefined): IToolAndToolSetEnablementMap;
|
||||
|
||||
readonly toolSets: IObservable<Iterable<ToolSet>>;
|
||||
getToolSet(id: string): ToolSet | undefined;
|
||||
|
||||
@@ -23,6 +23,10 @@ import { resolve } from '../../../../base/common/path.js';
|
||||
import { showChatView } from '../browser/chat.js';
|
||||
import { IViewsService } from '../../../services/views/common/viewsService.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';
|
||||
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { ChatContextKeys } from '../common/chatContextKeys.js';
|
||||
import { ViewContainerLocation } from '../../../common/views.js';
|
||||
|
||||
class NativeBuiltinToolsContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
@@ -49,7 +53,9 @@ class ChatCommandLineHandler extends Disposable {
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -84,6 +90,16 @@ class ChatCommandLineHandler extends Disposable {
|
||||
};
|
||||
|
||||
const chatWidget = await showChatView(this.viewsService);
|
||||
|
||||
if (args.maximize) {
|
||||
const location = this.contextKeyService.getContextKeyValue<ViewContainerLocation>(ChatContextKeys.panelLocation.key);
|
||||
if (location === ViewContainerLocation.AuxiliaryBar) {
|
||||
this.layoutService.setAuxiliaryBarMaximized(true);
|
||||
} else if (location === ViewContainerLocation.Panel && !this.layoutService.isPanelMaximized()) {
|
||||
this.layoutService.toggleMaximizedPanel();
|
||||
}
|
||||
}
|
||||
|
||||
await chatWidget?.waitForReady();
|
||||
await this.commandService.executeCommand(ACTION_ID_NEW_CHAT);
|
||||
await this.commandService.executeCommand(CHAT_OPEN_ACTION_ID, opts);
|
||||
|
||||
@@ -76,7 +76,7 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
toToolAndToolSetEnablementMap(toolOrToolSetNames: Set<string>): Map<ToolSet | IToolData, boolean> {
|
||||
toToolAndToolSetEnablementMap(toolOrToolSetNames: readonly string[] | undefined): Map<ToolSet | IToolData, boolean> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { IContextMenuService } from '../../../../platform/contextview/browser/co
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { manageExtensionIcon } from '../../extensions/browser/extensionsIcons.js';
|
||||
import { getDomNodePagePosition } from '../../../../base/browser/dom.js';
|
||||
import { IMcpSamplingService, IMcpServer, IMcpServerContainer, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCapability, McpConnectionState, McpServerEditorTab } from '../common/mcpTypes.js';
|
||||
import { IMcpSamplingService, IMcpServer, IMcpServerContainer, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCapability, McpConnectionState, McpServerEditorTab, McpServerInstallState } from '../common/mcpTypes.js';
|
||||
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
||||
import { McpCommandIds } from '../common/mcpCommandIds.js';
|
||||
@@ -116,6 +116,9 @@ export class InstallAction extends McpServerAction {
|
||||
if (!this.mcpServer?.gallery && !this.mcpServer?.installable) {
|
||||
return;
|
||||
}
|
||||
if (this.mcpServer.installState !== McpServerInstallState.Uninstalled) {
|
||||
return;
|
||||
}
|
||||
this.class = InstallAction.CLASS;
|
||||
this.enabled = true;
|
||||
this.label = localize('install', "Install");
|
||||
@@ -145,6 +148,20 @@ export class InstallAction extends McpServerAction {
|
||||
}
|
||||
}
|
||||
|
||||
export class InstallingLabelAction extends McpServerAction {
|
||||
|
||||
private static readonly LABEL = localize('installing', "Installing");
|
||||
private static readonly CLASS = `${McpServerAction.LABEL_ACTION_CLASS} install installing`;
|
||||
|
||||
constructor() {
|
||||
super('extension.installing', InstallingLabelAction.LABEL, InstallingLabelAction.CLASS, false);
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this.class = `${InstallingLabelAction.CLASS}${this.mcpServer && this.mcpServer.installState === McpServerInstallState.Installing ? '' : ' hide'}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class UninstallAction extends McpServerAction {
|
||||
|
||||
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent uninstall`;
|
||||
@@ -166,6 +183,10 @@ export class UninstallAction extends McpServerAction {
|
||||
if (!this.mcpServer.local) {
|
||||
return;
|
||||
}
|
||||
if (this.mcpServer.installState !== McpServerInstallState.Installed) {
|
||||
this.enabled = false;
|
||||
return;
|
||||
}
|
||||
this.class = UninstallAction.CLASS;
|
||||
this.enabled = true;
|
||||
this.label = localize('uninstall', "Uninstall");
|
||||
|
||||
@@ -39,7 +39,7 @@ import { IExtensionService } from '../../../services/extensions/common/extension
|
||||
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
|
||||
import { IMcpServerEditorOptions, IWorkbenchMcpServer, McpServerContainers } from '../common/mcpTypes.js';
|
||||
import { InstallCountWidget, McpServerIconWidget, McpServerWidget, onClick, PublisherWidget, RatingsWidget } from './mcpServerWidgets.js';
|
||||
import { DropDownAction, InstallAction, ManageMcpServerAction, UninstallAction } from './mcpServerActions.js';
|
||||
import { DropDownAction, InstallAction, InstallingLabelAction, ManageMcpServerAction, UninstallAction } from './mcpServerActions.js';
|
||||
import { McpServerEditorInput } from './mcpServerEditorInput.js';
|
||||
import { ILocalMcpServer, IMcpServerManifest, IMcpServerPackage, PackageType } from '../../../../platform/mcp/common/mcpManagement.js';
|
||||
import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
|
||||
@@ -220,6 +220,7 @@ export class McpServerEditor extends EditorPane {
|
||||
|
||||
const actions = [
|
||||
this.instantiationService.createInstance(InstallAction, true),
|
||||
this.instantiationService.createInstance(InstallingLabelAction),
|
||||
this.instantiationService.createInstance(UninstallAction),
|
||||
this.instantiationService.createInstance(ManageMcpServerAction, true),
|
||||
];
|
||||
|
||||
@@ -25,7 +25,7 @@ import { getLocationBasedViewColors, ViewPane } from '../../../browser/parts/vie
|
||||
import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';
|
||||
import { IViewDescriptorService, IViewsRegistry, Extensions as ViewExtensions } from '../../../common/views.js';
|
||||
import { HasInstalledMcpServersContext, IMcpWorkbenchService, InstalledMcpServersViewId, IWorkbenchMcpServer, McpServerContainers, mcpServerIcon, McpServerInstallState } from '../common/mcpTypes.js';
|
||||
import { DropDownAction, InstallAction, ManageMcpServerAction } from './mcpServerActions.js';
|
||||
import { DropDownAction, InstallAction, InstallingLabelAction, ManageMcpServerAction } from './mcpServerActions.js';
|
||||
import { PublisherWidget, InstallCountWidget, RatingsWidget, McpServerIconWidget } from './mcpServerWidgets.js';
|
||||
import { ActionRunner, IAction, Separator } from '../../../../base/common/actions.js';
|
||||
import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
|
||||
@@ -321,6 +321,7 @@ class McpServerRenderer implements IListRenderer<IWorkbenchMcpServer, IMcpServer
|
||||
|
||||
const actions = [
|
||||
this.instantiationService.createInstance(InstallAction, false),
|
||||
this.instantiationService.createInstance(InstallingLabelAction),
|
||||
this.instantiationService.createInstance(ManageMcpServerAction, false),
|
||||
];
|
||||
|
||||
|
||||
@@ -907,6 +907,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
|
||||
}
|
||||
|
||||
hideSuggestWidget(cancelAnyRequest: boolean): void {
|
||||
this._discoverability?.resetTimer();
|
||||
if (cancelAnyRequest) {
|
||||
this._cancellationTokenSource?.cancel();
|
||||
this._cancellationTokenSource = undefined;
|
||||
|
||||
+14
-4
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TimeoutTimer } from '../../../../../base/common/async.js';
|
||||
import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
|
||||
import { TerminalShellType } from '../../../../../platform/terminal/common/terminal.js';
|
||||
@@ -23,7 +24,7 @@ interface ITerminalSuggestShownTracker extends IDisposable {
|
||||
export class TerminalSuggestShownTracker extends Disposable implements ITerminalSuggestShownTracker {
|
||||
private _done: boolean;
|
||||
private _count: number;
|
||||
private _timeout: Timeout | undefined;
|
||||
private _timeout: TimeoutTimer | undefined;
|
||||
private _start: number | undefined;
|
||||
|
||||
private _firstShownTracker: { shell: Set<TerminalShellType>; window: boolean } | undefined = undefined;
|
||||
@@ -51,6 +52,14 @@ export class TerminalSuggestShownTracker extends Disposable implements ITerminal
|
||||
this._firstShownTracker = undefined;
|
||||
}
|
||||
|
||||
resetTimer(): void {
|
||||
if (this._timeout) {
|
||||
this._timeout.cancel();
|
||||
this._timeout = undefined;
|
||||
}
|
||||
this._start = undefined;
|
||||
}
|
||||
|
||||
update(widgetElt: HTMLElement | undefined): void {
|
||||
if (this._done) {
|
||||
return;
|
||||
@@ -63,10 +72,11 @@ export class TerminalSuggestShownTracker extends Disposable implements ITerminal
|
||||
if (this._count >= TERMINAL_SUGGEST_DISCOVERABILITY_MAX_COUNT) {
|
||||
this._setDone(widgetElt);
|
||||
} else if (!this._start) {
|
||||
this.resetTimer();
|
||||
this._start = Date.now();
|
||||
this._timeout = setTimeout(() => {
|
||||
this._timeout = this._register(new TimeoutTimer(() => {
|
||||
this._setDone(widgetElt);
|
||||
}, TERMINAL_SUGGEST_DISCOVERABILITY_MIN_MS);
|
||||
}, TERMINAL_SUGGEST_DISCOVERABILITY_MIN_MS));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +87,7 @@ export class TerminalSuggestShownTracker extends Disposable implements ITerminal
|
||||
widgetElt.classList.remove('increased-discoverability');
|
||||
}
|
||||
if (this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout.cancel();
|
||||
this._timeout = undefined;
|
||||
}
|
||||
this._start = undefined;
|
||||
|
||||
Reference in New Issue
Block a user