diff --git a/extensions/git/package.json b/extensions/git/package.json index 9f53f3ff16743..bb18ee9bf6bba 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -24,6 +24,7 @@ "contribSourceControlTitleMenu", "contribViewsWelcome", "editSessionIdentityProvider", + "findFiles2", "quickDiffProvider", "quickPickSortByLabel", "scmActionButton", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 97621cb597c97..7ca1c8861949d 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import * as fsPromises from 'fs/promises'; import * as path from 'path'; import picomatch from 'picomatch'; -import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, FileDecoration, FileType, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, QuickDiffProvider, RelativePattern, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, TabInputNotebookDiff, TabInputTextDiff, TabInputTextMultiDiff, ThemeColor, ThemeIcon, Uri, window, workspace, WorkspaceEdit } from 'vscode'; +import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, ExcludeSettingOptions, FileDecoration, FileType, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, QuickDiffProvider, RelativePattern, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, TabInputNotebookDiff, TabInputTextDiff, TabInputTextMultiDiff, ThemeColor, ThemeIcon, Uri, window, workspace, WorkspaceEdit } from 'vscode'; import { ActionButton } from './actionButton'; import { ApiRepository } from './api/api1'; import { Branch, BranchQuery, Change, CommitOptions, DiffChange, FetchOptions, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status } from './api/git'; @@ -1896,62 +1896,28 @@ export class Repository implements Disposable { return new Set(); } - try { - // Expand the glob patterns - const matchedFiles = new Set(); - for (const pattern of worktreeIncludeFiles) { - for await (const file of fsPromises.glob(pattern, { cwd: this.root })) { - matchedFiles.add(file); - } - } - - // Collect unique directories from all the matched files. Check - // first whether directories are ignored in order to limit the - // number of git check-ignore calls. - const directoriesToCheck = new Set(); - for (const file of matchedFiles) { - let parent = path.dirname(file); - while (parent && parent !== '.') { - directoriesToCheck.add(path.join(this.root, parent)); - parent = path.dirname(parent); - } - } - - const gitIgnoredDirectories = await this.checkIgnore(Array.from(directoriesToCheck)); - - // Files under a git ignored directory are ignored - const gitIgnoredFiles = new Set(); - const filesToCheck: string[] = []; - - for (const file of matchedFiles) { - const fullPath = path.join(this.root, file); - let parent = path.dirname(fullPath); - let isUnderIgnoredDir = false; - - while (parent !== this.root && parent.length > this.root.length) { - if (gitIgnoredDirectories.has(parent)) { - isUnderIgnoredDir = true; - break; - } - parent = path.dirname(parent); - } + const filePattern = worktreeIncludeFiles + .map(pattern => new RelativePattern(this.root, pattern)); - if (isUnderIgnoredDir) { - gitIgnoredFiles.add(fullPath); - } else { - filesToCheck.push(fullPath); - } - } + // Get all files matching the globs (no ignore files applied) + const allFiles = await workspace.findFiles2(filePattern, { + useExcludeSettings: ExcludeSettingOptions.None, + useIgnoreFiles: { local: false, parent: false, global: false } + }); - // Check the files that are not under a git ignored directories - const filesToCheckResults = await this.checkIgnore(Array.from(filesToCheck)); - filesToCheckResults.forEach(ignoredFile => gitIgnoredFiles.add(ignoredFile)); + // Get files matching the globs with git ignore files applied + const nonIgnoredFiles = await workspace.findFiles2(filePattern, { + useExcludeSettings: ExcludeSettingOptions.None, + useIgnoreFiles: { local: true, parent: true, global: true } + }); - return gitIgnoredFiles; - } catch (err) { - this.logger.warn(`[Repository][_getWorktreeIncludeFiles] Failed to get worktree include files: ${err}`); - return new Set(); + // Files that are git ignored = all files - non-ignored files + const gitIgnoredFiles = new Set(allFiles.map(uri => uri.fsPath)); + for (const uri of nonIgnoredFiles) { + gitIgnoredFiles.delete(uri.fsPath); } + + return gitIgnoredFiles; } private async _copyWorktreeIncludeFiles(worktreePath: string): Promise { @@ -1990,15 +1956,16 @@ export class Repository implements Disposable { )); }); - // When expanding the glob patterns, both directories and files are matched however - // directories cannot be copied so we filter out `ERR_FS_EISDIR` errors as those are - // expected. - const errors = results.filter(r => r.status === 'rejected' && - (r.reason as NodeJS.ErrnoException).code !== 'ERR_FS_EISDIR'); + // Log any failed operations + const failedOperations = results.filter(r => r.status === 'rejected'); - if (errors.length > 0) { - this.logger.warn(`[Repository][_copyWorktreeIncludeFiles] Failed to copy ${errors.length} files to worktree.`); - window.showWarningMessage(l10n.t('Failed to copy {0} files to the worktree.', errors.length)); + if (failedOperations.length > 0) { + window.showWarningMessage(l10n.t('Failed to copy {0} files to the worktree.', failedOperations.length)); + + this.logger.warn(`[Repository][_copyWorktreeIncludeFiles] Failed to copy ${failedOperations.length} files to worktree.`); + for (const error of failedOperations) { + this.logger.warn(` - ${(error as PromiseRejectedResult).reason}`); + } } } catch (err) { this.logger.warn(`[Repository][_copyWorktreeIncludeFiles] Failed to copy files to worktree: ${err}`); diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 9b5ea7dd67ea4..e0586d16816db 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -11,6 +11,7 @@ "src/**/*", "../../src/vscode-dts/vscode.d.ts", "../../src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts", + "../../src/vscode-dts/vscode.proposed.findFiles2.d.ts", "../../src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts", "../../src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts", "../../src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts", diff --git a/package-lock.json b/package-lock.json index c15b9ca7f093d..c5874a4622be4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "native-is-elevated": "0.8.0", "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", - "node-pty": "^1.2.0-beta.3", + "node-pty": "^1.1.0-beta43", "open": "^10.1.2", "tas-client": "0.3.1", "undici": "^7.9.0", @@ -3258,9 +3258,9 @@ } }, "node_modules/@vscode/policy-watcher": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@vscode/policy-watcher/-/policy-watcher-1.3.6.tgz", - "integrity": "sha512-YJA9+6M4s2SjChWczy3EdyhXNPWqNNU8O2jzlrsQz7za5Am5Vo+1Rrln4AQDSvo9aTCNlTwlTAhRVWvyGGaN8A==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@vscode/policy-watcher/-/policy-watcher-1.3.5.tgz", + "integrity": "sha512-k1n9gaDBjyVRy5yJLABbZCnyFwgQ8OA4sR3vXmXnmB+mO9JA0nsl/XOXQfVCoLasBu3UHCOfAnDWGn2sRzCR+A==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -3320,9 +3320,9 @@ } }, "node_modules/@vscode/spdlog": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.5.tgz", - "integrity": "sha512-2eckivcs73OTnP+CwvJOQxluzT9tLqEH5Wl+rrv8bt5hVeXLdRYtihENFNSAYW099hL4/oyJ990KAssi7OxSWw==", + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.4.tgz", + "integrity": "sha512-NmFasVWjn/6BjHMAjqalsbG2srQCt8yfC0EczP5wzNQFawv74rhvuarhWi44x3St9LB8bZBxrpbT7igPaTJwcw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -3559,9 +3559,9 @@ } }, "node_modules/@vscode/windows-mutex": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@vscode/windows-mutex/-/windows-mutex-0.5.3.tgz", - "integrity": "sha512-hWNmD+AzINR57jWuc/iW53kA+BghI4iOuicxhAEeeJLPOeMm9X5IUD0ttDwJFEib+D8H/2T9pT/8FeB/xcqbRw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@vscode/windows-mutex/-/windows-mutex-0.5.2.tgz", + "integrity": "sha512-O9CNYVl2GmFVbiHiz7tyFrKIdXVs3qf8HnyWlfxyuMaKzXd1L35jSTNCC1oAVwr8F0O2P4o3C/jOSIXulUCJ7w==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -12818,9 +12818,9 @@ "license": "MIT" }, "node_modules/native-keymap": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/native-keymap/-/native-keymap-3.3.8.tgz", - "integrity": "sha512-JoNfN3hlYWSiCJDMep9adOjpOvq64orKNO8zIy0ns1EZJFUwnvgHRpD7T8eWm7SMlbn4X3fh5FkA7LKPtT/Niw==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/native-keymap/-/native-keymap-3.3.7.tgz", + "integrity": "sha512-07n5kF0L9ERC9pilqEFucnhs1XG4WttbHAMWhhOSqQYXhB8mMNTSCzP4psTaVgDSp6si2HbIPhTIHuxSia6NPQ==", "hasInstallScript": true, "license": "MIT" }, @@ -12949,9 +12949,9 @@ } }, "node_modules/node-pty": { - "version": "1.2.0-beta.3", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.3.tgz", - "integrity": "sha512-SeAwG9LgWijWLtWldBWPwUUA1rAg2OKBG37dtSGOTYvBkUstWxAi2hXS0pX9JXbHPDxs0DnVc5tXrXsY821E+w==", + "version": "1.1.0-beta43", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta43.tgz", + "integrity": "sha512-CYyIQogRs97Rfjo0WKyku8V56Bm4WyWUijrbWDs5LJ+ZmsUW2gqbVAEpD+1gtA7dEZ6v1A08GzfqsDuIl/eRqw==", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index fc7bc3b48ad83..a4a014d7ae22c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.109.0", - "distro": "276abacfc6a1d1a9d17ab0d7d7cb4775998082b2", + "distro": "9ac7c0b1d7f8b73f10dc974777bccc7b55ee60d4", "author": { "name": "Microsoft Corporation" }, @@ -109,7 +109,7 @@ "native-is-elevated": "0.8.0", "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", - "node-pty": "^1.2.0-beta.3", + "node-pty": "^1.1.0-beta43", "open": "^10.1.2", "tas-client": "0.3.1", "undici": "^7.9.0", diff --git a/remote/package-lock.json b/remote/package-lock.json index 138d0fda7d697..44adad2a8260f 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -38,7 +38,7 @@ "kerberos": "2.1.1", "minimist": "^1.2.8", "native-watchdog": "^1.4.1", - "node-pty": "^1.2.0-beta.3", + "node-pty": "^1.1.0-beta43", "tas-client": "0.3.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", @@ -451,9 +451,9 @@ } }, "node_modules/@vscode/spdlog": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.5.tgz", - "integrity": "sha512-2eckivcs73OTnP+CwvJOQxluzT9tLqEH5Wl+rrv8bt5hVeXLdRYtihENFNSAYW099hL4/oyJ990KAssi7OxSWw==", + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.4.tgz", + "integrity": "sha512-NmFasVWjn/6BjHMAjqalsbG2srQCt8yfC0EczP5wzNQFawv74rhvuarhWi44x3St9LB8bZBxrpbT7igPaTJwcw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1051,9 +1051,9 @@ } }, "node_modules/node-pty": { - "version": "1.2.0-beta.3", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.3.tgz", - "integrity": "sha512-SeAwG9LgWijWLtWldBWPwUUA1rAg2OKBG37dtSGOTYvBkUstWxAi2hXS0pX9JXbHPDxs0DnVc5tXrXsY821E+w==", + "version": "1.1.0-beta43", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta43.tgz", + "integrity": "sha512-CYyIQogRs97Rfjo0WKyku8V56Bm4WyWUijrbWDs5LJ+ZmsUW2gqbVAEpD+1gtA7dEZ6v1A08GzfqsDuIl/eRqw==", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/remote/package.json b/remote/package.json index 1e20aa81c44c4..479adcd5410f4 100644 --- a/remote/package.json +++ b/remote/package.json @@ -33,7 +33,7 @@ "kerberos": "2.1.1", "minimist": "^1.2.8", "native-watchdog": "^1.4.1", - "node-pty": "^1.2.0-beta.3", + "node-pty": "^1.1.0-beta43", "tas-client": "0.3.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a6b87ac6aec5d..f069e02eca26e 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1118,6 +1118,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.whenReadyPromise.complete(); Promises.settled(layoutRestoredPromises).finally(() => { + if ( + this.editorService.editors.length === 0 && // no editors opened or restored + this.isVisible(Parts.AUXILIARYBAR_PART) && // auxiliary bar is visible + !this.hasFocus(Parts.AUXILIARYBAR_PART) && // auxiliary bar does not have focus yet + !this.environmentService.enableSmokeTestDriver // not in smoke test mode (where focus is sensitive) + ) { + this.focusPart(Parts.AUXILIARYBAR_PART); + } + this.restored = true; this.whenRestoredPromise.complete(); }); diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts index 64f4aee45dea9..c7a62628b104f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts @@ -10,7 +10,6 @@ import * as DOM from '../../../../../base/browser/dom.js'; import { Button, IButtonOptions } from '../../../../../base/browser/ui/button/button.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { ILanguageModelsService, IUserFriendlyLanguageModel } from '../../../chat/common/languageModels.js'; -import { ILanguageModelsConfigurationService } from '../../common/languageModelsConfiguration.js'; import { localize } from '../../../../../nls.js'; import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; @@ -712,7 +711,6 @@ class ActionsColumnRenderer extends ModelsTableColumnRenderer { diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index 5df9ba998b7ec..40dd8f85483f6 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -316,6 +316,8 @@ export interface ILanguageModelsService { addLanguageModelsProviderGroup(name: string, vendorId: string, configuration: IStringDictionary | undefined): Promise; + removeLanguageModelsProviderGroup(vendorId: string, providerGroupName: string): Promise; + configureLanguageModelsProviderGroup(vendorId: string, name?: string): Promise; } @@ -402,7 +404,8 @@ export const languageModelChatProviderExtensionPoint = ExtensionsRegistry.regist export class LanguageModelsService implements ILanguageModelsService { - private static SECRET_KEY = '${input:{0}}'; + private static SECRET_KEY_PREFIX = 'chat.lm.secret.'; + private static SECRET_INPUT = '${input:{0}}'; readonly _serviceBrand: undefined; @@ -753,6 +756,23 @@ export class LanguageModelsService implements ILanguageModelsService { await this._languageModelsConfigurationService.addLanguageModelsProviderGroup(languageModelProviderGroup); } + async removeLanguageModelsProviderGroup(vendorId: string, providerGroupName: string): Promise { + const vendor = this.getVendors().find(({ vendor }) => vendor === vendorId); + if (!vendor) { + throw new Error(`Vendor ${vendorId} not found.`); + } + + const languageModelProviderGroups = this._languageModelsConfigurationService.getLanguageModelsProviderGroups(); + const existing = languageModelProviderGroups.find(g => g.vendor === vendorId && g.name === providerGroupName); + + if (!existing) { + throw new Error(`Language model provider group ${providerGroupName} for vendor ${vendorId} not found.`); + } + + await this._deleteSecretsInConfiguration(existing, vendor.configuration); + await this._languageModelsConfigurationService.removeLanguageModelsProviderGroup(existing); + } + private canConfigure(configuration: IStringDictionary, schema: IJSONSchema): boolean { if (schema.additionalProperties) { return true; @@ -969,7 +989,7 @@ export class LanguageModelsService implements ILanguageModelsService { } private encodeSecretKey(property: string): string { - return format(LanguageModelsService.SECRET_KEY, property); + return format(LanguageModelsService.SECRET_INPUT, property); } private decodeSecretKey(secretInput: unknown): string | undefined { @@ -1017,7 +1037,7 @@ export class LanguageModelsService implements ILanguageModelsService { for (const key in configuration) { let value = configuration[key]; if (schema.properties?.[key]?.secret && isString(value)) { - const secretKey = `secret.${hash(generateUuid())}`; + const secretKey = `${LanguageModelsService.SECRET_KEY_PREFIX}${hash(generateUuid()).toString(16)}`; await this._secretStorageService.set(secretKey, value); value = this.encodeSecretKey(secretKey); } @@ -1027,6 +1047,23 @@ export class LanguageModelsService implements ILanguageModelsService { return { name, vendor, ...result }; } + private async _deleteSecretsInConfiguration(group: ILanguageModelsProviderGroup, schema: IJSONSchema | undefined): Promise { + if (!schema) { + return; + } + + const { vendor, name, range, ...configuration } = group; + for (const key in configuration) { + const value = group[key]; + if (schema.properties?.[key]?.secret) { + const secretKey = this.decodeSecretKey(value); + if (secretKey) { + await this._secretStorageService.delete(secretKey); + } + } + } + } + dispose() { this._store.dispose(); this._providers.clear(); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatManagement/chatModelsViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatManagement/chatModelsViewModel.test.ts index 53ab0d26203d5..6b0adcdd5a5d4 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatManagement/chatModelsViewModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatManagement/chatModelsViewModel.test.ts @@ -115,6 +115,9 @@ class MockLanguageModelsService implements ILanguageModelsService { async fetchLanguageModelGroups(vendor: string): Promise { return this.modelGroups.get(vendor) || []; } + + async removeLanguageModelsProviderGroup(vendorId: string, providerGroupName: string): Promise { + } } class MockChatEntitlementService implements IChatEntitlementService { diff --git a/src/vs/workbench/contrib/chat/test/common/languageModels.ts b/src/vs/workbench/contrib/chat/test/common/languageModels.ts index 914a99f9cd2e8..f38801abec300 100644 --- a/src/vs/workbench/contrib/chat/test/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/test/common/languageModels.ts @@ -71,4 +71,7 @@ export class NullLanguageModelsService implements ILanguageModelsService { async addLanguageModelsProviderGroup(name: string, vendorId: string, configuration: IStringDictionary | undefined): Promise { } + + async removeLanguageModelsProviderGroup(vendorId: string, providerGroupName: string): Promise { + } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 7491ecc8c1669..3b27190d3ca8b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -104,6 +104,8 @@ export class InlineChatController implements IEditorContribution { return editor.getContribution(InlineChatController.ID) ?? undefined; } + private static _selectVendorDefaultLanguageModel: boolean = true; + private readonly _store = new DisposableStore(); private readonly _isActiveController = observableValue(this, false); private readonly _zone: Lazy; @@ -125,7 +127,7 @@ export class InlineChatController implements IEditorContribution { @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, @ICodeEditorService codeEditorService: ICodeEditorService, @IContextKeyService contextKeyService: IContextKeyService, - @IConfigurationService configurationService: IConfigurationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @ISharedWebContentExtractorService private readonly _webContentExtractorService: ISharedWebContentExtractorService, @IFileService private readonly _fileService: IFileService, @IChatAttachmentResolveService private readonly _chatAttachmentResolveService: IChatAttachmentResolveService, @@ -135,7 +137,7 @@ export class InlineChatController implements IEditorContribution { ) { const ctxInlineChatVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); - const notebookAgentConfig = observableConfigValue(InlineChatConfigKeys.notebookAgent, false, configurationService); + const notebookAgentConfig = observableConfigValue(InlineChatConfigKeys.notebookAgent, false, this._configurationService); this._zone = new Lazy(() => { @@ -200,6 +202,12 @@ export class InlineChatController implements IEditorContribution { () => Promise.resolve(), ); + this._store.add(result); + + this._store.add(result.widget.chatWidget.input.onDidChangeCurrentLanguageModel(newModel => { + InlineChatController._selectVendorDefaultLanguageModel = Boolean(newModel.metadata.isDefault); + })); + result.domNode.classList.add('inline-chat-2'); return result; @@ -427,6 +435,22 @@ export class InlineChatController implements IEditorContribution { const session = this._inlineChatSessionService.createSession(this._editor); + + // fallback to the default model of the selected vendor unless an explicit selection was made for the session + // or unless the user has chosen to persist their model choice + const persistModelChoice = this._configurationService.getValue(InlineChatConfigKeys.PersistModelChoice); + const model = this._zone.value.widget.chatWidget.input.selectedLanguageModel; + if (!persistModelChoice && InlineChatController._selectVendorDefaultLanguageModel && model && !model.metadata.isDefault) { + const ids = await this._languageModelService.selectLanguageModels({ vendor: model.metadata.vendor }, false); + for (const identifier of ids) { + const candidate = this._languageModelService.lookupLanguageModel(identifier); + if (candidate?.isDefault) { + this._zone.value.widget.chatWidget.input.setCurrentLanguageModel({ metadata: candidate, identifier }); + break; + } + } + } + // ADD diagnostics const entries: IChatRequestVariableEntry[] = []; for (const [range, marker] of this._markerDecorationsService.getLiveMarkers(uri)) { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index cc0bd191ff2e1..83a96e87b0599 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -20,6 +20,7 @@ export const enum InlineChatConfigKeys { /** @deprecated do not read on client */ EnableV2 = 'inlineChat.enableV2', notebookAgent = 'inlineChat.notebookAgent', + PersistModelChoice = 'inlineChat.persistModelChoice', } Registry.as(Extensions.Configuration).registerConfiguration({ @@ -52,6 +53,11 @@ Registry.as(Extensions.Configuration).registerConfigurat experiment: { mode: 'startup' } + }, + [InlineChatConfigKeys.PersistModelChoice]: { + description: localize('persistModelChoice', "Whether to persist the selected language model choice across inline chat sessions. The default is not to persist and to use the vendor's default model for inline chat because that yields the best experience."), + default: false, + type: 'boolean' } } }); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 8a6f2eae90506..0839b94115add 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -379,7 +379,7 @@ export class GettingStartedPage extends EditorPane { this.editorInput.selectedStep = options?.selectedStep; this.container.classList.remove('animatable'); - await this.buildCategoriesSlide(); + await this.buildCategoriesSlide(options?.preserveFocus); if (this.shouldAnimate()) { setTimeout(() => this.container.classList.add('animatable'), 0); } @@ -800,7 +800,7 @@ export class GettingStartedPage extends EditorPane { return ''; } - private async selectStep(id: string | undefined, delayFocus = true) { + private async selectStep(id: string | undefined, delayFocus = true, preserveFocus?: boolean) { if (!this.editorInput) { return; } @@ -829,7 +829,9 @@ export class GettingStartedPage extends EditorPane { } } }); - setTimeout(() => (stepElement as HTMLElement).focus(), delayFocus && this.shouldAnimate() ? SLIDE_TRANSITION_TIME_MS : 0); + if (!preserveFocus) { + setTimeout(() => (stepElement as HTMLElement).focus(), delayFocus && this.shouldAnimate() ? SLIDE_TRANSITION_TIME_MS : 0); + } this.editorInput.selectedStep = id; @@ -885,7 +887,7 @@ export class GettingStartedPage extends EditorPane { parent.appendChild(this.container); } - private async buildCategoriesSlide() { + private async buildCategoriesSlide(preserveFocus?: boolean) { this.categoriesSlideDisposables.clear(); const showOnStartupCheckbox = new Toggle({ @@ -974,13 +976,13 @@ export class GettingStartedPage extends EditorPane { this.gettingStartedCategories = this.gettingStartedService.getWalkthroughs(); this.currentWalkthrough = this.gettingStartedCategories.find(category => category.id === editorInput.selectedCategory); if (this.currentWalkthrough) { - this.buildCategorySlide(editorInput.selectedCategory, editorInput.selectedStep); + this.buildCategorySlide(editorInput.selectedCategory, editorInput.selectedStep, preserveFocus); this.setSlide('details'); return; } } else { - this.buildCategorySlide(editorInput.selectedCategory, editorInput.selectedStep); + this.buildCategorySlide(editorInput.selectedCategory, editorInput.selectedStep, preserveFocus); this.setSlide('details'); return; } @@ -1001,7 +1003,7 @@ export class GettingStartedPage extends EditorPane { this.currentWalkthrough = first; this.editorInput.selectedCategory = this.currentWalkthrough?.id; this.editorInput.walkthroughPageTitle = this.currentWalkthrough.walkthroughPageTitle; - this.buildCategorySlide(this.editorInput.selectedCategory, undefined); + this.buildCategorySlide(this.editorInput.selectedCategory, undefined, preserveFocus); this.setSlide('details', true /* firstLaunch */); return; } @@ -1479,7 +1481,7 @@ export class GettingStartedPage extends EditorPane { super.clearInput(); } - private buildCategorySlide(categoryID: string, selectedStep?: string) { + private buildCategorySlide(categoryID: string, selectedStep?: string, preserveFocus?: boolean) { if (!this.editorInput) { return; } @@ -1625,7 +1627,7 @@ export class GettingStartedPage extends EditorPane { reset(this.stepsContent, categoryDescriptorComponent, stepListComponent, this.stepMediaComponent, categoryFooter); const toExpand = category.steps.find(step => this.contextService.contextMatchesRules(step.when) && !step.done) ?? category.steps[0]; - this.selectStep(selectedStep ?? toExpand.id, !selectedStep); + this.selectStep(selectedStep ?? toExpand.id, !selectedStep, preserveFocus); this.detailsScrollbar.scanDomNode(); this.detailsPageScrollbar?.scanDomNode(); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts index 8a08bed9a3214..41b1f95b8c0a6 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts @@ -16,7 +16,7 @@ import { ILifecycleService, LifecyclePhase, StartupKind } from '../../../service import { Disposable, } from '../../../../base/common/lifecycle.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { joinPath } from '../../../../base/common/resources.js'; -import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; +import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; import { GettingStartedEditorOptions, GettingStartedInput, gettingStartedInputTypeId } from './gettingStartedInput.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; @@ -27,9 +27,10 @@ import { INotificationService } from '../../../../platform/notification/common/n import { localize } from '../../../../nls.js'; import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; import { TerminalCommandId } from '../../terminal/common/terminal.js'; -import { ILogService } from '../../../../platform/log/common/log.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { AuxiliaryBarMaximizedContext } from '../../../common/contextkeys.js'; +import { mainWindow } from '../../../../base/browser/window.js'; +import { getActiveElement } from '../../../../base/browser/dom.js'; export const restoreWalkthroughsConfigurationKey = 'workbench.welcomePage.restorableWalkthroughs'; export type RestoreWalkthroughsConfigurationValue = { folder: string; category?: string; step?: string }; @@ -89,7 +90,6 @@ export class StartupPageRunnerContribution extends Disposable implements IWorkbe @ICommandService private readonly commandService: ICommandService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IStorageService private readonly storageService: IStorageService, - @ILogService private readonly logService: ILogService, @INotificationService private readonly notificationService: INotificationService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { @@ -128,7 +128,7 @@ export class StartupPageRunnerContribution extends Disposable implements IWorkbe return; } - const enabled = isStartupPageEnabled(this.configurationService, this.contextService, this.environmentService, this.logService); + const enabled = isStartupPageEnabled(this.configurationService, this.contextService, this.environmentService); if (enabled && this.lifecycleService.startupKind !== StartupKind.ReloadedWindow) { // Open the welcome even if we opened a set of default editors @@ -155,7 +155,7 @@ export class StartupPageRunnerContribution extends Disposable implements IWorkbe const restoreData: RestoreWalkthroughsConfigurationValue = JSON.parse(toRestore); const currentWorkspace = this.contextService.getWorkspace(); if (restoreData.folder === UNKNOWN_EMPTY_WINDOW_WORKSPACE.id || restoreData.folder === currentWorkspace.folders[0].uri.toString()) { - const options: GettingStartedEditorOptions = { selectedCategory: restoreData.category, selectedStep: restoreData.step, pinned: false }; + const options: GettingStartedEditorOptions = { selectedCategory: restoreData.category, selectedStep: restoreData.step, pinned: false, preserveFocus: this.shouldPreserveFocus() }; this.editorService.openEditor({ resource: GettingStartedInput.RESOURCE, options @@ -186,7 +186,7 @@ export class StartupPageRunnerContribution extends Disposable implements IWorkbe this.commandService.executeCommand('markdown.showPreview', null, readmes.filter(isMarkDown), { locked: true }).catch(error => { this.notificationService.error(localize('startupPage.markdownPreviewError', 'Could not open markdown preview: {0}.\n\nPlease make sure the markdown extension is enabled.', error.message)); }), - this.editorService.openEditors(readmes.filter(readme => !isMarkDown(readme)).map(readme => ({ resource: readme }))), + this.editorService.openEditors(readmes.filter(readme => !isMarkDown(readme)).map(readme => ({ resource: readme, options: { preserveFocus: this.shouldPreserveFocus() } }))), ]); } else { // If no readme is found, default to showing the welcome page. @@ -204,17 +204,30 @@ export class StartupPageRunnerContribution extends Disposable implements IWorkbe return; } - const options: GettingStartedEditorOptions = editor ? { pinned: false, index: 0, showTelemetryNotice } : { pinned: false, showTelemetryNotice }; if (startupEditorTypeID === gettingStartedInputTypeId) { this.editorService.openEditor({ resource: GettingStartedInput.RESOURCE, - options, + options: { + index: editor ? 0 : undefined, + pinned: false, + preserveFocus: this.shouldPreserveFocus(), + ...{ showTelemetryNotice } + }, }); } } + + private shouldPreserveFocus(): boolean { + const activeElement = getActiveElement(); + if (!activeElement || activeElement === mainWindow.document.body || this.layoutService.hasFocus(Parts.EDITOR_PART)) { + return false; // steal focus if nothing meaningful is focused or editor area has focus + } + + return true; // do not steal focus + } } -function isStartupPageEnabled(configurationService: IConfigurationService, contextService: IWorkspaceContextService, environmentService: IWorkbenchEnvironmentService, logService: ILogService) { +function isStartupPageEnabled(configurationService: IConfigurationService, contextService: IWorkspaceContextService, environmentService: IWorkbenchEnvironmentService) { if (environmentService.skipWelcome) { return false; }