From 055f83c0621e5e96f02bd5822f29d7515d250436 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 5 Dec 2024 11:10:04 -0800 Subject: [PATCH 01/37] Get correct container for inline chat overflow widgets (#235354) Fixes #235353 --- .../workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 4 ++-- src/vs/workbench/test/browser/workbenchTestServices.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index d82d9e99ddd32..131cb75876625 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, Dimension, getActiveElement, getTotalHeight, h, reset, trackFocus } from '../../../../base/browser/dom.js'; +import { $, Dimension, getActiveElement, getTotalHeight, getWindow, h, reset, trackFocus } from '../../../../base/browser/dom.js'; import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; @@ -520,7 +520,7 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget { @IHoverService hoverService: IHoverService, @ILayoutService layoutService: ILayoutService ) { - const overflowWidgetsNode = layoutService.mainContainer.appendChild($('.inline-chat-overflow.monaco-editor')); + const overflowWidgetsNode = layoutService.getContainer(getWindow(_parentEditor.getContainerDomNode())).appendChild($('.inline-chat-overflow.monaco-editor')); super(location, { ...options, chatWidgetViewOptions: { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 5a52c698e2d58..4a850d040d8d2 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -642,7 +642,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { hasMainWindowBorder(): boolean { return false; } getMainWindowBorderRadius(): string | undefined { return undefined; } isVisible(_part: Parts): boolean { return true; } - getContainer(): HTMLElement { return null!; } + getContainer(): HTMLElement { return mainWindow.document.body; } whenContainerStylesLoaded() { return undefined; } isTitleBarHidden(): boolean { return false; } isStatusBarHidden(): boolean { return false; } From afad311fdf4458cb02a37b083c015473974e4cfe Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 5 Dec 2024 19:21:51 +0000 Subject: [PATCH 02/37] Always show chat welcome view when signed out (#235428) --- .../contrib/chat/browser/chatViewPane.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index d4ea33c62ab18..3083c9f209330 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -26,6 +26,7 @@ import { SIDE_BAR_FOREGROUND } from '../../../common/theme.js'; import { IViewDescriptorService } from '../../../common/views.js'; import { IChatViewTitleActionContext } from '../common/chatActions.js'; import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatModelInitState, IChatModel } from '../common/chatModel.js'; import { CHAT_PROVIDER_ID } from '../common/chatParticipantContribTypes.js'; import { IChatService } from '../common/chatService.js'; @@ -99,6 +100,13 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this._onDidChangeViewWelcomeState.fire(); })); + + const keysToWatch = new Set(ChatContextKeys.Setup.signedOut.key); + this._register(this.contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(keysToWatch)) { + this._onDidChangeViewWelcomeState.fire(); + } + })); } override getActionsContext(): IChatViewTitleActionContext | undefined { @@ -130,10 +138,11 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { } override shouldShowWelcome(): boolean { + const signedOut = this.contextKeyService.getContextKeyValue(ChatContextKeys.Setup.signedOut.key); const noPersistedSessions = !this.chatService.hasSessions(); - const shouldShow = this.didUnregisterProvider || !this._widget?.viewModel && noPersistedSessions || this.defaultParticipantRegistrationFailed; - this.logService.trace(`ChatViewPane#shouldShowWelcome(${this.chatOptions.location}) = ${shouldShow}: didUnregister=${this.didUnregisterProvider} || noViewModel:${!this._widget?.viewModel} && noPersistedSessions=${noPersistedSessions} || defaultParticipantRegistrationFailed=${this.defaultParticipantRegistrationFailed}`); - return shouldShow; + const shouldShow = this.didUnregisterProvider || !this._widget?.viewModel && noPersistedSessions || this.defaultParticipantRegistrationFailed || signedOut; + this.logService.trace(`ChatViewPane#shouldShowWelcome(${this.chatOptions.location}) = ${shouldShow}: didUnregister=${this.didUnregisterProvider} || noViewModel:${!this._widget?.viewModel} && noPersistedSessions=${noPersistedSessions} || defaultParticipantRegistrationFailed=${this.defaultParticipantRegistrationFailed} || signedOut=${signedOut}`); + return !!shouldShow; } private getSessionId() { From c11b1b60980a32c48eb45c98343d50975e979893 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 5 Dec 2024 19:38:12 +0000 Subject: [PATCH 03/37] Ensure quota statusbar entry shows up after reloading the window (#235429) --- src/vs/workbench/contrib/chat/browser/chatQuotasService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts b/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts index 7d86a35629735..ba1ff798c35b1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts @@ -239,7 +239,7 @@ export class ChatQuotasStatusBarEntry extends Disposable implements IWorkbenchCo ) { super(); - this._register(this.chatQuotasService.onDidChangeQuotas(() => this.updateStatusbarEntry())); + this._register(Event.runAndSubscribe(this.chatQuotasService.onDidChangeQuotas, () => this.updateStatusbarEntry())); } private updateStatusbarEntry(): void { From d3273e5c2d7920f9a97e865edb0dc9af3ba00d34 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 5 Dec 2024 11:41:24 -0800 Subject: [PATCH 04/37] fix: set better hover background for Add Files button (#235430) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index fc60c5f5dbb06..3f80257dbdc50 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -696,12 +696,16 @@ have to be updated for changes to the rules above, or to support more deeply nes color: var(--vscode-button-secondaryForeground); } -.interactive-session .chat-editing-session .chat-editing-session-toolbar-actions .monaco-button.secondary:hover, .interactive-session .chat-editing-session .chat-editing-session-actions .monaco-button.secondary:hover { background-color: var(--vscode-button-secondaryHoverBackground); color: var(--vscode-button-secondaryForeground); } +/* The Add Files button is currently implemented as a secondary button but should not have the secondary button background */ +.interactive-session .chat-editing-session .chat-editing-session-toolbar-actions .monaco-button.secondary:hover { + background-color: var(--vscode-toolbar-hoverBackground); +} + .interactive-session .chat-editing-session .chat-editing-session-actions .monaco-button.secondary.monaco-text-button.codicon:not(.disabled):hover, .interactive-session .chat-editing-session .chat-editing-session-toolbar-actions .monaco-button:hover { background-color: var(--vscode-toolbar-hoverBackground); From c59c59ba4ece06dd2153d73f94c5ffd0632d0ffd Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 5 Dec 2024 12:18:32 -0800 Subject: [PATCH 05/37] fix: remove chat edits file suggestions when working set is emptied (#235433) --- .../contrib/chat/browser/chatEditing/chatEditingActions.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index 5a067cee72f27..8a2ac519e78dd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -109,6 +109,13 @@ registerAction2(class RemoveFileFromWorkingSet extends WorkingSetAction { for (const uri of uris) { chatWidget.attachmentModel.delete(uri.toString()); } + + // If there are now only suggested files in the working set, also clear those + const entries = [...currentEditingSession.workingSet.entries()]; + const suggestedFiles = entries.filter(([_, state]) => state.state === WorkingSetEntryState.Suggested); + if (suggestedFiles.length === entries.length && !chatWidget.attachmentModel.attachments.find((v) => v.isFile && URI.isUri(v.value))) { + currentEditingSession.remove(WorkingSetEntryRemovalReason.Programmatic, ...entries.map(([uri,]) => uri)); + } } }); From 039b3503ca9f43d1e9b2dd2b89206414c722500b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 5 Dec 2024 12:29:33 -0800 Subject: [PATCH 06/37] Fix chat editor overflow (#235212) * Fix chat overflow Use layout service instead Fixes #234902 * Use `getContainer` --- src/vs/workbench/contrib/chat/browser/chatViewPane.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 3083c9f209330..43af70f2eadc3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $ } from '../../../../base/browser/dom.js'; +import { $, getWindow } from '../../../../base/browser/dom.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { MarshalledId } from '../../../../base/common/marshallingIds.js'; @@ -14,6 +14,7 @@ import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; @@ -67,6 +68,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { @IChatService private readonly chatService: IChatService, @IChatAgentService private readonly chatAgentService: IChatAgentService, @ILogService private readonly logService: ILogService, + @ILayoutService private readonly layoutService: ILayoutService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); @@ -164,7 +166,9 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); const locationBasedColors = this.getLocationBasedColors(); - const editorOverflowNode = $('.chat-editor-overflow-widgets'); + const editorOverflowNode = this.layoutService.getContainer(getWindow(parent)).appendChild($('.chat-editor-overflow.monaco-editor')); + this._register({ dispose: () => editorOverflowNode.remove() }); + this._widget = this._register(scopedInstantiationService.createInstance( ChatWidget, this.chatOptions.location, @@ -181,7 +185,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { }, }, enableImplicitContext: this.chatOptions.location === ChatAgentLocation.Panel, - // editorOverflowWidgetsDomNode: editorOverflowNode, + editorOverflowWidgetsDomNode: editorOverflowNode, }, { listForeground: SIDE_BAR_FOREGROUND, @@ -196,7 +200,6 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { })); this._register(this._widget.onDidClear(() => this.clear())); this._widget.render(parent); - parent.appendChild(editorOverflowNode); const sessionId = this.getSessionId(); const disposeListener = this._register(this.chatService.onDidDisposeSession((e) => { From 3bb6b62e8d92436c09b6a08f320217a6f0fb8228 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 5 Dec 2024 21:30:14 +0100 Subject: [PATCH 07/37] chat - setup tweaks (#235432) --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 916bb9b66ee31..3a1a9b904865b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -179,10 +179,9 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr showCopilotView(viewsService); ensureSideBarChatViewSize(400, viewDescriptorService, layoutService); - // Setup should be kicked off immediately - if (typeof startSetup === 'boolean' && startSetup) { + if (startSetup === true && !ASK_FOR_PUBLIC_CODE_MATCHES) { const controller = that.controller.value; - controller.setup({ publicCodeSuggestions: true }); // TODO@sbatten pass in as argument + controller.setup({ publicCodeSuggestions: true }); } configurationService.updateValue('chat.commandCenter.enabled', true); From 3781e5f084838b16d9bb16998cd476f232b2ff5f Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 5 Dec 2024 12:30:31 -0800 Subject: [PATCH 08/37] fix: don't filter suggested files from Add Files picker (#235431) --- .../contrib/chat/browser/actions/chatContextActions.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index a7ce13ef1d6ad..c812ec0d053d2 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -43,7 +43,7 @@ import { ISymbolQuickPickItem, SymbolsQuickAccessProvider } from '../../../searc import { SearchContext } from '../../../search/common/constants.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { IChatEditingService } from '../../common/chatEditingService.js'; +import { IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IChatRequestVariableEntry } from '../../common/chatModel.js'; import { ChatRequestAgentPart } from '../../common/chatParserTypes.js'; import { IChatVariableData, IChatVariablesService } from '../../common/chatVariables.js'; @@ -794,8 +794,10 @@ export class AttachContextAction extends Action2 { // Avoid attaching the same context twice const attachedContext = widget.attachmentModel.getAttachmentIDs(); if (chatEditingService) { - for (const file of chatEditingService.currentEditingSessionObs.get()?.workingSet.keys() ?? []) { - attachedContext.add(this._getFileContextId({ resource: file })); + for (const [file, state] of chatEditingService.currentEditingSessionObs.get()?.workingSet.entries() ?? []) { + if (state.state !== WorkingSetEntryState.Suggested) { + attachedContext.add(this._getFileContextId({ resource: file })); + } } } From ac49e440694c6cf34b09c226deb399a50f064d97 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 5 Dec 2024 13:30:09 -0800 Subject: [PATCH 09/37] mention GH account (#235438) --- .../welcomeGettingStarted/common/gettingStartedContent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 850d0cb66505c..1b8194ea5158b 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -211,7 +211,7 @@ export const startEntries: GettingStartedStartEntryContent = [ const Button = (title: string, href: string) => `[${title}](${href})`; const CopilotStepTitle = localize('gettingStarted.copilotSetup.title', "Use AI features with Copilot for free"); -const CopilotDescription = localize({ key: 'gettingStarted.copilotSetup.description', comment: ['{Locked="["}', '{Locked="]({0})"}'] }, "Write code faster and smarter with [Copilot]({0}) for free.", product.defaultChatAgent?.documentationUrl ?? ''); +const CopilotDescription = localize({ key: 'gettingStarted.copilotSetup.description', comment: ['{Locked="["}', '{Locked="]({0})"}'] }, "Write code faster and smarter with [Copilot]({0}) for free with your GitHub account.", product.defaultChatAgent?.documentationUrl ?? ''); const CopilotTermsString = localize({ key: 'copilotTerms', comment: ['{Locked="["}', '{Locked="]({0})"}', '{Locked="]({1})"}'] }, "By continuing, you agree to Copilot [Terms]({0}) and [Privacy Policy]({1}).", product.defaultChatAgent?.termsStatementUrl ?? '', product.defaultChatAgent?.privacyStatementUrl ?? ''); const CopilotSignedOutButton = Button(localize('setupCopilotButton.signIn', "Sign in to use Copilot"), `command:workbench.action.chat.triggerSetup?${encodeURIComponent(JSON.stringify([true]))}`); const CopilotSignedInButton = Button(localize('setupCopilotButton.setup', "Setup Copilot"), `command:workbench.action.chat.triggerSetup?${encodeURIComponent(JSON.stringify([true]))}`); From f9e82a4890b75adf818caf5914865e8615020cf9 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 5 Dec 2024 13:47:39 -0800 Subject: [PATCH 10/37] fix: align chat editing progress spinner with text (#235439) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 3f80257dbdc50..502ef69725e48 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -312,8 +312,11 @@ top: 2px; } - /* But codicons in toolbars and other widgets assume the natural position of the codicon */ - .chat-codeblock-pill-widget .codicon, + .chat-codeblock-pill-widget .codicon { + top: -1px; + } + + /* But codicons in toolbars assume the natural position of the codicon */ .monaco-toolbar .codicon { position: initial; top: initial; From 9452402131b8a691d3f4aad10e956aead8d698c1 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 5 Dec 2024 13:51:44 -0800 Subject: [PATCH 11/37] fix: correct hover for chat editing code block pill with one insertion/deletion (#235440) --- .../chat/browser/chatContentParts/chatMarkdownContentPart.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index d0ac0fec70166..36ba8b160966c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -388,8 +388,8 @@ class CollapsedCodeBlock extends Disposable { } labelAdded.textContent = `+${addedLines}`; labelRemoved.textContent = `-${removedLines}`; - const insertionsFragment = addedLines === 1 ? localize('chat.codeblock.insertions.one', "{0} insertion") : localize('chat.codeblock.insertions', "{0} insertions", addedLines); - const deletionsFragment = removedLines === 1 ? localize('chat.codeblock.deletions.one', "{0} deletion") : localize('chat.codeblock.deletions', "{0} deletions", removedLines); + const insertionsFragment = addedLines === 1 ? localize('chat.codeblock.insertions.one', "1 insertion") : localize('chat.codeblock.insertions', "{0} insertions", addedLines); + const deletionsFragment = removedLines === 1 ? localize('chat.codeblock.deletions.one', "1 deletion") : localize('chat.codeblock.deletions', "{0} deletions", removedLines); this.element.ariaLabel = this.element.title = localize('summary', 'Edited {0}, {1}, {2}', iconText, insertionsFragment, deletionsFragment); } } From 03b64eb40c23711f6f5be5adb88990729d85d81d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 5 Dec 2024 22:26:13 +0000 Subject: [PATCH 12/37] Use new copilot-large icon (#235442) --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 81808 -> 82204 bytes src/vs/base/common/codiconsLibrary.ts | 1 + .../contrib/chat/browser/chatSetup.ts | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 4a4d15cc2df09d0928d922b25be0135d064cd68b..95c8a7a04c1c7b285ddd1e6ed88b0263c7333725 100644 GIT binary patch delta 7140 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 disposables.add(this.instantiationService.createInstance(ChatSetupWelcomeContent, this.controller.value, this.context)).element, }); } From 14b81ef6fe1c3ac40b1f7f0d60ffde57bc1318a4 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 5 Dec 2024 17:28:11 -0800 Subject: [PATCH 13/37] shorten the main walkthrough (#235447) --- .../common/gettingStartedContent.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 1b8194ea5158b..02350e772581c 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -283,6 +283,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ id: 'settings', title: localize('gettingStarted.settings.title', "Tune your settings"), description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), + when: '!config.chat.experimental.offerSetup', media: { type: 'svg', altText: 'VS Code Settings', path: 'settings.svg' }, @@ -291,12 +292,22 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ id: 'settingsSync', title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"), description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')), - when: 'syncStatus != uninitialized', + when: '!config.chat.experimental.offerSetup && syncStatus != uninitialized', completionEvents: ['onEvent:sync-enabled'], media: { type: 'svg', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: 'settingsSync.svg' }, }, + { + id: 'settingsAndSync', + title: localize('gettingStarted.settings.title', "Tune your settings"), + description: localize('gettingStarted.settingsAndSync.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. [Back up and sync](command:workbench.userDataSync.actions.turnOn) your essential customizations across all your devices.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), + when: 'config.chat.experimental.offerSetup && syncStatus != uninitialized', + completionEvents: ['onEvent:sync-enabled'], + media: { + type: 'svg', altText: 'VS Code Settings', path: 'settings.svg' + }, + }, { id: 'commandPaletteTask', title: localize('gettingStarted.commandPalette.title', "Unlock productivity with the Command Palette "), @@ -307,7 +318,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ id: 'pickAFolderTask-Mac', title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFileFolder')), - when: 'isMac && workspaceFolderCount == 0', + when: '!config.chat.experimental.offerSetup && isMac && workspaceFolderCount == 0', media: { type: 'svg', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.svg' } @@ -316,7 +327,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ id: 'pickAFolderTask-Other', title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFolder')), - when: '!isMac && workspaceFolderCount == 0', + when: '!config.chat.experimental.offerSetup && !isMac && workspaceFolderCount == 0', media: { type: 'svg', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.svg' } From 6287936e02750767221492eb144931a234e90208 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 6 Dec 2024 02:05:41 +0000 Subject: [PATCH 14/37] Better condition for chat welcome view (#235443) * Better condition for chat welcome view * Undupe --- .../contrib/chat/browser/chatSetup.ts | 37 ++++++++++--------- .../contrib/chat/browser/chatViewPane.ts | 11 +++--- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 6011d5be3f077..411bb5bfeed37 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -94,6 +94,25 @@ const ASK_FOR_PUBLIC_CODE_MATCHES = false; // TODO@bpasero revisit this const TRIGGER_SETUP_COMMAND_ID = 'workbench.action.chat.triggerSetup'; const TRIGGER_SETUP_COMMAND_LABEL = localize2('triggerChatSetup', "Use AI Features with Copilot for Free..."); +export const SetupWelcomeViewKeys = new Set([ChatContextKeys.Setup.triggered.key, ChatContextKeys.Setup.installed.key, ChatContextKeys.Setup.signedOut.key, ChatContextKeys.Setup.canSignUp.key]); +export const SetupWelcomeViewCondition = ContextKeyExpr.and( + ContextKeyExpr.has('config.chat.experimental.offerSetup'), + ContextKeyExpr.or( + ContextKeyExpr.and( + ChatContextKeys.Setup.triggered, + ChatContextKeys.Setup.installed.negate() + ), + ContextKeyExpr.and( + ChatContextKeys.Setup.canSignUp, + ChatContextKeys.Setup.installed + ), + ContextKeyExpr.and( + ChatContextKeys.Setup.signedOut, + ChatContextKeys.Setup.installed + ) + ) +)!; + export class ChatSetupContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.chat.setup'; @@ -119,23 +138,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr private registerChatWelcome(): void { Registry.as(ChatViewsWelcomeExtensions.ChatViewsWelcomeRegistry).register({ title: localize('welcomeChat', "Welcome to Copilot"), - when: ContextKeyExpr.and( - ContextKeyExpr.has('config.chat.experimental.offerSetup'), - ContextKeyExpr.or( - ContextKeyExpr.and( - ChatContextKeys.Setup.triggered, - ChatContextKeys.Setup.installed.negate() - ), - ContextKeyExpr.and( - ChatContextKeys.Setup.canSignUp, - ChatContextKeys.Setup.installed - ), - ContextKeyExpr.and( - ChatContextKeys.Setup.signedOut, - ChatContextKeys.Setup.installed - ) - ) - )!, + when: SetupWelcomeViewCondition, icon: Codicon.copilotLarge, content: disposables => disposables.add(this.instantiationService.createInstance(ChatSetupWelcomeContent, this.controller.value, this.context)).element, }); diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 43af70f2eadc3..485adf05ab9dd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -27,10 +27,10 @@ import { SIDE_BAR_FOREGROUND } from '../../../common/theme.js'; import { IViewDescriptorService } from '../../../common/views.js'; import { IChatViewTitleActionContext } from '../common/chatActions.js'; import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js'; -import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatModelInitState, IChatModel } from '../common/chatModel.js'; import { CHAT_PROVIDER_ID } from '../common/chatParticipantContribTypes.js'; import { IChatService } from '../common/chatService.js'; +import { SetupWelcomeViewCondition, SetupWelcomeViewKeys } from './chatSetup.js'; import { ChatWidget, IChatViewState } from './chatWidget.js'; import { ChatViewWelcomeController, IViewWelcomeDelegate } from './viewsWelcome/chatViewWelcomeController.js'; @@ -103,9 +103,8 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this._onDidChangeViewWelcomeState.fire(); })); - const keysToWatch = new Set(ChatContextKeys.Setup.signedOut.key); this._register(this.contextKeyService.onDidChangeContext(e => { - if (e.affectsSome(keysToWatch)) { + if (e.affectsSome(SetupWelcomeViewKeys)) { this._onDidChangeViewWelcomeState.fire(); } })); @@ -140,10 +139,10 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { } override shouldShowWelcome(): boolean { - const signedOut = this.contextKeyService.getContextKeyValue(ChatContextKeys.Setup.signedOut.key); + const showSetup = this.contextKeyService.contextMatchesRules(SetupWelcomeViewCondition); const noPersistedSessions = !this.chatService.hasSessions(); - const shouldShow = this.didUnregisterProvider || !this._widget?.viewModel && noPersistedSessions || this.defaultParticipantRegistrationFailed || signedOut; - this.logService.trace(`ChatViewPane#shouldShowWelcome(${this.chatOptions.location}) = ${shouldShow}: didUnregister=${this.didUnregisterProvider} || noViewModel:${!this._widget?.viewModel} && noPersistedSessions=${noPersistedSessions} || defaultParticipantRegistrationFailed=${this.defaultParticipantRegistrationFailed} || signedOut=${signedOut}`); + const shouldShow = this.didUnregisterProvider || !this._widget?.viewModel && noPersistedSessions || this.defaultParticipantRegistrationFailed || showSetup; + this.logService.trace(`ChatViewPane#shouldShowWelcome(${this.chatOptions.location}) = ${shouldShow}: didUnregister=${this.didUnregisterProvider} || noViewModel:${!this._widget?.viewModel} && noPersistedSessions=${noPersistedSessions} || defaultParticipantRegistrationFailed=${this.defaultParticipantRegistrationFailed} || showSetup=${showSetup}`); return !!shouldShow; } From c8f53ea748dcbbd12ff40d7f944e516d9cd36746 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 6 Dec 2024 07:42:27 +0100 Subject: [PATCH 15/37] chat - some fixes to setup and quotas (#235457) --- .../contrib/chat/browser/chatQuotasService.ts | 16 +++++++++++++--- .../workbench/contrib/chat/browser/chatSetup.ts | 13 ++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts b/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts index ba1ff798c35b1..6ed345aacdb9c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts @@ -32,6 +32,7 @@ export interface IChatQuotasService { readonly quotas: IChatQuotas; acceptQuotas(quotas: IChatQuotas): void; + clearQuotas(): void; } export interface IChatQuotas { @@ -113,9 +114,12 @@ export class ChatQuotasService extends Disposable implements IChatQuotasService id: MenuId.ChatCommandCenter, group: 'a_first', order: 1, - when: ContextKeyExpr.or( - ChatContextKeys.chatQuotaExceeded, - ChatContextKeys.completionsQuotaExceeded + when: ContextKeyExpr.and( + ChatContextKeys.Setup.installed, + ContextKeyExpr.or( + ChatContextKeys.chatQuotaExceeded, + ChatContextKeys.completionsQuotaExceeded + ) ) } }); @@ -221,6 +225,12 @@ export class ChatQuotasService extends Disposable implements IChatQuotasService this._onDidChangeQuotas.fire(); } + clearQuotas(): void { + if (this.quotas.chatQuotaExceeded || this.quotas.completionsQuotaExceeded) { + this.acceptQuotas({ chatQuotaExceeded: false, completionsQuotaExceeded: false, quotaResetDate: new Date(0) }); + } + } + private updateContextKeys(): void { this.chatQuotaExceededContextKey.set(this._quotas.chatQuotaExceeded); this.completionsQuotaExceededContextKey.set(this._quotas.completionsQuotaExceeded); diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 411bb5bfeed37..0e6fa39883763 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -56,6 +56,7 @@ import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID } from './chatView import { ChatViewsWelcomeExtensions, IChatViewsWelcomeContributionRegistry } from './viewsWelcome/chatViewsWelcome.js'; import { IChatQuotasService } from './chatQuotasService.js'; import { Checkbox } from '../../../../base/browser/ui/toggle/toggle.js'; +import { mainWindow } from '../../../../base/browser/window.js'; const defaultChat = { extensionId: product.defaultChatAgent?.extensionId ?? '', @@ -341,6 +342,15 @@ class ChatSetupRequests extends Disposable { this.resolve(); } })); + + this._register(this.context.onDidChange(() => { + if (!this.context.state.installed || this.context.state.entitlement === ChatEntitlement.Unknown) { + // When the extension is not installed or the user is not entitled + // make sure to clear quotas so that any indicators are also gone + this.state = { entitlement: this.state.entitlement, quotas: undefined }; + this.chatQuotasService.clearQuotas(); + } + })); } private async resolve(): Promise { @@ -737,7 +747,8 @@ class ChatSetupController extends Disposable { this.telemetryService.publicLog2('commandCenter.chatInstall', { installResult, signedIn }); - if (activeElement === getActiveElement()) { + const currentActiveElement = getActiveElement(); + if (activeElement === currentActiveElement || currentActiveElement === mainWindow.document.body) { (await showCopilotView(this.viewsService))?.focusInput(); } } From 714dc6d80480542c4ff93f5bff1b1ca21bc9429f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 6 Dec 2024 09:09:00 +0100 Subject: [PATCH 16/37] fix concurrency in publish stage (#235411) * :lipstick: * use blob leases to prevent artifact overwriting * more logs * :lipstick: --- build/azure-pipelines/common/publish.js | 122 ++++++++++++++------ build/azure-pipelines/common/publish.ts | 142 +++++++++++++++++------- 2 files changed, 188 insertions(+), 76 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index 102c5518d9b78..742db9d9e9781 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -18,6 +18,7 @@ const node_worker_threads_1 = require("node:worker_threads"); const msal_node_1 = require("@azure/msal-node"); const storage_blob_1 = require("@azure/storage-blob"); const jws = require("jws"); +const node_timers_1 = require("node:timers"); function e(name) { const result = process.env[name]; if (typeof result !== 'string') { @@ -37,6 +38,7 @@ function hashStream(hashName, stream) { var StatusCode; (function (StatusCode) { StatusCode["Pass"] = "pass"; + StatusCode["Aborted"] = "aborted"; StatusCode["Inprogress"] = "inprogress"; StatusCode["FailCanRetry"] = "failCanRetry"; StatusCode["FailDoNotRetry"] = "failDoNotRetry"; @@ -136,8 +138,13 @@ class ESRPReleaseService { if (releaseStatus.status === 'pass') { break; } + else if (releaseStatus.status === 'aborted') { + this.log(JSON.stringify(releaseStatus)); + throw new Error(`Release was aborted`); + } else if (releaseStatus.status !== 'inprogress') { - throw new Error(`Failed to submit release: ${JSON.stringify(releaseStatus)}`); + this.log(JSON.stringify(releaseStatus)); + throw new Error(`Unknown error when polling for release`); } } const releaseDetails = await this.getReleaseDetails(submitReleaseResult.operationId); @@ -470,50 +477,95 @@ function getRealType(type) { return type; } } +async function withLease(client, fn) { + const lease = client.getBlobLeaseClient(); + for (let i = 0; i < 360; i++) { // Try to get lease for 30 minutes + try { + await client.uploadData(new ArrayBuffer()); // blob needs to exist for lease to be acquired + await lease.acquireLease(60); + try { + const abortController = new AbortController(); + const refresher = new Promise((c, e) => { + abortController.signal.onabort = () => { + (0, node_timers_1.clearInterval)(interval); + c(); + }; + const interval = (0, node_timers_1.setInterval)(() => { + lease.renewLease().catch(err => { + (0, node_timers_1.clearInterval)(interval); + e(new Error('Failed to renew lease ' + err)); + }); + }, 30_000); + }); + const result = await Promise.race([fn(), refresher]); + abortController.abort(); + return result; + } + finally { + await lease.releaseLease(); + } + } + catch (err) { + if (err.statusCode !== 409 && err.statusCode !== 412) { + throw err; + } + await new Promise(c => setTimeout(c, 5000)); + } + } + throw new Error('Failed to acquire lease on blob after 30 minutes'); +} async function processArtifact(artifact, filePath) { + const log = (...args) => console.log(`[${artifact.name}]`, ...args); const match = /^vscode_(?[^_]+)_(?[^_]+)(?:_legacy)?_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); if (!match) { throw new Error(`Invalid artifact name: ${artifact.name}`); } - // getPlatform needs the unprocessedType const { cosmosDBAccessToken, blobServiceAccessToken } = JSON.parse(e('PUBLISH_AUTH_TOKENS')); const quality = e('VSCODE_QUALITY'); const version = e('BUILD_SOURCEVERSION'); - const { product, os, arch, unprocessedType } = match.groups; - const isLegacy = artifact.name.includes('_legacy'); - const platform = getPlatform(product, os, arch, unprocessedType, isLegacy); - const type = getRealType(unprocessedType); - const size = fs.statSync(filePath).size; - const stream = fs.createReadStream(filePath); - const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 - const log = (...args) => console.log(`[${artifact.name}]`, ...args); - const blobServiceClient = new storage_blob_1.BlobServiceClient(`https://${e('VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME')}.blob.core.windows.net/`, { getToken: async () => blobServiceAccessToken }); - const containerClient = blobServiceClient.getContainerClient('staging'); - const releaseService = await ESRPReleaseService.create(log, e('RELEASE_TENANT_ID'), e('RELEASE_CLIENT_ID'), e('RELEASE_AUTH_CERT'), e('RELEASE_REQUEST_SIGNING_CERT'), containerClient); const friendlyFileName = `${quality}/${version}/${path.basename(filePath)}`; - const url = `${e('PRSS_CDN_URL')}/${friendlyFileName}`; - const res = await (0, retry_1.retry)(() => fetch(url)); - if (res.status === 200) { - log(`Already released and provisioned: ${url}`); - } - else { - await releaseService.createRelease(version, filePath, friendlyFileName); - } - const asset = { platform, type, url, hash: hash.toString('hex'), sha256hash: sha256hash.toString('hex'), size, supportsFastUpdate: true }; - log('Creating asset...'); - const result = await (0, retry_1.retry)(async (attempt) => { - log(`Creating asset in Cosmos DB (attempt ${attempt})...`); - const client = new cosmos_1.CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT'), tokenProvider: () => Promise.resolve(`type=aad&ver=1.0&sig=${cosmosDBAccessToken.token}`) }); - const scripts = client.database('builds').container(quality).scripts; - const { resource: result } = await scripts.storedProcedure('createAsset').execute('', [version, asset, true]); - return result; + const blobServiceClient = new storage_blob_1.BlobServiceClient(`https://${e('VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME')}.blob.core.windows.net/`, { getToken: async () => blobServiceAccessToken }); + const leasesContainerClient = blobServiceClient.getContainerClient('leases'); + await leasesContainerClient.createIfNotExists(); + const leaseBlobClient = leasesContainerClient.getBlockBlobClient(friendlyFileName); + log(`Acquiring lease for: ${friendlyFileName}`); + await withLease(leaseBlobClient, async () => { + log(`Successfully acquired lease for: ${friendlyFileName}`); + const url = `${e('PRSS_CDN_URL')}/${friendlyFileName}`; + const res = await (0, retry_1.retry)(() => fetch(url)); + if (res.status === 200) { + log(`Already released and provisioned: ${url}`); + } + else { + const stagingContainerClient = blobServiceClient.getContainerClient('staging'); + await stagingContainerClient.createIfNotExists(); + const releaseService = await ESRPReleaseService.create(log, e('RELEASE_TENANT_ID'), e('RELEASE_CLIENT_ID'), e('RELEASE_AUTH_CERT'), e('RELEASE_REQUEST_SIGNING_CERT'), stagingContainerClient); + await (0, retry_1.retry)(() => releaseService.createRelease(version, filePath, friendlyFileName)); + } + const { product, os, arch, unprocessedType } = match.groups; + const isLegacy = artifact.name.includes('_legacy'); + const platform = getPlatform(product, os, arch, unprocessedType, isLegacy); + const type = getRealType(unprocessedType); + const size = fs.statSync(filePath).size; + const stream = fs.createReadStream(filePath); + const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 + const asset = { platform, type, url, hash: hash.toString('hex'), sha256hash: sha256hash.toString('hex'), size, supportsFastUpdate: true }; + log('Creating asset...'); + const result = await (0, retry_1.retry)(async (attempt) => { + log(`Creating asset in Cosmos DB (attempt ${attempt})...`); + const client = new cosmos_1.CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT'), tokenProvider: () => Promise.resolve(`type=aad&ver=1.0&sig=${cosmosDBAccessToken.token}`) }); + const scripts = client.database('builds').container(quality).scripts; + const { resource: result } = await scripts.storedProcedure('createAsset').execute('', [version, asset, true]); + return result; + }); + if (result === 'already exists') { + log('Asset already exists!'); + } + else { + log('Asset successfully created: ', JSON.stringify(asset, undefined, 2)); + } }); - if (result === 'already exists') { - log('Asset already exists!'); - } - else { - log('Asset successfully created: ', JSON.stringify(asset, undefined, 2)); - } + log(`Successfully released lease for: ${friendlyFileName}`); } // It is VERY important that we don't download artifacts too much too fast from AZDO. // AZDO throttles us SEVERELY if we do. Not just that, but they also close open diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index ab9270e177df7..0987502432bc2 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -16,8 +16,9 @@ import * as cp from 'child_process'; import * as os from 'os'; import { Worker, isMainThread, workerData } from 'node:worker_threads'; import { ConfidentialClientApplication } from '@azure/msal-node'; -import { BlobClient, BlobServiceClient, ContainerClient } from '@azure/storage-blob'; +import { BlobClient, BlobServiceClient, BlockBlobClient, ContainerClient } from '@azure/storage-blob'; import * as jws from 'jws'; +import { clearInterval, setInterval } from 'node:timers'; function e(name: string): string { const result = process.env[name]; @@ -74,6 +75,7 @@ interface ReleaseError { const enum StatusCode { Pass = 'pass', + Aborted = 'aborted', Inprogress = 'inprogress', FailCanRetry = 'failCanRetry', FailDoNotRetry = 'failDoNotRetry', @@ -376,8 +378,12 @@ class ESRPReleaseService { if (releaseStatus.status === 'pass') { break; + } else if (releaseStatus.status === 'aborted') { + this.log(JSON.stringify(releaseStatus)); + throw new Error(`Release was aborted`); } else if (releaseStatus.status !== 'inprogress') { - throw new Error(`Failed to submit release: ${JSON.stringify(releaseStatus)}`); + this.log(JSON.stringify(releaseStatus)); + throw new Error(`Unknown error when polling for release`); } } @@ -788,67 +794,121 @@ function getRealType(type: string) { } } +async function withLease(client: BlockBlobClient, fn: () => Promise) { + const lease = client.getBlobLeaseClient(); + + for (let i = 0; i < 360; i++) { // Try to get lease for 30 minutes + try { + await client.uploadData(new ArrayBuffer()); // blob needs to exist for lease to be acquired + await lease.acquireLease(60); + + try { + const abortController = new AbortController(); + const refresher = new Promise((c, e) => { + abortController.signal.onabort = () => { + clearInterval(interval); + c(); + }; + + const interval = setInterval(() => { + lease.renewLease().catch(err => { + clearInterval(interval); + e(new Error('Failed to renew lease ' + err)); + }); + }, 30_000); + }); + + const result = await Promise.race([fn(), refresher]); + abortController.abort(); + return result; + } finally { + await lease.releaseLease(); + } + } catch (err) { + if (err.statusCode !== 409 && err.statusCode !== 412) { + throw err; + } + + await new Promise(c => setTimeout(c, 5000)); + } + } + + throw new Error('Failed to acquire lease on blob after 30 minutes'); +} + async function processArtifact( artifact: Artifact, filePath: string ) { + const log = (...args: any[]) => console.log(`[${artifact.name}]`, ...args); const match = /^vscode_(?[^_]+)_(?[^_]+)(?:_legacy)?_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); if (!match) { throw new Error(`Invalid artifact name: ${artifact.name}`); } - // getPlatform needs the unprocessedType const { cosmosDBAccessToken, blobServiceAccessToken } = JSON.parse(e('PUBLISH_AUTH_TOKENS')); const quality = e('VSCODE_QUALITY'); const version = e('BUILD_SOURCEVERSION'); - const { product, os, arch, unprocessedType } = match.groups!; - const isLegacy = artifact.name.includes('_legacy'); - const platform = getPlatform(product, os, arch, unprocessedType, isLegacy); - const type = getRealType(unprocessedType); - const size = fs.statSync(filePath).size; - const stream = fs.createReadStream(filePath); - const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 + const friendlyFileName = `${quality}/${version}/${path.basename(filePath)}`; - const log = (...args: any[]) => console.log(`[${artifact.name}]`, ...args); const blobServiceClient = new BlobServiceClient(`https://${e('VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME')}.blob.core.windows.net/`, { getToken: async () => blobServiceAccessToken }); - const containerClient = blobServiceClient.getContainerClient('staging'); + const leasesContainerClient = blobServiceClient.getContainerClient('leases'); + await leasesContainerClient.createIfNotExists(); + const leaseBlobClient = leasesContainerClient.getBlockBlobClient(friendlyFileName); - const releaseService = await ESRPReleaseService.create( - log, - e('RELEASE_TENANT_ID'), - e('RELEASE_CLIENT_ID'), - e('RELEASE_AUTH_CERT'), - e('RELEASE_REQUEST_SIGNING_CERT'), - containerClient - ); + log(`Acquiring lease for: ${friendlyFileName}`); - const friendlyFileName = `${quality}/${version}/${path.basename(filePath)}`; - const url = `${e('PRSS_CDN_URL')}/${friendlyFileName}`; - const res = await retry(() => fetch(url)); + await withLease(leaseBlobClient, async () => { + log(`Successfully acquired lease for: ${friendlyFileName}`); - if (res.status === 200) { - log(`Already released and provisioned: ${url}`); - } else { - await releaseService.createRelease(version, filePath, friendlyFileName); - } + const url = `${e('PRSS_CDN_URL')}/${friendlyFileName}`; + const res = await retry(() => fetch(url)); - const asset: Asset = { platform, type, url, hash: hash.toString('hex'), sha256hash: sha256hash.toString('hex'), size, supportsFastUpdate: true }; - log('Creating asset...'); + if (res.status === 200) { + log(`Already released and provisioned: ${url}`); + } else { + const stagingContainerClient = blobServiceClient.getContainerClient('staging'); + await stagingContainerClient.createIfNotExists(); + + const releaseService = await ESRPReleaseService.create( + log, + e('RELEASE_TENANT_ID'), + e('RELEASE_CLIENT_ID'), + e('RELEASE_AUTH_CERT'), + e('RELEASE_REQUEST_SIGNING_CERT'), + stagingContainerClient + ); + + await retry(() => releaseService.createRelease(version, filePath, friendlyFileName)); + } - const result = await retry(async (attempt) => { - log(`Creating asset in Cosmos DB (attempt ${attempt})...`); - const client = new CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT')!, tokenProvider: () => Promise.resolve(`type=aad&ver=1.0&sig=${cosmosDBAccessToken.token}`) }); - const scripts = client.database('builds').container(quality).scripts; - const { resource: result } = await scripts.storedProcedure('createAsset').execute<'ok' | 'already exists'>('', [version, asset, true]); - return result; + const { product, os, arch, unprocessedType } = match.groups!; + const isLegacy = artifact.name.includes('_legacy'); + const platform = getPlatform(product, os, arch, unprocessedType, isLegacy); + const type = getRealType(unprocessedType); + const size = fs.statSync(filePath).size; + const stream = fs.createReadStream(filePath); + const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 + const asset: Asset = { platform, type, url, hash: hash.toString('hex'), sha256hash: sha256hash.toString('hex'), size, supportsFastUpdate: true }; + log('Creating asset...'); + + const result = await retry(async (attempt) => { + log(`Creating asset in Cosmos DB (attempt ${attempt})...`); + const client = new CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT')!, tokenProvider: () => Promise.resolve(`type=aad&ver=1.0&sig=${cosmosDBAccessToken.token}`) }); + const scripts = client.database('builds').container(quality).scripts; + const { resource: result } = await scripts.storedProcedure('createAsset').execute<'ok' | 'already exists'>('', [version, asset, true]); + return result; + }); + + if (result === 'already exists') { + log('Asset already exists!'); + } else { + log('Asset successfully created: ', JSON.stringify(asset, undefined, 2)); + } }); - if (result === 'already exists') { - log('Asset already exists!'); - } else { - log('Asset successfully created: ', JSON.stringify(asset, undefined, 2)); - } + log(`Successfully released lease for: ${friendlyFileName}`); } // It is VERY important that we don't download artifacts too much too fast from AZDO. From c43ed48f5ffd68476d3c1ee0849a20032de4a558 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Fri, 6 Dec 2024 09:14:52 +0100 Subject: [PATCH 17/37] Bump version number to 1.97 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b4c9991af6b16..a16b3ecf06f5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-oss-dev", - "version": "1.96.0", + "version": "1.97.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-oss-dev", - "version": "1.96.0", + "version": "1.97.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index f7a7f68cde781..f283587b8c5f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.96.0", + "version": "1.97.0", "distro": "c883c91dadf5f063b26c86ffe01851acee3747c6", "author": { "name": "Microsoft Corporation" From 5addad41b0d7cb991e43a51341637fd8e19049b7 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 6 Dec 2024 09:25:12 +0100 Subject: [PATCH 18/37] Git/SCM - fix GC warnings (#235464) --- extensions/git/src/repository.ts | 2 +- src/vs/workbench/contrib/scm/browser/menus.ts | 27 ++++++++++--------- .../contrib/scm/browser/scmHistoryViewPane.ts | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 1c2e6ca45c2a6..ef7997c7542ea 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -986,7 +986,7 @@ export class Repository implements Disposable { const actionButton = new ActionButton(this, this.commitCommandCenter, this.logger); this.disposables.push(actionButton); - actionButton.onDidChange(() => this._sourceControl.actionButton = actionButton.button); + actionButton.onDidChange(() => this._sourceControl.actionButton = actionButton.button, this, this.disposables); this._sourceControl.actionButton = actionButton.button; const progressManager = new ProgressManager(this); diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 486f978b768c2..f14d8ea79f67c 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -20,18 +20,6 @@ function actionEquals(a: IAction, b: IAction): boolean { return a.id === b.id; } -const repositoryMenuDisposables = new DisposableStore(); - -MenuRegistry.onDidChangeMenu(e => { - if (e.has(MenuId.SCMTitle)) { - repositoryMenuDisposables.clear(); - - for (const menuItem of MenuRegistry.getMenuItems(MenuId.SCMTitle)) { - repositoryMenuDisposables.add(MenuRegistry.appendMenuItem(MenuId.SCMSourceControlInline, menuItem)); - } - } -}); - export class SCMTitleMenu implements IDisposable { private _actions: IAction[] = []; @@ -244,6 +232,7 @@ export class SCMMenus implements ISCMMenus, IDisposable { readonly titleMenu: SCMTitleMenu; private readonly disposables = new DisposableStore(); + private readonly repositoryMenuDisposables = new DisposableStore(); private readonly menus = new Map void }>(); constructor( @@ -252,6 +241,20 @@ export class SCMMenus implements ISCMMenus, IDisposable { ) { this.titleMenu = instantiationService.createInstance(SCMTitleMenu); scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); + + // Duplicate the `SCMTitle` menu items to the `SCMSourceControlInline` menu. We do this + // so that menu items can be independently hidden/shown in the "Source Control" and the + // "Source Control Repositories" views. + MenuRegistry.onDidChangeMenu(e => { + if (!e.has(MenuId.SCMTitle)) { + return; + } + + this.repositoryMenuDisposables.clear(); + for (const menuItem of MenuRegistry.getMenuItems(MenuId.SCMTitle)) { + this.repositoryMenuDisposables.add(MenuRegistry.appendMenuItem(MenuId.SCMSourceControlInline, menuItem)); + } + }, this, this.disposables); } private onDidRemoveRepository(repository: ISCMRepository): void { diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index d6057c14ecb0c..af17ed451bd09 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -1372,7 +1372,7 @@ export class SCMHistoryViewPane extends ViewPane { } isFirstRun = false; })); - }); + }, this, this._store); } protected override layoutBody(height: number, width: number): void { From 266cc016110147732915cfce2387e4d7aaa51175 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 6 Dec 2024 10:33:32 +0100 Subject: [PATCH 19/37] Rerender hover on modifier key press - updates hover on hover definition preview request (#235406) rerender hover on modifier key press - updates hover on definition hover preview --- .../contrib/hover/browser/contentHoverController.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHoverController.ts b/src/vs/editor/contrib/hover/browser/contentHoverController.ts index ca0677d32066a..9b8252eec14cd 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverController.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverController.ts @@ -244,10 +244,16 @@ export class ContentHoverController extends Disposable implements IEditorContrib return; } const isPotentialKeyboardShortcut = this._isPotentialKeyboardShortcut(e); - const isModifierKeyPressed = this._isModifierKeyPressed(e); - if (isPotentialKeyboardShortcut || isModifierKeyPressed) { + if (isPotentialKeyboardShortcut) { return; } + const isModifierKeyPressed = this._isModifierKeyPressed(e); + if (isModifierKeyPressed && this._mouseMoveEvent) { + const contentWidget: ContentHoverWidgetWrapper = this._getOrCreateContentWidget(); + if (contentWidget.showsOrWillShow(this._mouseMoveEvent)) { + return; + } + } this.hideContentHover(); } From c83840631aa2b50312b9d4207c6fa6519d62ade2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 6 Dec 2024 10:34:30 +0100 Subject: [PATCH 20/37] update codicons (#235469) --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 82204 -> 82588 bytes src/vs/base/common/codiconsLibrary.ts | 1 + 2 files changed, 1 insertion(+) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 95c8a7a04c1c7b285ddd1e6ed88b0263c7333725..01f7648ad16f1fe1a31451e092708832703edd89 100644 GIT binary patch delta 7512 zcmXZh31C#!-39P-lFUr@1d>ch2q7dP30r_9B#^LgA_%ezVV4lLu!YDjgNT$;T!Yq9 ziWIG)B1N$-lp^j+aYLk5E!Eaq`rgzcrE0Aoe&_NR_`Nf4mN$3qeed3PFMGEJ_TCb> zqbPDMz+C|5&R;ff<**A^Jq08T2HFo^*tEW}rq7rgfwJzv<&PIFnAb4vp8b>b-cO`@ zk)DXDkJUpQm*PdsTGkb>{r+nmFA9VvH?5dI@8Gu2&*-HsK%uH z!SZ>_7PNky`|KF7G+s}BvvS4imfhV3ZU(#P^_;zT9xqCHAJk)MTRHusJEOh%errD0 zC%lc*db%s#KDwU|^61dmlwhB#D{naI)#0#%5Mi_yE=B3;kKcssmZ-mBj0 zeu6(PTGzTQpXUX8`qoWOB-mIJw$-FO2txe?d$4KCy=uEag4W;K3-_Zf#F+=CmD z&6lwsw{kR#n9KJ3HQvUfc%BdON4SIcatm+gt-OhM@Ta_sKfz1f&fSPX90Dp=5|WXE zRHPxjwSQt#(p=2L^_Y)FENq>a*e^02^_YQ~Sc+v>fo811I;_VAY{VvPMg&`M6Sm@J z+=A`cfgfWRZo{3}jk|F#euh2xIricg*oXV@OWcnK@GCrshj0*&;4vJ=ul1fIcpOLZ zJDk7^cm;3Z_jn8M;9b@1d-!7nAL1i?j6dNV{;c-?55B>F@g2S=NX9V85R;k0RAw-X zVP-Rj9hlEfEM#Yvu$&dFWEK0chJ9JfI`-!P4&^Wo=LnAERUE~uIfi3-Eyr^rCvgg= z@;XlAbk5~GMy}_4Hn5RP*~DdB!DhB_9oKUMH}OVp<;~p2TeyQi=1%V7ZM>7Myqo)Y z9}n_jKFY^ruT2)eQ-OHqTq7{j0QcYK<^;Zyhx>p6=Zv6AobG~eYfd4Tux0scxS^nOIz z;5v520ltpq_yhimzu;3$!~~2-7Cyj}cnY)7#5s5vOAy9uc%3~^fKH4>CQmRA|Hfzd z8$6uHKM=(~@h^Odi}*V}=Pz)EgK#^72q7K`xBwqtAP;XciSejIE^;t#5uU~~T!67$ z#I-ofwtR$#Fp8^jh_9eGyR#kJ@ET0TSDef>_z|`-!sY4-j^Vd_jjv(^XLAOo;Up88 zfR}YmXW&Kb#7InGKTN`8JfRCa*t#m^a_dP3T34h76Owfy>W?n+&8gWD^(5Z)h7*+Y zjV@An4F=CD7Z?syHX80#E;Q_>Tx58IaH}DYqN-g7AKTJM?&m;l0Y8hVDyUZ3u5y?lN?Rp5>kpH)4410792{ z8vayym!Zp6qplX-ZiAPUcN@Bbx#kh>Zv7(txdC&v=ZY-qe&HQ7>WJYzY}6&gd&H=7 zhUb2|VBt}(%?zrahIh)S$Ao~u1ke-7^*qn;h!yGDIHywgU#J-qjf z`h9pBLLH3&@Z9+#8Vlf^btkDd8W!OF!DxVh_kqz+0q>7Sg9f}0jZQ6i9~o>>erz

cz-h*t>Arb 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 Lr}KOAa-07LFYD&z delta 7080 zcmXBZ349dQ9S88=FUN*J4swu9LI@#&B*qv*2m!(k0mC8peTFMQxFJ9|WDyY+EdoKL zc$6wt1dAxPAhk#pDOyCdXj`>Nt+jM!OBE5RQtj{gm(O>8JDc6vH}B27H}Cn_J<*SC zjNX

Id*a05vlgPG3?xt^e~tYXu8#-PXh#9_M@yzLyPGvRdPfLKv0}H1&EQ$DVZ5lu`YnEkH}EFj!rOQU z=kcy;^Uopt1s~!ge2l;1B0j-2e1jYK4&UPkf@CxUY{odoGnuJu!8E2bi>;W$Hf+mw zyp0`L#*Qp!1v|4VE7^_R*@u1Ek5%l?0lb~n9K<0U%3&PAksQr29LsSW&uOe-XgX(b zCTFvb^SOYFxRlGeiVa-NwOq%$xRG~r6Yu4Hyq^#7C)~yz+{wrJ1fSu49^iBQC7EN{Wjc?Jvd0Y1gw zbeoOD2nk)k%hBt${2J*CNeN|27ZmBoP|3$hb!?ullUwTqMFNa zkZ+&^Z)K3pIT&N`HAis;Hsfxt=R*7jFXK3W&(rA7DV&ILc$Kkif>SI+3SPtg7=Y32 zf>C(k$fEdohIH$>|9AoLYK-nhc+(B8D`yxESI#uLQ{l}rIHsI!*h5)s_^5J@VUcpK zVZCym8Os#Cs583Z;mtP~rd(k7rgEX7JNP0)^<>^+qb>rT+ZR0CI6N^pu#@(d8+9D; z+|LT!sje{UOyI3F=%ZX^)WN`0U+G|78g)DH))}a`_U<$ss9bNDt5oee z%usGH%v9cG)MdfjXw-SZyW6N6gSW}xr1Bmh+swKA?o4by>Hal!~4KhyEf|f;r+>I0D$*r zqoDxaUyKF?cpn-K5AZ%R8Ytj>Y&2xR`>WC50q>&GFaqxrqX7lpC8MDw1n+O!5sfnN zJ~bL|;Qifb*>n zvyWc50|@&nyBPLUx`PR;l3&=|Qt5tNI9lm`TsTJQeq13&=|PU(JJI9@r>aGKH;L|CI7Z1fze z7p_#o8Ol2hXDVF@g|n5z4eOMyfWrAoS3uzc^CnQl%@baJkYIR=7&3!iL<2 z4cc*~6|PoJFkGvgXt+)}$+%gSKiP1ja*E;I%BhB%lO&NAGlbTuH{p{zCBshnf@xN@%H6Uupp&nR7u3HK{qjR_AZU5zbJ`|~-y zSZMf5rK>*S^UB4Bhm}hVUr@Sg6~3rkW_U#Ds#thbS#S8Va)se>I5CWt=wREPU)IZLVQmzTpNle zD)3z!il!{^T^oufFYxa%n#RE2Y&4;P@7h*0wSn*2Ry4_hf4|Xm2mThLi4T0&%mPncP)q}P4KrHOl@43mDeFbd+wW}i4^=@MpG*I zj~KYR*==~N@qAWZ$Xzp!8GNO5*O4$!>8>N8yXb#r=)S$zXkrHc=LW9zo-}mV-cyF| z`1=eCl)o@^t@X6K`?O)HcAhbs-@)H+Fh%*S(VP#yyJ!Vf%7aGpK=|(Z70n3YyX#kQ zT6xGYO8LBDYvp0X-e~-?Ra$(Bc8?h5H>PH{5AD(3F~e7s?gAG6T6x^?C8fK7MUzeV z?sEe7{%;K@C|%76Co5kynv}vnX|P!7PEPoaQrCFMZTPNsUN=0iJY~37`G%pZes`uq zSN*>?bk%>xaG&x`Ls$LoW)aPS;kye$kfd}si(o9K!GBvjq8T#$a|Z6mTnR<9X85j9 zqWLp?S6I*C%Tmb(oqlE+b*Nm1B z;D2qjm;nE}(Xs;kZw&sSykWG|0N?df0$0`F8ZAG-|ITP30{%^-C5gtn+LR1bS;I71 zn?P7~;%Jou;Yg$P3WTE!bXkO>4Vx+hhB1vd+H_l}yCNKCa8wy@w77wAg25fiM8i4C z=0-~&2qzi5uS_;{@1+<%t5i!lJlME5H<+_oON`cl5N>bqs-7sq zrP>k3Dmxf05g}Y=a7x+HXxRwia-#(#ggY56B_Uj4w5Wt|XQSmMgu57wR(3U7azeP$ zQ4?zQ318|0bMOMtxS{RAP+W@>Ej~@_k~Sl4QQDMQc z)l1nC**Vz*vTL)qWS`2u)H0__I0d#>HNb~p1A@{95h<)6*JTo6?-vS3@mse-G8vkE`B zW#}z?ZaGy{Ra95>#jTr)QG8$Vd$*SzT@$Z*UQ_M*Ok9j{%xmFdZ*e>dpez{D6ZI8aiZc%=kA@a zb;Q&uqPp{LxuJx|$UDJC*@7=v$?0vCM(>|qr zYWjTG=UU%EeK+>oK}GndTVHuJSv zfmyp|9h-G^cG>K)vp3G(H~YidCAIr%&(BGlb8gO!xeaq)3eC%&H(=g|dDrX8>Q>er ztGhP8a{lJ|7Z*e<7_#8Ah20l-UvhP6_R_vf4=w#*S;Vr;W!1~}EIYG2bNR95SL-9{ zlk0Qq%j$R3zqcZJ#pD%Fu1sFpedU%_%~w6Q>f?rzhMI=Q8m_J`U%g@V-ZjZ<#;)1D z=IYwywZqr0Tl>Mtp(^-@INo)sf=DXA46yx-pK^jB`w&wBA6CTYmpig%rB@YFJZv#1_Lqq1%a4AUT#55i;nJ%H2qos+H3Kzqa&jN$!${O$fj|rsexv>x$$u^fo3r=5s`@r zjEs(No|hjV8QC-{(4={j(!{u;l!(ZYNv)HTTIXdaCujEy7L?=%gZU)|!NKY2>5PpC zF)}$SDkUj9B`y#f8B>rF-8?NdHZmeAHV|kMNREye9*Axdm6Q;f5C}v?H_>OJ1Hr_M z02AX9LH7&I&ZCZxWqS7@v}w_EA!H+w7ztH+_OX{+`>Ur=;X}QRfu+ n;Rn@tvDORXkbdOAs8#Rt!qMDYxIgmf* Date: Fri, 6 Dec 2024 11:25:23 +0100 Subject: [PATCH 21/37] remove API guidelines from copilot instructions (#235475) --- .github/copilot-instructions.md | 134 -------------------------------- 1 file changed, 134 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 6b833803b2e34..6f20c66c400e5 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -55,137 +55,3 @@ for (let i = 0, n = str.length; i < 10; i++) { function f(x: number, y: string): void { } ``` - -# Extension API Guidelines - -## Overview - -The following guidelines only apply to files in the `src/vscode-dts` folder. - -This is a loose collection of guidelines that you should be following when proposing API. The process for adding API is described here: [Extension API Process](https://github.com/Microsoft/vscode/wiki/Extension-API-process). - -## Core Principles - -### Avoid Breakage - -We DO NOT want to break API. Therefore be careful and conservative when proposing new API. It needs to hold up in the long term. Expose only the minimum but still try to anticipate potential future requests. - -### Namespaces - -The API is structured into different namespaces, like `commands`, `window`, `workspace` etc. Namespaces contain functions, constants, and events. All types (classes, enum, interfaces) are defined in the global, `vscode`, namespace. - -### JavaScript Feel - -The API should have a JavaScript’ish feel. While that is harder to put in rules, it means we use namespaces, properties, functions, and globals instead of object-factories and services. Also take inspiration from popular existing JS API, for instance `window.createStatusBarItem` is like `document.createElement`, the members of `DiagnosticsCollection` are similar to ES6 maps etc. - -### Global Events - -Events aren’t defined on the types they occur on but in the best matching namespace. For instance, document changes aren't sent by a document but via the `workspace.onDidChangeTextDocument` event. The event will contain the document in question. This **global event** pattern makes it easier to manage event subscriptions because changes happen less frequently. - -### Private Events - -Private or instance events aren't accessible via globals but exist on objects, e.g., `FileSystemWatcher#onDidCreate`. *Don't* use private events unless the sender of the event is private. The rule of thumb is: 'Objects that can be accessed globally (editors, tasks, terminals, documents, etc)' should not have private events, objects that are private (only known by its creators, like tree views, web views) can send private events' - -### Event Naming - -Events follow the `on[Did|Will]VerbSubject` patterns, like `onDidChangeActiveEditor` or `onWillSaveTextDocument`. It doesn’t hurt to use explicit names. - -### Creating Objects - -Objects that live in the main thread but can be controlled/instantiated by extensions are declared as interfaces, e.g. `TextDocument` or `StatusBarItem`. When you allow creating such objects your API must follow the `createXYZ(args): XYZ` pattern. Because this is a constructor-replacement, the call must return synchronously. - -### Shy Objects - -Objects the API hands out to extensions should not contain more than what the API defines. Don’t expect everyone to read `vscode.d.ts` but also expect folks to use debugging-aided-intellisense, meaning whatever the debugger shows developers will program against. We don’t want to appear as making false promises. Prefix your private members with `_` as that is a common rule or, even better, use function-scopes to hide information. - -### Sync vs. Async - -Reading data, like an editor selection, a configuration value, etc. is synchronous. Setting a state that reflects on the main side is asynchronous. Despite updates being async your ‘extension host object’ should reflect the new state synchronously. This happens when setting an editor selection - -``` - editor.selection = newSelection - - | - | - V - - 1. On the API object set the value as given - 2. Make an async-call to the main side ala `trySetSelection` - 3. The async-call returns with the actual selection (it might have changed in the meantime) - 4. On the API object set the value again -``` - -We usually don’t expose the fact that setting state is asynchronous. We try to have API that feels sync -`editor.selection` is a getter/setter and not a method. - -### Data Driven - -Whenever possible, you should define a data model and define provider-interfaces. This puts VS Code into control as we can decide when to ask those providers, how to deal with multiple providers etc. The `ReferenceProvider` interface is a good sample for this. - -### Enrich Data Incrementally - -Sometimes it is expensive for a provider to compute parts of its data. For instance, creating a full `CompletionItem` (with all the documentation and symbols resolved) conflicts with being able to compute a large list of them quickly. In those cases, providers should return a lightweight version and offer a `resolve` method that allows extensions to enrich data. The `CodeLensProvider` and `CompletionItemProvider` interfaces are good samples for this. - -### Cancellation - -Calls into a provider should always include a `CancellationToken` as the last parameter. With that, the main thread can signal to the provider that its result won’t be needed anymore. When adding new parameters to provider-functions, it is OK to have the token not at the end anymore. - -### Objects vs. Interfaces - -Objects that should be returned by a provider are usually represented by a class that extensions can instantiate, e.g. `CodeLens`. We do that to provide convenience constructors and to be able to populate default values. - -Data that we accept in methods calls, i.e., parameter types, like in `registerRenameProvider` or `showQuickPick`, are declared as interfaces. That makes it easy to fulfill the API contract using class-instances or plain object literals. - -### Strict and Relaxed Data - -Data the API returns is strict, e.g. `activeTextEditor` is an editor or `undefined`, but not `null`. On the other side, providers can return relaxed data. We usually accept 4 types: The actual type, like `Hover`, a `Thenable` of that type, `undefined` or `null`. With that we want to make it easy to implement a provider, e.g., if you can compute results synchronous you don’t need to wrap things into a promise or if a certain condition isn’t met simple return, etc. - -### Validate Data - -Although providers can return ‘relaxed’ data, you need to verify it. The same is true for arguments etc. Throw validation errors when possible, drop data object when invalid. - -### Copy Data - -Don’t send the data that a provider returned over the wire. Often it contains more information than we need and often there are cyclic dependencies. Use the provider data to create objects that your protocol speaks. - -### Enums - -When API-work started only numeric-enums were supported, today TypeScript supports string-or-types and string-enums. Because fewer concepts are better, we stick to numeric-enums. - -### Strict Null - -We define the API with strictNull-checks in mind. That means we use the optional annotation `foo?: number` and `null` or `undefined` in type annotations. For instance, its `activeTextEditor: TextEditor | undefined`. Again, be strict for types we define and relaxed when accepting data. - -### Undefined is False - -The default value of an optional, boolean property is `false`. This is for consistency with JS where undefined never evaluates to `true`. - -### JSDoc - -We add JSDoc for all parts of the API. The doc is supported by markdown syntax. When document string-datatypes that end up in the UI, use the phrase ‘Human-readable string…’ - -## Optional Parameters (`?` vs `| undefined`) - -* For implementation, treat omitting a parameter with `?` the same as explicitly passing in `undefined` -* Use `| undefined` when you want to callers to always have to consider the parameter. -* Use `?` when you want to allow callers to omit the parameter. -* Never use `?` and `| undefined` on a parameter. Instead follow the two rules above to decide which version to use. -* If adding a new parameter to an existing function, use `?` as this allows the new signature to be backwards compatible with the old version. -* Do not add an overload to add an optional parameter to the end of the function. Instead use `?`. - -## Optional Properties (`?` vs `| undefined`) - -* Do not write code that treats the absence of a property differently than a property being present but set to `undefined` - * This can sometimes hit you on spreads or iterating through objects, so just something to be aware of - -* For readonly properties on interfaces that VS Code exposes to extensions (this include managed objects, as well as the objects passed to events): - * Use `| undefined` as this makes it clear the property exists but has the value `undefined`. - -* For readonly properties on options bag type objects passed from extensions to VS Code: - * Use `?` when it is ok to omit the property - * Use `| undefined` when you want the user to have to pass in the property but `undefined` signals that you will fall back to some default - * Try to avoid `?` + `| undefined` in most cases. Instead use `?`. Using both `?` + `| undefined` isn't wrong, but it's often more clear to treat omitting the property as falling back to the default rather than passing in `undefined` - -* For unmanaged, writable objects: - * If using `?`, always also add `| undefined` unless want to allow the property to be omitted during initialization, but never allow users to explicitly set it to `undefined` afterwards. I don't think we have many cases where this will be needed - * In these cases, you may want to try changing the api to avoid this potential confusion - * If adding a new property to an unmanaged object, use `?` as this ensures the type is backwards compatible with the old version From 2443bffc6ccab10fbe32b8486055a59cd9ed99de Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 6 Dec 2024 11:50:18 +0100 Subject: [PATCH 22/37] remove cyclic dependency `notebookChatEditController -> notebookChatActionsOverlay -> chatEditorOverlay` (#235480) * fix https://github.com/microsoft/vscode/issues/235383 * remove cyclic dependency `notebookChatEditController -> notebookChatActionsOverlay -> chatEditorOverlay` fyi @DonJayamanne --- .../chatMarkdownContentPart.ts | 15 +-- .../contrib/chat/browser/chatEditorOverlay.ts | 109 +----------------- 2 files changed, 6 insertions(+), 118 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index 36ba8b160966c..a281df1c40cee 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -35,7 +35,6 @@ import { IChatMarkdownContent } from '../../common/chatService.js'; import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; import { CodeBlockModelCollection } from '../../common/codeBlockModelCollection.js'; import { IChatCodeBlockInfo, IChatListItemRendererOptions } from '../chat.js'; -import { AnimatedValue, ObservableAnimatedValue } from '../chatEditorOverlay.js'; import { IChatRendererDelegate } from '../chatListRenderer.js'; import { ChatMarkdownDecorationsRenderer } from '../chatMarkdownDecorationsRenderer.js'; import { ChatEditorOptions } from '../chatOptions.js'; @@ -351,23 +350,15 @@ class CollapsedCodeBlock extends Disposable { this.element.title = this.labelService.getUriLabel(uri, { relative: false }); // Show a percentage progress that is driven by the rewrite - const slickRatio = ObservableAnimatedValue.const(0); - let t = Date.now(); + this._progressStore.add(autorun(r => { const rewriteRatio = modifiedEntry?.rewriteRatio.read(r); - if (rewriteRatio) { - slickRatio.changeAnimation(prev => { - const result = new AnimatedValue(prev.getValue(), rewriteRatio, Date.now() - t); - t = Date.now(); - return result; - }, undefined); - } const labelDetail = this.element.querySelector('.label-detail'); const isComplete = !modifiedEntry?.isCurrentlyBeingModified.read(r); if (labelDetail && !isStreaming && !isComplete) { - const value = slickRatio.getValue(undefined); - labelDetail.textContent = value === 0 ? localize('chat.codeblock.applying', "Applying edits...") : localize('chat.codeblock.applyingPercentage', "Applying edits ({0}%)...", Math.round(value * 100)); + const value = rewriteRatio; + labelDetail.textContent = value === 0 || !value ? localize('chat.codeblock.applying', "Applying edits...") : localize('chat.codeblock.applyingPercentage', "Applying edits ({0}%)...", Math.round(value * 100)); } else if (labelDetail && !isStreaming && isComplete) { iconEl.classList.remove(...iconClasses); const fileKind = uri.path.endsWith('/') ? FileKind.FOLDER : FileKind.FILE; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts b/src/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts index 978b13b59a11a..6a09f5ef2751f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts @@ -5,7 +5,7 @@ import './media/chatEditorOverlay.css'; import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun, IReader, ISettableObservable, ITransaction, observableFromEvent, observableSignal, observableValue, transaction } from '../../../../base/common/observable.js'; +import { autorun, observableFromEvent, observableValue, transaction } from '../../../../base/common/observable.js'; import { isEqual } from '../../../../base/common/resources.js'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js'; import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; @@ -17,7 +17,7 @@ import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionView import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; import { Range } from '../../../../editor/common/core/range.js'; import { IActionRunner } from '../../../../base/common/actions.js'; -import { $, append, EventLike, getWindow, reset, scheduleAtNextAnimationFrame } from '../../../../base/browser/dom.js'; +import { $, append, EventLike, reset } from '../../../../base/browser/dom.js'; import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; @@ -25,7 +25,6 @@ import { Codicon } from '../../../../base/common/codicons.js'; import { assertType } from '../../../../base/common/types.js'; import { localize } from '../../../../nls.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; -import { ctxNotebookHasEditorModification } from '../../notebook/browser/contrib/chatEdit/notebookChatEditController.js'; import { AcceptAction, RejectAction } from './chatEditorActions.js'; import { ChatEditorController } from './chatEditorController.js'; @@ -195,22 +194,11 @@ class ChatEditorOverlayWidget implements IOverlayWidget { this._domNode.classList.toggle('busy', busy); })); - const slickRatio = ObservableAnimatedValue.const(0); - let t = Date.now(); this._showStore.add(autorun(r => { const value = activeEntry.rewriteRatio.read(r); - - slickRatio.changeAnimation(prev => { - const result = new AnimatedValue(prev.getValue(), value, Date.now() - t); - t = Date.now(); - return result; - }, undefined); - - const value2 = slickRatio.getValue(r); - reset(this._progressNode, (value === 0 ? localize('generating', "Generating edits...") - : localize('applyingPercentage', "{0}% Applying edits...", Math.round(value2 * 100)))); + : localize('applyingPercentage', "{0}% Applying edits...", Math.round(value * 100)))); })); this._showStore.add(autorun(r => { @@ -269,101 +257,10 @@ MenuRegistry.appendMenuItem(MenuId.ChatEditingEditorContent, { title: localize('label', "Navigation Status"), precondition: ContextKeyExpr.false(), }, - when: ctxNotebookHasEditorModification.negate(), group: 'navigate', order: -1 }); - -export class ObservableAnimatedValue { - public static const(value: number): ObservableAnimatedValue { - return new ObservableAnimatedValue(AnimatedValue.const(value)); - } - - private readonly _value: ISettableObservable; - - constructor( - initialValue: AnimatedValue, - ) { - this._value = observableValue(this, initialValue); - } - - setAnimation(value: AnimatedValue, tx: ITransaction | undefined): void { - this._value.set(value, tx); - } - - changeAnimation(fn: (prev: AnimatedValue) => AnimatedValue, tx: ITransaction | undefined): void { - const value = fn(this._value.get()); - this._value.set(value, tx); - } - - getValue(reader: IReader | undefined): number { - const value = this._value.read(reader); - if (!value.isFinished()) { - Scheduler.instance.invalidateOnNextAnimationFrame(reader); - } - return value.getValue(); - } -} - -class Scheduler { - static instance = new Scheduler(); - - private readonly _signal = observableSignal(this); - - private _isScheduled = false; - - invalidateOnNextAnimationFrame(reader: IReader | undefined): void { - this._signal.read(reader); - if (!this._isScheduled) { - this._isScheduled = true; - scheduleAtNextAnimationFrame(getWindow(undefined), () => { - this._isScheduled = false; - this._signal.trigger(undefined); - }); - } - } -} - -export class AnimatedValue { - - static const(value: number): AnimatedValue { - return new AnimatedValue(value, value, 0); - } - - readonly startTimeMs = Date.now(); - - constructor( - readonly startValue: number, - readonly endValue: number, - readonly durationMs: number, - ) { - if (startValue === endValue) { - this.durationMs = 0; - } - } - - isFinished(): boolean { - return Date.now() >= this.startTimeMs + this.durationMs; - } - - getValue(): number { - const timePassed = Date.now() - this.startTimeMs; - if (timePassed >= this.durationMs) { - return this.endValue; - } - const value = easeOutExpo(timePassed, this.startValue, this.endValue - this.startValue, this.durationMs); - return value; - } -} - -function easeOutExpo(passedTime: number, start: number, length: number, totalDuration: number): number { - return passedTime === totalDuration - ? start + length - : length * (-Math.pow(2, -10 * passedTime / totalDuration) + 1) + start; -} - - export class ChatEditorOverlayController implements IEditorContribution { static readonly ID = 'editor.contrib.chatOverlayController'; From 359ec5270a9c9220c418bb2e50a5b35e28b2aae1 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 6 Dec 2024 11:51:50 +0100 Subject: [PATCH 23/37] Add new line comment when pressing Enter from within line comment for languages that use double slash for line comment (#235407) * add decrease and increase indent patterns for cpp * adding new line with line comment on next line from within languages that use double slash for line comment --- extensions/cpp/language-configuration.json | 122 +++++++++++++++--- extensions/csharp/language-configuration.json | 100 +++++++++++--- extensions/go/language-configuration.json | 109 +++++++++++++--- extensions/groovy/language-configuration.json | 91 +++++++++++-- extensions/java/language-configuration.json | 104 ++++++++++++--- extensions/json/language-configuration.json | 82 ++++++++++-- extensions/less/language-configuration.json | 107 ++++++++++++--- .../objective-c/language-configuration.json | 91 +++++++++++-- extensions/php/language-configuration.json | 115 ++++++++++++++--- extensions/rust/language-configuration.json | 85 ++++++++++-- extensions/swift/language-configuration.json | 104 ++++++++++++--- 11 files changed, 941 insertions(+), 169 deletions(-) diff --git a/extensions/cpp/language-configuration.json b/extensions/cpp/language-configuration.json index 0bf8df9dc0106..cb1fb733b9998 100644 --- a/extensions/cpp/language-configuration.json +++ b/extensions/cpp/language-configuration.json @@ -1,29 +1,94 @@ { "comments": { "lineComment": "//", - "blockComment": ["/*", "*/"] + "blockComment": [ + "/*", + "*/" + ] }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] ], "autoClosingPairs": [ - { "open": "[", "close": "]" }, - { "open": "{", "close": "}" }, - { "open": "(", "close": ")" }, - { "open": "'", "close": "'", "notIn": ["string", "comment"] }, - { "open": "\"", "close": "\"", "notIn": ["string"] }, - { "open": "/*", "close": "*/", "notIn": ["string", "comment"] }, - { "open": "/**", "close": " */", "notIn": ["string"] } + { + "open": "[", + "close": "]" + }, + { + "open": "{", + "close": "}" + }, + { + "open": "(", + "close": ")" + }, + { + "open": "'", + "close": "'", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "\"", + "close": "\"", + "notIn": [ + "string" + ] + }, + { + "open": "/*", + "close": "*/", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "/**", + "close": " */", + "notIn": [ + "string" + ] + } ], "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["<", ">"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ], + [ + "<", + ">" + ] ], "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", "folding": { @@ -32,6 +97,14 @@ "end": "^\\s*#pragma\\s+endregion\\b" } }, + "indentationRules": { + "decreaseIndentPattern": { + "pattern": "^\\s*[\\}\\]\\)].*$" + }, + "increaseIndentPattern": { + "pattern": "^.*(\\{[^}]*|\\([^)]*|\\[[^\\]]*)$" + }, + }, "onEnterRules": [ { // Decrease indentation after single line if/else if/else, for, or while @@ -41,6 +114,19 @@ "action": { "indent": "outdent" } - } + }, + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, ] } diff --git a/extensions/csharp/language-configuration.json b/extensions/csharp/language-configuration.json index d8698b46c0906..60814ae02f4ae 100644 --- a/extensions/csharp/language-configuration.json +++ b/extensions/csharp/language-configuration.json @@ -1,32 +1,100 @@ { "comments": { "lineComment": "//", - "blockComment": ["/*", "*/"] + "blockComment": [ + "/*", + "*/" + ] }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] ], "autoClosingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - { "open": "'", "close": "'", "notIn": ["string", "comment"] }, - { "open": "\"", "close": "\"", "notIn": ["string", "comment"] } + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + { + "open": "'", + "close": "'", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "\"", + "close": "\"", + "notIn": [ + "string", + "comment" + ] + } ], "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["<", ">"], - ["'", "'"], - ["\"", "\""] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "<", + ">" + ], + [ + "'", + "'" + ], + [ + "\"", + "\"" + ] ], "folding": { "markers": { "start": "^\\s*#region\\b", "end": "^\\s*#endregion\\b" } - } + }, + "onEnterRules": [ + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, + ] } diff --git a/extensions/go/language-configuration.json b/extensions/go/language-configuration.json index a5e06a56bad12..9238bf3529b04 100644 --- a/extensions/go/language-configuration.json +++ b/extensions/go/language-configuration.json @@ -1,28 +1,86 @@ { "comments": { "lineComment": "//", - "blockComment": [ "/*", "*/" ] + "blockComment": [ + "/*", + "*/" + ] }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] ], "autoClosingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - { "open": "`", "close": "`", "notIn": ["string"]}, - { "open": "\"", "close": "\"", "notIn": ["string"]}, - { "open": "'", "close": "'", "notIn": ["string", "comment"]} + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + { + "open": "`", + "close": "`", + "notIn": [ + "string" + ] + }, + { + "open": "\"", + "close": "\"", + "notIn": [ + "string" + ] + }, + { + "open": "'", + "close": "'", + "notIn": [ + "string", + "comment" + ] + } ], "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["`", "`"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ], + [ + "`", + "`" + ] ], "indentationRules": { "increaseIndentPattern": "^.*(\\bcase\\b.*:|\\bdefault\\b:|(\\b(func|if|else|switch|select|for|struct)\\b.*)?{[^}\"'`]*|\\([^)\"'`]*)$", @@ -33,5 +91,20 @@ "start": "^\\s*//\\s*#?region\\b", "end": "^\\s*//\\s*#?endregion\\b" } - } -} \ No newline at end of file + }, + "onEnterRules": [ + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, + ] +} diff --git a/extensions/groovy/language-configuration.json b/extensions/groovy/language-configuration.json index a81a8864a5127..39e5fd4092c05 100644 --- a/extensions/groovy/language-configuration.json +++ b/extensions/groovy/language-configuration.json @@ -1,25 +1,88 @@ { "comments": { "lineComment": "//", - "blockComment": [ "/*", "*/" ] + "blockComment": [ + "/*", + "*/" + ] }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] ], "autoClosingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - { "open": "\"", "close": "\"", "notIn": ["string"] }, - { "open": "'", "close": "'", "notIn": ["string"] } + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + { + "open": "\"", + "close": "\"", + "notIn": [ + "string" + ] + }, + { + "open": "'", + "close": "'", + "notIn": [ + "string" + ] + } ], "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["\"", "\""], - ["'", "'"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ] + ], + "onEnterRules": [ + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, ] } diff --git a/extensions/java/language-configuration.json b/extensions/java/language-configuration.json index 610adc686b45c..6ba09bbd15c01 100644 --- a/extensions/java/language-configuration.json +++ b/extensions/java/language-configuration.json @@ -1,28 +1,85 @@ { "comments": { "lineComment": "//", - "blockComment": [ "/*", "*/" ] + "blockComment": [ + "/*", + "*/" + ] }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] ], "autoClosingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - { "open": "\"", "close": "\"", "notIn": ["string"] }, - { "open": "'", "close": "'", "notIn": ["string"] }, - { "open": "/**", "close": " */", "notIn": ["string"] } + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + { + "open": "\"", + "close": "\"", + "notIn": [ + "string" + ] + }, + { + "open": "'", + "close": "'", + "notIn": [ + "string" + ] + }, + { + "open": "/**", + "close": " */", + "notIn": [ + "string" + ] + } ], "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["<", ">"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ], + [ + "<", + ">" + ] ], "folding": { "markers": { @@ -97,6 +154,19 @@ "action": { "indent": "indent" } - } + }, + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, ] } diff --git a/extensions/json/language-configuration.json b/extensions/json/language-configuration.json index f9ec3fec78102..d47efe2587edb 100644 --- a/extensions/json/language-configuration.json +++ b/extensions/json/language-configuration.json @@ -1,22 +1,84 @@ { "comments": { "lineComment": "//", - "blockComment": [ "/*", "*/" ] + "blockComment": [ + "/*", + "*/" + ] }, "brackets": [ - ["{", "}"], - ["[", "]"] + [ + "{", + "}" + ], + [ + "[", + "]" + ] ], "autoClosingPairs": [ - { "open": "{", "close": "}", "notIn": ["string"] }, - { "open": "[", "close": "]", "notIn": ["string"] }, - { "open": "(", "close": ")", "notIn": ["string"] }, - { "open": "'", "close": "'", "notIn": ["string"] }, - { "open": "\"", "close": "\"", "notIn": ["string", "comment"] }, - { "open": "`", "close": "`", "notIn": ["string", "comment"] } + { + "open": "{", + "close": "}", + "notIn": [ + "string" + ] + }, + { + "open": "[", + "close": "]", + "notIn": [ + "string" + ] + }, + { + "open": "(", + "close": ")", + "notIn": [ + "string" + ] + }, + { + "open": "'", + "close": "'", + "notIn": [ + "string" + ] + }, + { + "open": "\"", + "close": "\"", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "`", + "close": "`", + "notIn": [ + "string", + "comment" + ] + } ], "indentationRules": { "increaseIndentPattern": "({+(?=((\\\\.|[^\"\\\\])*\"(\\\\.|[^\"\\\\])*\")*[^\"}]*)$)|(\\[+(?=((\\\\.|[^\"\\\\])*\"(\\\\.|[^\"\\\\])*\")*[^\"\\]]*)$)", "decreaseIndentPattern": "^\\s*[}\\]],?\\s*$" - } + }, + "onEnterRules": [ + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, + ] } diff --git a/extensions/less/language-configuration.json b/extensions/less/language-configuration.json index 7325d05270449..71e155ddfcc3e 100644 --- a/extensions/less/language-configuration.json +++ b/extensions/less/language-configuration.json @@ -1,26 +1,88 @@ { "comments": { - "blockComment": ["/*", "*/"], + "blockComment": [ + "/*", + "*/" + ], "lineComment": "//" }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] ], "autoClosingPairs": [ - { "open": "{", "close": "}", "notIn": ["string", "comment"] }, - { "open": "[", "close": "]", "notIn": ["string", "comment"] }, - { "open": "(", "close": ")", "notIn": ["string", "comment"] }, - { "open": "\"", "close": "\"", "notIn": ["string", "comment"] }, - { "open": "'", "close": "'", "notIn": ["string", "comment"] } + { + "open": "{", + "close": "}", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "[", + "close": "]", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "(", + "close": ")", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "\"", + "close": "\"", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "'", + "close": "'", + "notIn": [ + "string", + "comment" + ] + } ], "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["\"", "\""], - ["'", "'"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ] ], "folding": { "markers": { @@ -32,5 +94,20 @@ "increaseIndentPattern": "(^.*\\{[^}]*$)", "decreaseIndentPattern": "^\\s*\\}" }, - "wordPattern": "(#?-?\\d*\\.\\d\\w*%?)|(::?[\\w-]+(?=[^,{;]*[,{]))|(([@#.!])?[\\w-?]+%?|[@#!.])" + "wordPattern": "(#?-?\\d*\\.\\d\\w*%?)|(::?[\\w-]+(?=[^,{;]*[,{]))|(([@#.!])?[\\w-?]+%?|[@#!.])", + "onEnterRules": [ + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, + ] } diff --git a/extensions/objective-c/language-configuration.json b/extensions/objective-c/language-configuration.json index a81a8864a5127..39e5fd4092c05 100644 --- a/extensions/objective-c/language-configuration.json +++ b/extensions/objective-c/language-configuration.json @@ -1,25 +1,88 @@ { "comments": { "lineComment": "//", - "blockComment": [ "/*", "*/" ] + "blockComment": [ + "/*", + "*/" + ] }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] ], "autoClosingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - { "open": "\"", "close": "\"", "notIn": ["string"] }, - { "open": "'", "close": "'", "notIn": ["string"] } + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + { + "open": "\"", + "close": "\"", + "notIn": [ + "string" + ] + }, + { + "open": "'", + "close": "'", + "notIn": [ + "string" + ] + } ], "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["\"", "\""], - ["'", "'"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ] + ], + "onEnterRules": [ + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, ] } diff --git a/extensions/php/language-configuration.json b/extensions/php/language-configuration.json index f44d7a25cb618..d696ffa29503a 100644 --- a/extensions/php/language-configuration.json +++ b/extensions/php/language-configuration.json @@ -1,28 +1,96 @@ { "comments": { "lineComment": "//", // "#" - "blockComment": [ "/*", "*/" ] + "blockComment": [ + "/*", + "*/" + ] }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] ], "autoClosingPairs": [ - { "open": "{", "close": "}", "notIn": ["string"] }, - { "open": "[", "close": "]", "notIn": ["string"] }, - { "open": "(", "close": ")", "notIn": ["string"] }, - { "open": "'", "close": "'", "notIn": ["string", "comment"] }, - { "open": "\"", "close": "\"", "notIn": ["string", "comment"] }, - { "open": "/**", "close": " */", "notIn": ["string"] } + { + "open": "{", + "close": "}", + "notIn": [ + "string" + ] + }, + { + "open": "[", + "close": "]", + "notIn": [ + "string" + ] + }, + { + "open": "(", + "close": ")", + "notIn": [ + "string" + ] + }, + { + "open": "'", + "close": "'", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "\"", + "close": "\"", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "/**", + "close": " */", + "notIn": [ + "string" + ] + } ], "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["'", "'"], - ["\"", "\""], - ["`", "`"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "'", + "'" + ], + [ + "\"", + "\"" + ], + [ + "`", + "`" + ] ], "indentationRules": { "increaseIndentPattern": "({(?!.*}).*|\\(|\\[|((else(\\s)?)?if|else|for(each)?|while|switch|case).*:)\\s*((/[/*].*|)?$|\\?>)", @@ -85,6 +153,19 @@ "action": { "indent": "outdent" } - } + }, + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, ] } diff --git a/extensions/rust/language-configuration.json b/extensions/rust/language-configuration.json index ecb0007f6ea2c..490f4409c652c 100644 --- a/extensions/rust/language-configuration.json +++ b/extensions/rust/language-configuration.json @@ -1,25 +1,67 @@ { "comments": { "lineComment": "//", - "blockComment": [ "/*", "*/" ] + "blockComment": [ + "/*", + "*/" + ] }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] ], "autoClosingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - { "open": "\"", "close": "\"", "notIn": ["string"] } + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + { + "open": "\"", + "close": "\"", + "notIn": [ + "string" + ] + } ], "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["\"", "\""], - ["<", ">"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "<", + ">" + ] ], "indentationRules": { "increaseIndentPattern": "^.*\\{[^}\"']*$|^.*\\([^\\)\"']*$", @@ -30,5 +72,20 @@ "start": "^\\s*//\\s*#?region\\b", "end": "^\\s*//\\s*#?endregion\\b" } - } + }, + "onEnterRules": [ + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, + ] } diff --git a/extensions/swift/language-configuration.json b/extensions/swift/language-configuration.json index 54095ef5212e2..e1ceb1f6bc6fc 100644 --- a/extensions/swift/language-configuration.json +++ b/extensions/swift/language-configuration.json @@ -1,27 +1,99 @@ { "comments": { "lineComment": "//", - "blockComment": [ "/*", "*/" ] + "blockComment": [ + "/*", + "*/" + ] }, "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] ], "autoClosingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - { "open": "\"", "close": "\"", "notIn": ["string"] }, - { "open": "'", "close": "'", "notIn": ["string"] }, - { "open": "`", "close": "`", "notIn": ["string"] } + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + { + "open": "\"", + "close": "\"", + "notIn": [ + "string" + ] + }, + { + "open": "'", + "close": "'", + "notIn": [ + "string" + ] + }, + { + "open": "`", + "close": "`", + "notIn": [ + "string" + ] + } ], "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["`", "`"] + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ], + [ + "`", + "`" + ] + ], + "onEnterRules": [ + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, ] } From c2037f152b2bb3119ce1d87f52987776540dd57f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 6 Dec 2024 12:01:44 +0100 Subject: [PATCH 24/37] chat - some setup and quotas tweaks (#235477) --- .../parts/titlebar/media/titlebarpart.css | 4 -- .../chat/browser/actions/chatActions.ts | 59 +++++++++++-------- .../contrib/chat/browser/chatQuotasService.ts | 2 +- .../contrib/chat/browser/chatSetup.ts | 26 +++++--- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 6aa44168c7b95..0264e5460d4d3 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -207,10 +207,6 @@ border-color: var(--vscode-commandCenter-inactiveBorder) !important; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .codicon-copilot { - font-size: 14px; -} - .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:HOVER { color: var(--vscode-commandCenter-activeForeground); background-color: var(--vscode-commandCenter-activeBackground); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index ce421fb04911a..cca372c610304 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -20,7 +20,7 @@ import { ILocalizedString, localize, localize2 } from '../../../../../nls.js'; import { IActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js'; import { DropdownWithPrimaryActionViewItem } from '../../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; import { Action2, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IsLinuxContext, IsWindowsContext } from '../../../../../platform/contextkey/common/contextkeys.js'; import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -571,9 +571,12 @@ export class ChatCommandCenterRendering extends Disposable implements IWorkbench @IChatAgentService agentService: IChatAgentService, @IChatQuotasService chatQuotasService: IChatQuotasService, @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService contextKeyService: IContextKeyService, ) { super(); + const contextKeySet = new Set([ChatContextKeys.Setup.signedOut.key]); + actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatCommandCenter, (action, options) => { if (!(action instanceof SubmenuItemAction)) { return undefined; @@ -587,29 +590,39 @@ export class ChatCommandCenterRendering extends Disposable implements IWorkbench const chatExtensionInstalled = agentService.getAgents().some(agent => agent.isDefault); const { chatQuotaExceeded, completionsQuotaExceeded } = chatQuotasService.quotas; - - let primaryAction: MenuItemAction; - if (chatExtensionInstalled && !chatQuotaExceeded && !completionsQuotaExceeded) { - primaryAction = instantiationService.createInstance(MenuItemAction, { - id: CHAT_OPEN_ACTION_ID, - title: OpenChatGlobalAction.TITLE, - icon: Codicon.copilot, - }, undefined, undefined, undefined, undefined); - } else if (!chatExtensionInstalled) { - primaryAction = instantiationService.createInstance(MenuItemAction, { - id: 'workbench.action.chat.triggerSetup', - title: localize2('triggerChatSetup', "Use AI Features with Copilot for Free..."), - icon: Codicon.copilot, - }, undefined, undefined, undefined, undefined); + const signedOut = contextKeyService.getContextKeyValue(ChatContextKeys.Setup.signedOut.key) ?? false; + + let primaryActionId: string; + let primaryActionTitle: string; + let primaryActionIcon: ThemeIcon; + if (!chatExtensionInstalled) { + primaryActionId = 'workbench.action.chat.triggerSetup'; + primaryActionTitle = localize('triggerChatSetup', "Use AI Features with Copilot for Free..."); + primaryActionIcon = Codicon.copilot; } else { - primaryAction = instantiationService.createInstance(MenuItemAction, { - id: OPEN_CHAT_QUOTA_EXCEEDED_DIALOG, - title: quotaToButtonMessage({ chatQuotaExceeded, completionsQuotaExceeded }), - icon: Codicon.copilotWarning, - }, undefined, undefined, undefined, undefined); + if (signedOut) { + primaryActionId = CHAT_OPEN_ACTION_ID; + primaryActionTitle = localize('signInToChatSetup', "Sign in to Use Copilot..."); + primaryActionIcon = Codicon.copilotWarning; + } else if (chatQuotaExceeded || completionsQuotaExceeded) { + primaryActionId = OPEN_CHAT_QUOTA_EXCEEDED_DIALOG; + primaryActionTitle = quotaToButtonMessage({ chatQuotaExceeded, completionsQuotaExceeded }); + primaryActionIcon = Codicon.copilotWarning; + } else { + primaryActionId = CHAT_OPEN_ACTION_ID; + primaryActionTitle = OpenChatGlobalAction.TITLE.value; + primaryActionIcon = Codicon.copilot; + } } - - return instantiationService.createInstance(DropdownWithPrimaryActionViewItem, primaryAction, dropdownAction, action.actions, '', { ...options, skipTelemetry: true }); - }, Event.any(agentService.onDidChangeAgents, chatQuotasService.onDidChangeQuotas)); + return instantiationService.createInstance(DropdownWithPrimaryActionViewItem, instantiationService.createInstance(MenuItemAction, { + id: primaryActionId, + title: primaryActionTitle, + icon: primaryActionIcon, + }, undefined, undefined, undefined, undefined), dropdownAction, action.actions, '', { ...options, skipTelemetry: true }); + }, Event.any( + agentService.onDidChangeAgents, + chatQuotasService.onDidChangeQuotas, + Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(contextKeySet)) + )); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts b/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts index 6ed345aacdb9c..11cb99aa8eb50 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts @@ -173,7 +173,7 @@ export class ChatQuotasService extends Disposable implements IChatQuotasService ], custom: { closeOnLinkClick: true, - icon: Codicon.copilotWarning, + icon: Codicon.copilotWarningLarge, markdownDetails: [ { markdown: new MarkdownString(message, true) }, { markdown: new MarkdownString(upgradeToPro, true) } diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 0e6fa39883763..b4eca48c3bbbf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -180,7 +180,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr await that.context.update({ triggered: true }); - showCopilotView(viewsService); + showCopilotView(viewsService, layoutService); ensureSideBarChatViewSize(400, viewDescriptorService, layoutService); if (startSetup === true && !ASK_FOR_PUBLIC_CODE_MATCHES) { @@ -616,7 +616,8 @@ class ChatSetupController extends Disposable { @IProgressService private readonly progressService: IProgressService, @IChatAgentService private readonly chatAgentService: IChatAgentService, @IActivityService private readonly activityService: IActivityService, - @ICommandService private readonly commandService: ICommandService + @ICommandService private readonly commandService: ICommandService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(); @@ -692,7 +693,7 @@ class ChatSetupController extends Disposable { let session: AuthenticationSession | undefined; let entitlement: ChatEntitlement | undefined; try { - showCopilotView(this.viewsService); + showCopilotView(this.viewsService, this.layoutService); session = await this.authenticationService.createSession(defaultChat.providerId, defaultChat.providerScopes[0]); entitlement = await this.requests.forceResolveEntitlement(session); } catch (error) { @@ -714,7 +715,7 @@ class ChatSetupController extends Disposable { const wasInstalled = this.context.state.installed; let didSignUp = false; try { - showCopilotView(this.viewsService); + showCopilotView(this.viewsService, this.layoutService); if (entitlement !== ChatEntitlement.Limited && entitlement !== ChatEntitlement.Pro && entitlement !== ChatEntitlement.Unavailable) { didSignUp = await this.requests.signUpLimited(session, options); @@ -749,7 +750,7 @@ class ChatSetupController extends Disposable { const currentActiveElement = getActiveElement(); if (activeElement === currentActiveElement || currentActiveElement === mainWindow.document.body) { - (await showCopilotView(this.viewsService))?.focusInput(); + (await showCopilotView(this.viewsService, this.layoutService))?.focusInput(); } } } @@ -953,7 +954,7 @@ class ChatSetupContext extends Disposable { super(); this.checkExtensionInstallation(); - this.updateContext(); + this.updateContextSync(); } private async checkExtensionInstallation(): Promise { @@ -1014,6 +1015,10 @@ class ChatSetupContext extends Disposable { private async updateContext(): Promise { await this.updateBarrier?.wait(); + this.updateContextSync(); + } + + private updateContextSync(): void { this.logService.trace(`[chat setup] updateContext(): ${JSON.stringify(this._state)}`); if (this._state.triggered && !this._state.installed) { @@ -1060,7 +1065,14 @@ function isCopilotEditsViewActive(viewsService: IViewsService): boolean { return viewsService.getFocusedView()?.id === EditsViewId; } -function showCopilotView(viewsService: IViewsService): Promise { +function showCopilotView(viewsService: IViewsService, layoutService: IWorkbenchLayoutService): Promise { + + // Ensure main window is in front + if (layoutService.activeContainer !== layoutService.mainContainer) { + layoutService.mainContainer.focus(); + } + + // Bring up the correct view if (isCopilotEditsViewActive(viewsService)) { return showEditsView(viewsService); } else { From 9d3edd480c56d906565ecde462716867aa9379f9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:15:35 +0100 Subject: [PATCH 25/37] SCM - dirty diff refactoring + clean-up (#235486) * DirtyDiff - clean-up original model variables * Add more information to the result object * A bit more cleanup * Switch to a DisposableMap --- src/vs/editor/common/diff/rangeMapping.ts | 36 ++-- .../api/browser/mainThreadEditors.ts | 14 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 202 ++++++++---------- .../workbench/contrib/scm/common/quickDiff.ts | 13 +- 4 files changed, 133 insertions(+), 132 deletions(-) diff --git a/src/vs/editor/common/diff/rangeMapping.ts b/src/vs/editor/common/diff/rangeMapping.ts index 09021d118b747..f6565bdc4e2f7 100644 --- a/src/vs/editor/common/diff/rangeMapping.ts +++ b/src/vs/editor/common/diff/rangeMapping.ts @@ -383,28 +383,22 @@ export function getLineRangeMapping(rangeMapping: RangeMapping, originalLines: A return new DetailedLineRangeMapping(originalLineRange, modifiedLineRange, [rangeMapping]); } -export function lineRangeMappingFromChanges(changes: IChange[]): LineRangeMapping[] { - const lineRangeMapping: LineRangeMapping[] = []; - - for (const change of changes) { - let originalRange: LineRange; - if (change.originalEndLineNumber === 0) { - // Insertion - originalRange = new LineRange(change.originalStartLineNumber + 1, change.originalStartLineNumber + 1); - } else { - originalRange = new LineRange(change.originalStartLineNumber, change.originalEndLineNumber + 1); - } - - let modifiedRange: LineRange; - if (change.modifiedEndLineNumber === 0) { - // Deletion - modifiedRange = new LineRange(change.modifiedStartLineNumber + 1, change.modifiedStartLineNumber + 1); - } else { - modifiedRange = new LineRange(change.modifiedStartLineNumber, change.modifiedEndLineNumber + 1); - } +export function lineRangeMappingFromChange(change: IChange): LineRangeMapping { + let originalRange: LineRange; + if (change.originalEndLineNumber === 0) { + // Insertion + originalRange = new LineRange(change.originalStartLineNumber + 1, change.originalStartLineNumber + 1); + } else { + originalRange = new LineRange(change.originalStartLineNumber, change.originalEndLineNumber + 1); + } - lineRangeMapping.push(new LineRangeMapping(originalRange, modifiedRange)); + let modifiedRange: LineRange; + if (change.modifiedEndLineNumber === 0) { + // Deletion + modifiedRange = new LineRange(change.modifiedStartLineNumber + 1, change.modifiedStartLineNumber + 1); + } else { + modifiedRange = new LineRange(change.modifiedStartLineNumber, change.modifiedEndLineNumber + 1); } - return lineRangeMapping; + return new LineRangeMapping(originalRange, modifiedRange); } diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 93af5bc077d59..73d81e729cbbc 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -166,7 +166,12 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } return observableFromEvent(this, dirtyDiffModel.onDidChange, () => { - return dirtyDiffModel.getQuickDiffResults(); + return dirtyDiffModel.getQuickDiffResults() + .map(result => ({ + original: result.original, + modified: result.modified, + changes: result.changes2 + })); }); } @@ -180,7 +185,12 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } return observableFromEvent(Event.any(dirtyDiffModel.onDidChange, diffEditor.onDidUpdateDiff), () => { - const dirtyDiffInformation = dirtyDiffModel.getQuickDiffResults(); + const dirtyDiffInformation = dirtyDiffModel.getQuickDiffResults() + .map(result => ({ + original: result.original, + modified: result.modified, + changes: result.changes2 + })); const diffChanges = diffEditor.getDiffComputationResult()?.changes2 ?? []; const diffInformation = [{ diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 098df9c8c1b72..7ca28f94265d1 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -7,7 +7,7 @@ import * as nls from '../../../../nls.js'; import './media/dirtydiffDecorator.css'; import { ThrottledDelayer } from '../../../../base/common/async.js'; -import { IDisposable, dispose, toDisposable, Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { IDisposable, dispose, toDisposable, Disposable, DisposableStore, DisposableMap } from '../../../../base/common/lifecycle.js'; import { Event, Emitter } from '../../../../base/common/event.js'; import * as ext from '../../../common/contributions.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -57,12 +57,13 @@ import { ResourceMap } from '../../../../base/common/map.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; -import { IQuickDiffService, QuickDiff, QuickDiffResult } from '../common/quickDiff.js'; +import { IQuickDiffService, QuickDiff, QuickDiffChange, QuickDiffResult } from '../common/quickDiff.js'; import { IQuickDiffSelectItem, SwitchQuickDiffBaseAction, SwitchQuickDiffViewItem } from './dirtyDiffSwitcher.js'; import { IChatEditingService, WorkingSetEntryState } from '../../chat/common/chatEditingService.js'; -import { lineRangeMappingFromChanges } from '../../../../editor/common/diff/rangeMapping.js'; +import { LineRangeMapping, lineRangeMappingFromChange } from '../../../../editor/common/diff/rangeMapping.js'; import { DiffState } from '../../../../editor/browser/widget/diffEditor/diffEditorViewModel.js'; import { toLineChanges } from '../../../../editor/browser/widget/diffEditor/diffEditorWidget.js'; +import { Iterable } from '../../../../base/common/iterator.js'; class DiffActionRunner extends ActionRunner { @@ -204,8 +205,10 @@ class DirtyDiffWidget extends PeekViewWidget { this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme, this)); this._applyTheme(themeService.getColorTheme()); - if (this.model.original.length > 0) { - contextKeyService = contextKeyService.createOverlay([['originalResourceScheme', this.model.original[0].uri.scheme], ['originalResourceSchemes', this.model.original.map(original => original.uri.scheme)]]); + if (!Iterable.isEmpty(this.model.originalTextModels)) { + contextKeyService = contextKeyService.createOverlay([ + ['originalResourceScheme', Iterable.first(this.model.originalTextModels)?.uri.scheme], + ['originalResourceSchemes', Iterable.map(this.model.originalTextModels, textModel => textModel.uri.scheme)]]); } this.create(); @@ -234,15 +237,13 @@ class DirtyDiffWidget extends PeekViewWidget { const labeledChange = this.model.changes[index]; const change = labeledChange.change; this._index = index; - this.contextKeyService.createKey('originalResourceScheme', this.model.changes[index].uri.scheme); + this.contextKeyService.createKey('originalResourceScheme', this.model.changes[index].original.scheme); this.updateActions(); this._provider = labeledChange.label; this.change = change; - const originalModel = this.model.original; - - if (!originalModel) { + if (Iterable.isEmpty(this.model.originalTextModels)) { return; } @@ -252,7 +253,7 @@ class DirtyDiffWidget extends PeekViewWidget { // non-side-by-side diff still hasn't created the view zones onFirstDiffUpdate(() => setTimeout(() => this.revealChange(change), 0)); - const diffEditorModel = this.model.getDiffEditorModel(labeledChange.uri.toString()); + const diffEditorModel = this.model.getDiffEditorModel(labeledChange.original); if (!diffEditorModel) { return; } @@ -291,7 +292,7 @@ class DirtyDiffWidget extends PeekViewWidget { } private renderTitle(label: string): void { - const providerChanges = this.model.mapChanges.get(label)!; + const providerChanges = this.model.quickDiffChanges.get(label)!; const providerIndex = providerChanges.indexOf(this._index); let detail: string; @@ -336,16 +337,8 @@ class DirtyDiffWidget extends PeekViewWidget { } private shouldUseDropdown(): boolean { - let providersWithChangesCount = 0; - if (this.model.mapChanges.size > 1) { - const keys = Array.from(this.model.mapChanges.keys()); - for (let i = 0; (i < keys.length) && (providersWithChangesCount <= 1); i++) { - if (this.model.mapChanges.get(keys[i])!.length > 0) { - providersWithChangesCount++; - } - } - } - return providersWithChangesCount >= 2; + return this.model.getQuickDiffResults() + .filter(quickDiff => quickDiff.changes.length > 0).length > 1; } private updateActions(): void { @@ -787,7 +780,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu if (this.editor.hasModel() && (typeof lineNumber === 'number' || !this.widget.provider)) { index = this.model.findNextClosestChange(typeof lineNumber === 'number' ? lineNumber : this.editor.getPosition().lineNumber, true, this.widget.provider); } else { - const providerChanges: number[] = this.model.mapChanges.get(this.widget.provider) ?? this.model.mapChanges.values().next().value!; + const providerChanges: number[] = this.model.quickDiffChanges.get(this.widget.provider) ?? this.model.quickDiffChanges.values().next().value!; const mapIndex = providerChanges.findIndex(value => value === this.widget!.index); index = providerChanges[rot(mapIndex + 1, providerChanges.length)]; } @@ -807,7 +800,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu if (this.editor.hasModel() && (typeof lineNumber === 'number')) { index = this.model.findPreviousClosestChange(typeof lineNumber === 'number' ? lineNumber : this.editor.getPosition().lineNumber, true, this.widget.provider); } else { - const providerChanges: number[] = this.model.mapChanges.get(this.widget.provider) ?? this.model.mapChanges.values().next().value!; + const providerChanges: number[] = this.model.quickDiffChanges.get(this.widget.provider) ?? this.model.quickDiffChanges.values().next().value!; const mapIndex = providerChanges.findIndex(value => value === this.widget!.index); index = providerChanges[rot(mapIndex - 1, providerChanges.length)]; } @@ -879,7 +872,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu return true; } - private onDidModelChange(splices: ISplice[]): void { + private onDidModelChange(splices: ISplice[]): void { if (!this.model || !this.widget || this.widget.hasFocus()) { return; } @@ -1210,29 +1203,34 @@ export async function getOriginalResource(quickDiffService: IQuickDiffService, u return quickDiffs.length > 0 ? quickDiffs[0].originalResource : null; } -type LabeledChange = { change: IChange; label: string; uri: URI }; - export class DirtyDiffModel extends Disposable { - private _quickDiffs: QuickDiff[] = []; - private _originalModels: Map = new Map(); // key is uri.toString() - private _originalTextModels: ITextModel[] = []; private _model: ITextFileEditorModel; - get original(): ITextModel[] { return this._originalTextModels; } - private diffDelayer = new ThrottledDelayer(200); - private _quickDiffsPromise?: Promise; - private repositoryDisposables = new Set(); - private readonly originalModelDisposables = this._register(new DisposableStore()); + private readonly _originalEditorModels = new ResourceMap(); + private readonly _originalEditorModelsDisposables = this._register(new DisposableStore()); + get originalTextModels(): Iterable { + return Iterable.map(this._originalEditorModels.values(), editorModel => editorModel.textEditorModel); + } + private _disposed = false; + private _quickDiffs: QuickDiff[] = []; + private _quickDiffsPromise?: Promise; + private _diffDelayer = new ThrottledDelayer(200); + + private readonly _onDidChange = new Emitter<{ changes: QuickDiffChange[]; diff: ISplice[] }>(); + readonly onDidChange: Event<{ changes: QuickDiffChange[]; diff: ISplice[] }> = this._onDidChange.event; - private readonly _onDidChange = new Emitter<{ changes: LabeledChange[]; diff: ISplice[] }>(); - readonly onDidChange: Event<{ changes: LabeledChange[]; diff: ISplice[] }> = this._onDidChange.event; + private _changes: QuickDiffChange[] = []; + get changes(): QuickDiffChange[] { return this._changes; } - private _changes: LabeledChange[] = []; - get changes(): LabeledChange[] { return this._changes; } - private _mapChanges: Map = new Map(); // key is the quick diff name, value is the index of the change in this._changes - get mapChanges(): Map { return this._mapChanges; } + /** + * Map of quick diff name to the index of the change in `this.changes` + */ + private _quickDiffChanges: Map = new Map(); + get quickDiffChanges(): Map { return this._quickDiffChanges; } + + private readonly _repositoryDisposables = new DisposableMap(); constructor( textFileModel: IResolvedTextFileEditorModel, @@ -1260,10 +1258,9 @@ export class DirtyDiffModel extends Disposable { } this._register(this._model.onDidChangeEncoding(() => { - this.diffDelayer.cancel(); + this._diffDelayer.cancel(); this._quickDiffs = []; - this._originalModels.clear(); - this._originalTextModels = []; + this._originalEditorModels.clear(); this._quickDiffsPromise = undefined; this.setChanges([], new Map()); this.triggerDiff(); @@ -1280,65 +1277,56 @@ export class DirtyDiffModel extends Disposable { public getQuickDiffResults(): QuickDiffResult[] { return this._quickDiffs.map(quickDiff => { - const changes = this._changes - .filter(change => change.label === quickDiff.label) - .map(change => change.change); + const changes = this.changes + .filter(change => change.label === quickDiff.label); - // Convert IChange[] to LineRangeMapping[] - const lineRangeMappings = lineRangeMappingFromChanges(changes); return { + label: quickDiff.label, original: quickDiff.originalResource, modified: this._model.resource, - changes: lineRangeMappings + changes: changes.map(change => change.change), + changes2: changes.map(change => change.change2) }; }); } - public getDiffEditorModel(originalUri: string): IDiffEditorModel | undefined { - if (!this._originalModels.has(originalUri)) { - return; - } - const original = this._originalModels.get(originalUri)!; - - return { - modified: this._model.textEditorModel!, - original: original.textEditorModel - }; + public getDiffEditorModel(originalUri: URI): IDiffEditorModel | undefined { + const editorModel = this._originalEditorModels.get(originalUri); + return editorModel ? + { + modified: this._model.textEditorModel!, + original: editorModel.textEditorModel + } : undefined; } private onDidAddRepository(repository: ISCMRepository): void { const disposables = new DisposableStore(); - this.repositoryDisposables.add(disposables); - disposables.add(toDisposable(() => this.repositoryDisposables.delete(disposables))); - disposables.add(repository.provider.onDidChangeResources(this.triggerDiff, this)); - const onDidRemoveThis = Event.filter(this.scmService.onDidRemoveRepository, r => r === repository); - disposables.add(onDidRemoveThis(() => dispose(disposables), null)); + const onDidRemoveRepository = Event.filter(this.scmService.onDidRemoveRepository, r => r === repository); + disposables.add(onDidRemoveRepository(() => this._repositoryDisposables.deleteAndDispose(repository))); + + this._repositoryDisposables.set(repository, disposables); this.triggerDiff(); } private triggerDiff(): void { - if (!this.diffDelayer) { + if (!this._diffDelayer) { return; } - this.diffDelayer + this._diffDelayer .trigger(async () => { - const result: { changes: LabeledChange[]; mapChanges: Map } | null = await this.diff(); + const result: { changes: QuickDiffChange[]; mapChanges: Map } | null = await this.diff(); - const originalModels = Array.from(this._originalModels.values()); - if (!result || this._disposed || this._model.isDisposed() || originalModels.some(originalModel => originalModel.isDisposed())) { + const editorModels = Array.from(this._originalEditorModels.values()); + if (!result || this._disposed || this._model.isDisposed() || editorModels.some(editorModel => editorModel.isDisposed())) { return; // disposed } - if (originalModels.every(originalModel => originalModel.textEditorModel.getValueLength() === 0)) { - result.changes = []; - } - - if (!result.changes) { + if (editorModels.every(editorModel => editorModel.textEditorModel.getValueLength() === 0)) { result.changes = []; } @@ -1347,14 +1335,14 @@ export class DirtyDiffModel extends Disposable { .catch(err => onUnexpectedError(err)); } - private setChanges(changes: LabeledChange[], mapChanges: Map): void { - const diff = sortedDiff(this._changes, changes, (a, b) => compareChanges(a.change, b.change)); + private setChanges(changes: QuickDiffChange[], mapChanges: Map): void { + const diff = sortedDiff(this.changes, changes, (a, b) => compareChanges(a.change, b.change)); this._changes = changes; - this._mapChanges = mapChanges; + this._quickDiffChanges = mapChanges; this._onDidChange.fire({ changes, diff }); } - private diff(): Promise<{ changes: LabeledChange[]; mapChanges: Map } | null> { + private diff(): Promise<{ changes: QuickDiffChange[]; mapChanges: Map } | null> { return this.progressService.withProgress({ location: ProgressLocation.Scm, delay: 250 }, async () => { const originalURIs = await this.getQuickDiffsPromise(); if (this._disposed || this._model.isDisposed() || (originalURIs.length === 0)) { @@ -1371,14 +1359,18 @@ export class DirtyDiffModel extends Disposable { ? this.configurationService.getValue('diffEditor.ignoreTrimWhitespace') : ignoreTrimWhitespaceSetting !== 'false'; - const allDiffs: LabeledChange[] = []; + const allDiffs: QuickDiffChange[] = []; for (const quickDiff of filteredToDiffable) { const dirtyDiff = await this._diff(quickDiff.originalResource, this._model.resource, ignoreTrimWhitespace); - if (dirtyDiff) { - for (const diff of dirtyDiff) { - if (diff) { - allDiffs.push({ change: diff, label: quickDiff.label, uri: quickDiff.originalResource }); - } + if (dirtyDiff.changes && dirtyDiff.changes2 && dirtyDiff.changes.length === dirtyDiff.changes2.length) { + for (let index = 0; index < dirtyDiff.changes.length; index++) { + allDiffs.push({ + label: quickDiff.label, + original: quickDiff.originalResource, + modified: this._model.resource, + change: dirtyDiff.changes[index], + change2: dirtyDiff.changes2[index] + }); } } } @@ -1395,22 +1387,19 @@ export class DirtyDiffModel extends Disposable { }); } - private async _diff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { + private async _diff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<{ changes: readonly IChange[] | null; changes2: readonly LineRangeMapping[] | null }> { if (this.algorithm === undefined) { - return this.editorWorkerService.computeDirtyDiff(original, modified, ignoreTrimWhitespace); + const changes = await this.editorWorkerService.computeDirtyDiff(original, modified, ignoreTrimWhitespace); + return { changes, changes2: changes?.map(change => lineRangeMappingFromChange(change)) ?? null }; } - const diffResult = await this.editorWorkerService.computeDiff(original, modified, { + const result = await this.editorWorkerService.computeDiff(original, modified, { computeMoves: false, ignoreTrimWhitespace, maxComputationTimeMs: Number.MAX_SAFE_INTEGER }, this.algorithm); - if (!diffResult) { - return null; - } - - return toLineChanges(DiffState.fromDiffResult(diffResult)); + return { changes: result ? toLineChanges(DiffState.fromDiffResult(result)) : null, changes2: result?.changes ?? null }; } private getQuickDiffsPromise(): Promise { @@ -1425,8 +1414,7 @@ export class DirtyDiffModel extends Disposable { if (quickDiffs.length === 0) { this._quickDiffs = []; - this._originalModels.clear(); - this._originalTextModels = []; + this._originalEditorModels.clear(); return []; } @@ -1434,10 +1422,10 @@ export class DirtyDiffModel extends Disposable { return quickDiffs; } - this.originalModelDisposables.clear(); - this._originalModels.clear(); - this._originalTextModels = []; this._quickDiffs = quickDiffs; + + this._originalEditorModels.clear(); + this._originalEditorModelsDisposables.clear(); return (await Promise.all(quickDiffs.map(async (quickDiff) => { try { const ref = await this.textModelResolverService.createModelReference(quickDiff.originalResource); @@ -1446,8 +1434,7 @@ export class DirtyDiffModel extends Disposable { return []; } - this._originalModels.set(quickDiff.originalResource.toString(), ref.object); - this._originalTextModels.push(ref.object.textEditorModel); + this._originalEditorModels.set(quickDiff.originalResource, ref.object); if (isTextFileEditorModel(ref.object)) { const encoding = this._model.getEncoding(); @@ -1457,8 +1444,8 @@ export class DirtyDiffModel extends Disposable { } } - this.originalModelDisposables.add(ref); - this.originalModelDisposables.add(ref.object.textEditorModel.onDidChangeContent(() => this.triggerDiff())); + this._originalEditorModelsDisposables.add(ref); + this._originalEditorModelsDisposables.add(ref.object.textEditorModel.onDidChangeContent(() => this.triggerDiff())); return quickDiff; } catch (error) { @@ -1553,15 +1540,14 @@ export class DirtyDiffModel extends Disposable { } override dispose(): void { - super.dispose(); - this._disposed = true; + this._quickDiffs = []; - this._originalModels.clear(); - this._originalTextModels = []; - this.diffDelayer.cancel(); - this.repositoryDisposables.forEach(d => dispose(d)); - this.repositoryDisposables.clear(); + this._diffDelayer.cancel(); + this._originalEditorModels.clear(); + this._repositoryDisposables.dispose(); + + super.dispose(); } } diff --git a/src/vs/workbench/contrib/scm/common/quickDiff.ts b/src/vs/workbench/contrib/scm/common/quickDiff.ts index 0720dc9f5ff41..3770f071c7219 100644 --- a/src/vs/workbench/contrib/scm/common/quickDiff.ts +++ b/src/vs/workbench/contrib/scm/common/quickDiff.ts @@ -9,6 +9,7 @@ import { IDisposable } from '../../../../base/common/lifecycle.js'; import { LanguageSelector } from '../../../../editor/common/languageSelector.js'; import { Event } from '../../../../base/common/event.js'; import { LineRangeMapping } from '../../../../editor/common/diff/rangeMapping.js'; +import { IChange } from '../../../../editor/common/diff/legacyLinesDiffComputer.js'; export const IQuickDiffService = createDecorator('quickDiff'); @@ -28,10 +29,20 @@ export interface QuickDiff { visible: boolean; } +export interface QuickDiffChange { + readonly label: string; + readonly original: URI; + readonly modified: URI; + readonly change: IChange; + readonly change2: LineRangeMapping; +} + export interface QuickDiffResult { + readonly label: string; readonly original: URI; readonly modified: URI; - readonly changes: readonly LineRangeMapping[]; + readonly changes: IChange[]; + readonly changes2: LineRangeMapping[]; } export interface IQuickDiffService { From 7b991b48600f10508f35ca67e368c4584548eb3e Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:16:04 +0100 Subject: [PATCH 26/37] Fix explorer find rerender error (#235488) fix #234129 --- .../workbench/contrib/files/browser/views/explorerViewer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 5951803d0ae1d..9f15f33a26bdc 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -562,7 +562,9 @@ export class ExplorerFindProvider implements IAsyncFindProvider { const tree = this.treeProvider(); for (const directory of highlightedDirectories) { - tree.rerender(directory); + if (tree.hasNode(directory)) { + tree.rerender(directory); + } } } From 354482357af4ff091191b9fa5c607bd23c6430c0 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 6 Dec 2024 14:20:30 +0100 Subject: [PATCH 27/37] Await that decorations are added with definition hover preview before showing hover (#235489) returning promise --- .../contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts index 6f6f8323e022f..70c093ee9d8f3 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts @@ -196,7 +196,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri return; } - this.textModelResolverService.createModelReference(result.uri).then(ref => { + return this.textModelResolverService.createModelReference(result.uri).then(ref => { if (!ref.object || !ref.object.textEditorModel) { ref.dispose(); From ad42640a78864e43008b8d5b750d04e96dd05218 Mon Sep 17 00:00:00 2001 From: Pankaj Khandelwal <44100113+pankajk07@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:30:53 +0530 Subject: [PATCH 28/37] fix: disabled PlzDedicatedWorker in Electron (#233175) * fix: disabled PlzDedicatedWorker in Electron * chore: cleanup --------- Co-authored-by: deepak1556 --- src/main.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 2d9c977a3bdb3..1504375283f39 100644 --- a/src/main.ts +++ b/src/main.ts @@ -288,8 +288,9 @@ function configureCommandlineSwitchesSync(cliArgs: NativeParsedArgs) { // Following features are disabled from the runtime: // `CalculateNativeWinOcclusion` - Disable native window occlusion tracker (https://groups.google.com/a/chromium.org/g/embedder-dev/c/ZF3uHHyWLKw/m/VDN2hDXMAAAJ) + // `PlzDedicatedWorker` - Refs https://github.com/microsoft/vscode/issues/233060#issuecomment-2523212427 const featuresToDisable = - `CalculateNativeWinOcclusion,${app.commandLine.getSwitchValue('disable-features')}`; + `CalculateNativeWinOcclusion,PlzDedicatedWorker,${app.commandLine.getSwitchValue('disable-features')}`; app.commandLine.appendSwitch('disable-features', featuresToDisable); // Blink features to configure. From 5d8ea135391debc0ccabf6d6cb9c35e572ef469b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 6 Dec 2024 15:48:28 +0100 Subject: [PATCH 29/37] Ben/active-ocelot (#235494) * chat - make quota reset date explicitly `undefined` if neede * chat - tweak status quota indication * chat - tweak status indicator further --- .../contrib/chat/browser/chatQuotasService.ts | 50 +++++++++++++------ .../contrib/chat/browser/chatSetup.ts | 2 +- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts b/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts index 11cb99aa8eb50..efa9304c9742f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuotasService.ts @@ -36,9 +36,9 @@ export interface IChatQuotasService { } export interface IChatQuotas { - readonly chatQuotaExceeded: boolean; - readonly completionsQuotaExceeded: boolean; - readonly quotaResetDate: Date; + chatQuotaExceeded: boolean; + completionsQuotaExceeded: boolean; + quotaResetDate: Date | undefined; } export const OPEN_CHAT_QUOTA_EXCEEDED_DIALOG = 'workbench.action.chat.openQuotaExceededDialog'; @@ -50,7 +50,7 @@ export class ChatQuotasService extends Disposable implements IChatQuotasService private readonly _onDidChangeQuotas = this._register(new Emitter()); readonly onDidChangeQuotas: Event = this._onDidChangeQuotas.event; - private _quotas = { chatQuotaExceeded: false, completionsQuotaExceeded: false, quotaResetDate: new Date(0) }; + private _quotas: IChatQuotas = { chatQuotaExceeded: false, completionsQuotaExceeded: false, quotaResetDate: undefined }; get quotas(): IChatQuotas { return this._quotas; } private readonly chatQuotaExceededContextKey = ChatContextKeys.chatQuotaExceeded.bindTo(this.contextKeyService); @@ -227,7 +227,7 @@ export class ChatQuotasService extends Disposable implements IChatQuotasService clearQuotas(): void { if (this.quotas.chatQuotaExceeded || this.quotas.completionsQuotaExceeded) { - this.acceptQuotas({ chatQuotaExceeded: false, completionsQuotaExceeded: false, quotaResetDate: new Date(0) }); + this.acceptQuotas({ chatQuotaExceeded: false, completionsQuotaExceeded: false, quotaResetDate: undefined }); } } @@ -241,6 +241,8 @@ export class ChatQuotasStatusBarEntry extends Disposable implements IWorkbenchCo static readonly ID = 'chat.quotasStatusBarEntry'; + private static readonly COPILOT_STATUS_ID = 'GitHub.copilot.status'; // TODO@bpasero unify into 1 core indicator + private readonly _entry = this._register(new MutableDisposable()); constructor( @@ -254,19 +256,39 @@ export class ChatQuotasStatusBarEntry extends Disposable implements IWorkbenchCo private updateStatusbarEntry(): void { const { chatQuotaExceeded, completionsQuotaExceeded } = this.chatQuotasService.quotas; + + // Some quota exceeded, show indicator if (chatQuotaExceeded || completionsQuotaExceeded) { - // Some quota exceeded, show indicator + let text: string; + if (chatQuotaExceeded && !completionsQuotaExceeded) { + text = localize('chatQuotaExceededStatus', "Chat limit reached"); + } else if (completionsQuotaExceeded && !chatQuotaExceeded) { + text = localize('completionsQuotaExceededStatus', "Completions limit reached"); + } else { + text = localize('chatAndCompletionsQuotaExceededStatus', "Copilot limit reached"); + } + + const isCopilotStatusVisible = this.statusbarService.isEntryVisible(ChatQuotasStatusBarEntry.COPILOT_STATUS_ID); + if (!isCopilotStatusVisible) { + text = `$(copilot-warning) ${text}`; + } + this._entry.value = this.statusbarService.addEntry({ - name: localize('indicator', "Copilot Quota Indicator"), - text: localize('limitReached', "Copilot Limit Reached"), - ariaLabel: localize('copilotQuotaExceeded', "Copilot Limit Reached"), + name: localize('indicator', "Copilot Limit Indicator"), + text, + ariaLabel: text, command: OPEN_CHAT_QUOTA_EXCEEDED_DIALOG, - kind: 'prominent', showInAllWindows: true, - tooltip: quotaToButtonMessage({ chatQuotaExceeded, completionsQuotaExceeded }), - }, ChatQuotasStatusBarEntry.ID, StatusbarAlignment.RIGHT, { id: 'GitHub.copilot.status', alignment: StatusbarAlignment.RIGHT }); // TODO@bpasero unify into 1 core indicator - } else { - // No quota exceeded, remove indicator + tooltip: quotaToButtonMessage({ chatQuotaExceeded, completionsQuotaExceeded }) + }, ChatQuotasStatusBarEntry.ID, StatusbarAlignment.RIGHT, { + id: ChatQuotasStatusBarEntry.COPILOT_STATUS_ID, + alignment: StatusbarAlignment.RIGHT, + compact: isCopilotStatusVisible + }); + } + + // No quota exceeded, remove indicator + else { this._entry.clear(); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index b4eca48c3bbbf..27c17e0617254 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -512,7 +512,7 @@ class ChatSetupRequests extends Disposable { this.chatQuotasService.acceptQuotas({ chatQuotaExceeded: typeof state.quotas.chat === 'number' ? state.quotas.chat <= 0 : false, completionsQuotaExceeded: typeof state.quotas.completions === 'number' ? state.quotas.completions <= 0 : false, - quotaResetDate: state.quotas.resetDate ? new Date(state.quotas.resetDate) : new Date(0) + quotaResetDate: state.quotas.resetDate ? new Date(state.quotas.resetDate) : undefined }); } } From 33ba5efde7fd9fbd635acf4c2f3d605b4b871576 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 6 Dec 2024 16:13:56 +0100 Subject: [PATCH 30/37] Use AI features for free button does nothing in new profile (fix microsoft/vscode-copilot#11066) (#235495) --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 9506286866508..2aa243649de88 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -124,6 +124,7 @@ configurationRegistry.registerConfiguration({ 'chat.experimental.offerSetup': { type: 'boolean', default: false, + scope: ConfigurationScope.APPLICATION, markdownDescription: nls.localize('chat.experimental.offerSetup', "Controls whether setup is offered for Chat if not done already."), tags: ['experimental', 'onExP'] }, From 96098190240692109dd0de866cdbf74090377823 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 6 Dec 2024 16:23:47 +0100 Subject: [PATCH 31/37] makes inline chat hint stable wrt blame decoration (#235497) - change from injected text to after decoration - use class name for awol attachedData https://github.com/microsoft/vscode-copilot/issues/10943 --- .../browser/inlineChatCurrentLine.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index 5f48988ff0626..8cf0f0891a67f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -16,7 +16,7 @@ import { Range } from '../../../../editor/common/core/range.js'; import { IPosition, Position } from '../../../../editor/common/core/position.js'; import { AbstractInlineChatAction } from './inlineChatActions.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; -import { InjectedTextCursorStops, IValidEditOperation, TrackedRangeStickiness } from '../../../../editor/common/model.js'; +import { IValidEditOperation, TrackedRangeStickiness } from '../../../../editor/common/model.js'; import { URI } from '../../../../base/common/uri.js'; import { isEqual } from '../../../../base/common/resources.js'; import { StandardTokenType } from '../../../../editor/common/encodedTokenAttributes.js'; @@ -36,6 +36,8 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { Event } from '../../../../base/common/event.js'; import { observableCodeEditor } from '../../../../editor/browser/observableCodeEditor.js'; import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js'; +import { createStyleSheet2 } from '../../../../base/browser/domStylesheets.js'; +import { stringValue } from '../../../../base/browser/cssValue.js'; export const CTX_INLINE_CHAT_SHOWING_HINT = new RawContextKey('inlineChatShowingHint', false, localize('inlineChatShowingHint', "Whether inline chat shows a contextual hint")); @@ -149,12 +151,6 @@ export class ShowInlineChatHintAction extends EditorAction2 { } } -class HintData { - constructor( - readonly setting: string - ) { } -} - export class InlineChatHintsController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.inlineChatHints'; @@ -193,8 +189,7 @@ export class InlineChatHintsController extends Disposable implements IEditorCont if (e.target.type !== MouseTargetType.CONTENT_TEXT) { return; } - const attachedData = e.target.detail.injectedText?.options.attachedData; - if (!(attachedData instanceof HintData)) { + if (!e.target.element?.classList.contains('inline-chat-hint-text')) { return; } if (e.event.leftButton) { @@ -202,7 +197,10 @@ export class InlineChatHintsController extends Disposable implements IEditorCont this.hide(); } else if (e.event.rightButton) { e.event.preventDefault(); - this._showContextMenu(e.event, attachedData.setting); + this._showContextMenu(e.event, e.target.element?.classList.contains('whitespace') + ? InlineChatConfigKeys.LineEmptyHint + : InlineChatConfigKeys.LineNLHint + ); } })); @@ -251,6 +249,9 @@ export class InlineChatHintsController extends Disposable implements IEditorCont return undefined; }); + const style = createStyleSheet2(); + this._store.add(style); + this._store.add(autorun(r => { const showData = showDataObs.read(r); @@ -264,7 +265,7 @@ export class InlineChatHintsController extends Disposable implements IEditorCont const agentName = chatAgentService.getDefaultAgent(ChatAgentLocation.Editor)?.name ?? localize('defaultTitle', "Chat"); const { position, isEol, isWhitespace, kb, model } = showData; - const inlineClassName: string[] = ['inline-chat-hint']; + const inlineClassName: string[] = ['a' /*HACK but sorts as we want*/, 'inline-chat-hint', 'inline-chat-hint-text']; let content: string; if (isWhitespace) { content = '\u00a0' + localize('title2', "{0} to edit with {1}", kb, agentName); @@ -275,6 +276,11 @@ export class InlineChatHintsController extends Disposable implements IEditorCont inlineClassName.push('embedded'); } + style.setStyle(`.inline-chat-hint-text::after { content: ${stringValue(content)} }`); + if (isWhitespace) { + inlineClassName.push('whitespace'); + } + this._ctxShowingHint.set(true); decos.set([{ @@ -283,13 +289,7 @@ export class InlineChatHintsController extends Disposable implements IEditorCont description: 'inline-chat-hint-line', showIfCollapsed: true, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - after: { - content, - inlineClassName: inlineClassName.join(' '), - inlineClassNameAffectsLetterSpacing: true, - cursorStops: InjectedTextCursorStops.None, - attachedData: new HintData(isWhitespace ? InlineChatConfigKeys.LineEmptyHint : InlineChatConfigKeys.LineNLHint) - } + afterContentClassName: inlineClassName.join(' '), } }]); From f7069b2feada75d95faf3806e35e507cfa8b6fed Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 6 Dec 2024 16:41:46 +0100 Subject: [PATCH 32/37] Use `disregardIgnoreFiles` in file search queries (#235501) Use `disregardIgnoreFiles` from file search --- .../workbench/contrib/files/browser/views/explorerViewer.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 9f15f33a26bdc..4de8cc1c24455 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -604,7 +604,10 @@ export class ExplorerFindProvider implements IAsyncFindProvider { const searchExcludePattern = getExcludes(this.configurationService.getValue({ resource: root.resource })) || {}; const searchOptions: IFileQuery = { - folderQueries: [{ folder: root.resource }], + folderQueries: [{ + folder: root.resource, + disregardIgnoreFiles: !this.configurationService.getValue('explorer.excludeGitIgnore'), + }], type: QueryType.File, shouldGlobMatchFilePattern: true, cacheKey: `explorerfindprovider:${root.name}:${rootIndex}:${this.sessionId}`, From 8270a86019db7551da42b71d15c6080a414d8c81 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 6 Dec 2024 16:55:37 +0100 Subject: [PATCH 33/37] Update grammars (#235506) --- extensions/dart/cgmanifest.json | 2 +- extensions/dart/syntaxes/dart.tmLanguage.json | 47 +++--- extensions/docker/cgmanifest.json | 4 +- .../docker/syntaxes/docker.tmLanguage.json | 19 ++- extensions/go/cgmanifest.json | 4 +- extensions/go/syntaxes/go.tmLanguage.json | 137 +++++++++++++++++- extensions/latex/cgmanifest.json | 2 +- extensions/perl/cgmanifest.json | 2 +- 8 files changed, 178 insertions(+), 39 deletions(-) diff --git a/extensions/dart/cgmanifest.json b/extensions/dart/cgmanifest.json index da493cafa700c..5558b78af1d9b 100644 --- a/extensions/dart/cgmanifest.json +++ b/extensions/dart/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dart-lang/dart-syntax-highlight", "repositoryUrl": "https://github.com/dart-lang/dart-syntax-highlight", - "commitHash": "e8b053f9834cb44db0f49ac4a4567177bd943dbf" + "commitHash": "e1ac5c446c2531343393adbe8fff9d45d8a7c412" } }, "licenseDetail": [ diff --git a/extensions/dart/syntaxes/dart.tmLanguage.json b/extensions/dart/syntaxes/dart.tmLanguage.json index 32ea3f5b0c340..b4f80b680bd8f 100644 --- a/extensions/dart/syntaxes/dart.tmLanguage.json +++ b/extensions/dart/syntaxes/dart.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dart-lang/dart-syntax-highlight/commit/e8b053f9834cb44db0f49ac4a4567177bd943dbf", + "version": "https://github.com/dart-lang/dart-syntax-highlight/commit/e1ac5c446c2531343393adbe8fff9d45d8a7c412", "name": "Dart", "scopeName": "source.dart", "patterns": [ @@ -66,6 +66,16 @@ } ], "repository": { + "dartdoc-codeblock-triple": { + "begin": "^\\s*///\\s*(?!\\s*```)", + "end": "\n", + "contentName": "variable.other.source.dart" + }, + "dartdoc-codeblock-block": { + "begin": "^\\s*\\*\\s*(?!(\\s*```|/))", + "end": "\n", + "contentName": "variable.other.source.dart" + }, "dartdoc": { "patterns": [ { @@ -77,30 +87,31 @@ } }, { - "match": "^ {4,}(?![ \\*]).*", - "captures": { - "0": { - "name": "variable.name.source.dart" + "begin": "^\\s*///\\s*(```)", + "end": "^\\s*///\\s*(```)|^(?!\\s*///)", + "patterns": [ + { + "include": "#dartdoc-codeblock-triple" } - } + ] }, { - "contentName": "variable.other.source.dart", - "begin": "```.*?$", - "end": "```" + "begin": "^\\s*\\*\\s*(```)", + "end": "^\\s*\\*\\s*(```)|^(?=\\s*\\*/)", + "patterns": [ + { + "include": "#dartdoc-codeblock-block" + } + ] }, { - "match": "(`[^`]+?`)", - "captures": { - "0": { - "name": "variable.other.source.dart" - } - } + "match": "`[^`\n]+`", + "name": "variable.other.source.dart" }, { - "match": "(\\* (( ).*))$", + "match": "(?:\\*|\\/\\/)\\s{4,}(.*?)(?=($|\\*\\/))", "captures": { - "2": { + "1": { "name": "variable.other.source.dart" } } @@ -154,7 +165,7 @@ { "name": "comment.block.documentation.dart", "begin": "///", - "while": "^\\s*///", + "end": "^(?!\\s*///)", "patterns": [ { "include": "#dartdoc" diff --git a/extensions/docker/cgmanifest.json b/extensions/docker/cgmanifest.json index 4f568542aed88..8462de7dd7283 100644 --- a/extensions/docker/cgmanifest.json +++ b/extensions/docker/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "language-docker", "repositoryUrl": "https://github.com/moby/moby", - "commitHash": "abd39744c6f3ed854500e423f5fabf952165161f" + "commitHash": "c2029cb2574647e4bc28ed58486b8e85883eedb9" } }, "license": "Apache-2.0", @@ -15,4 +15,4 @@ } ], "version": 1 -} +} \ No newline at end of file diff --git a/extensions/docker/syntaxes/docker.tmLanguage.json b/extensions/docker/syntaxes/docker.tmLanguage.json index f7f414636c496..aa5223a31eae9 100644 --- a/extensions/docker/syntaxes/docker.tmLanguage.json +++ b/extensions/docker/syntaxes/docker.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/moby/moby/commit/abd39744c6f3ed854500e423f5fabf952165161f", + "version": "https://github.com/moby/moby/commit/c2029cb2574647e4bc28ed58486b8e85883eedb9", "name": "Dockerfile", "scopeName": "source.dockerfile", "patterns": [ @@ -41,6 +41,9 @@ }, "match": "^\\s*(?i:(ONBUILD)\\s+)?(?i:(CMD|ENTRYPOINT))\\s" }, + { + "include": "#string-character-escape" + }, { "begin": "\"", "beginCaptures": { @@ -57,8 +60,7 @@ "name": "string.quoted.double.dockerfile", "patterns": [ { - "match": "\\\\.", - "name": "constant.character.escaped.dockerfile" + "include": "#string-character-escape" } ] }, @@ -78,8 +80,7 @@ "name": "string.quoted.single.dockerfile", "patterns": [ { - "match": "\\\\.", - "name": "constant.character.escaped.dockerfile" + "include": "#string-character-escape" } ] }, @@ -98,5 +99,11 @@ "comment": "comment.line", "match": "^(\\s*)((#).*$\\n?)" } - ] + ], + "repository": { + "string-character-escape": { + "name": "constant.character.escaped.dockerfile", + "match": "\\\\." + } + } } \ No newline at end of file diff --git a/extensions/go/cgmanifest.json b/extensions/go/cgmanifest.json index 5276d2824a718..a6dbd5d1bf015 100644 --- a/extensions/go/cgmanifest.json +++ b/extensions/go/cgmanifest.json @@ -6,12 +6,12 @@ "git": { "name": "go-syntax", "repositoryUrl": "https://github.com/worlpaker/go-syntax", - "commitHash": "32bbaebcf218fa552e8f0397401e12f6e94fa3c5" + "commitHash": "fbdaec061157e98dda185c0ce771ce6a2c793045" } }, "license": "MIT", "description": "The file syntaxes/go.tmLanguage.json is from https://github.com/worlpaker/go-syntax, which in turn was derived from https://github.com/jeff-hykin/better-go-syntax.", - "version": "0.7.8" + "version": "0.7.9" } ], "version": 1 diff --git a/extensions/go/syntaxes/go.tmLanguage.json b/extensions/go/syntaxes/go.tmLanguage.json index ed6ead03480da..db17cad3f9119 100644 --- a/extensions/go/syntaxes/go.tmLanguage.json +++ b/extensions/go/syntaxes/go.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/worlpaker/go-syntax/commit/32bbaebcf218fa552e8f0397401e12f6e94fa3c5", + "version": "https://github.com/worlpaker/go-syntax/commit/fbdaec061157e98dda185c0ce771ce6a2c793045", "name": "Go", "scopeName": "source.go", "patterns": [ @@ -97,7 +97,10 @@ "comment": "all statements related to variables", "patterns": [ { - "include": "#var_const_assignment" + "include": "#const_assignment" + }, + { + "include": "#var_assignment" }, { "include": "#variable_assignment" @@ -2645,12 +2648,12 @@ } ] }, - "var_const_assignment": { - "comment": "variable assignment with var and const keyword", + "var_assignment": { + "comment": "variable assignment with var keyword", "patterns": [ { - "comment": "var and const with single type assignment", - "match": "(?:(?<=\\bvar\\b|\\bconst\\b)(?:\\s*)(\\b[\\w\\.]+(?:\\,\\s*[\\w\\.]+)*)(?:\\s*)((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?!(?:[\\[\\]\\*]+)?\\b(?:struct|func|map)\\b)(?:[\\w\\.\\[\\]\\*]+(?:\\,\\s*[\\w\\.\\[\\]\\*]+)*)?(?:\\s*)(?:\\=)?)?)", + "comment": "single assignment", + "match": "(?:(?<=\\bvar\\b)(?:\\s*)(\\b[\\w\\.]+(?:\\,\\s*[\\w\\.]+)*)(?:\\s*)((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?!(?:[\\[\\]\\*]+)?\\b(?:struct|func|map)\\b)(?:[\\w\\.\\[\\]\\*]+(?:\\,\\s*[\\w\\.\\[\\]\\*]+)*)?(?:\\s*)(?:\\=)?)?)", "captures": { "1": { "patterns": [ @@ -2696,8 +2699,8 @@ } }, { - "comment": "var and const with multi type assignment", - "begin": "(?:(?<=\\bvar\\b|\\bconst\\b)(?:\\s*)(\\())", + "comment": "multi assignment", + "begin": "(?:(?<=\\bvar\\b)(?:\\s*)(\\())", "beginCaptures": { "1": { "name": "punctuation.definition.begin.bracket.round.go" @@ -2763,6 +2766,124 @@ } ] }, + "const_assignment": { + "comment": "constant assignment with const keyword", + "patterns": [ + { + "comment": "single assignment", + "match": "(?:(?<=\\bconst\\b)(?:\\s*)(\\b[\\w\\.]+(?:\\,\\s*[\\w\\.]+)*)(?:\\s*)((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?!(?:[\\[\\]\\*]+)?\\b(?:struct|func|map)\\b)(?:[\\w\\.\\[\\]\\*]+(?:\\,\\s*[\\w\\.\\[\\]\\*]+)*)?(?:\\s*)(?:\\=)?)?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#delimiters" + }, + { + "match": "\\w+", + "name": "variable.other.constant.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "include": "#generic_types" + }, + { + "match": "\\(", + "name": "punctuation.definition.begin.bracket.round.go" + }, + { + "match": "\\)", + "name": "punctuation.definition.end.bracket.round.go" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + } + } + }, + { + "comment": "multi assignment", + "begin": "(?:(?<=\\bconst\\b)(?:\\s*)(\\())", + "beginCaptures": { + "1": { + "name": "punctuation.definition.begin.bracket.round.go" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.go" + } + }, + "patterns": [ + { + "match": "(?:(?:^\\s*)(\\b[\\w\\.]+(?:\\,\\s*[\\w\\.]+)*)(?:\\s*)((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?!(?:[\\[\\]\\*]+)?\\b(?:struct|func|map)\\b)(?:[\\w\\.\\[\\]\\*]+(?:\\,\\s*[\\w\\.\\[\\]\\*]+)*)?(?:\\s*)(?:\\=)?)?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#delimiters" + }, + { + "match": "\\w+", + "name": "variable.other.constant.go" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type-declarations-without-brackets" + }, + { + "include": "#generic_types" + }, + { + "match": "\\(", + "name": "punctuation.definition.begin.bracket.round.go" + }, + { + "match": "\\)", + "name": "punctuation.definition.end.bracket.round.go" + }, + { + "match": "\\[", + "name": "punctuation.definition.begin.bracket.square.go" + }, + { + "match": "\\]", + "name": "punctuation.definition.end.bracket.square.go" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + } + } + }, + { + "include": "$self" + } + ] + } + ] + }, "variable_assignment": { "comment": "variable assignment", "patterns": [ diff --git a/extensions/latex/cgmanifest.json b/extensions/latex/cgmanifest.json index d937ba4f4304f..25b52bf3787e7 100644 --- a/extensions/latex/cgmanifest.json +++ b/extensions/latex/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jlelong/vscode-latex-basics", "repositoryUrl": "https://github.com/jlelong/vscode-latex-basics", - "commitHash": "df6ef817c932d24da5cc72927344a547e463cc65" + "commitHash": "59971565a7065dbb617576c04add9d891b056319" } }, "license": "MIT", diff --git a/extensions/perl/cgmanifest.json b/extensions/perl/cgmanifest.json index cd175abe37de7..b7c850dd1aab8 100644 --- a/extensions/perl/cgmanifest.json +++ b/extensions/perl/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "textmate/perl.tmbundle", "repositoryUrl": "https://github.com/textmate/perl.tmbundle", - "commitHash": "a85927a902d6e5d7805f56a653f324d34dfad53a" + "commitHash": "d9841a0878239fa43f88c640f8d458590f97e8f5" } }, "licenseDetail": [ From d6faa5b076c07a4a7ef25f74e21501bc145a06a4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 6 Dec 2024 17:30:17 +0100 Subject: [PATCH 34/37] debt - use `observableConfigValue` (#235508) --- .../inlineChat/browser/inlineChatCurrentLine.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index 8cf0f0891a67f..43e73454efd11 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -33,11 +33,11 @@ import { IContextMenuService } from '../../../../platform/contextview/browser/co import { toAction } from '../../../../base/common/actions.js'; import { IMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { Event } from '../../../../base/common/event.js'; import { observableCodeEditor } from '../../../../editor/browser/observableCodeEditor.js'; import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js'; import { createStyleSheet2 } from '../../../../base/browser/domStylesheets.js'; import { stringValue } from '../../../../base/browser/cssValue.js'; +import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; export const CTX_INLINE_CHAT_SHOWING_HINT = new RawContextKey('inlineChatShowingHint', false, localize('inlineChatShowingHint', "Whether inline chat shows a contextual hint")); @@ -208,10 +208,9 @@ export class InlineChatHintsController extends Disposable implements IEditorCont const decos = this._editor.createDecorationsCollection(); const editorObs = observableCodeEditor(editor); - const keyObs = observableFromEvent(keybindingService.onDidUpdateKeybindings, _ => keybindingService.lookupKeybinding(ACTION_START)?.getLabel()); - - const configSignal = observableFromEvent(Event.filter(_configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(InlineChatConfigKeys.LineEmptyHint) || e.affectsConfiguration(InlineChatConfigKeys.LineNLHint)), () => undefined); + const configHintEmpty = observableConfigValue(InlineChatConfigKeys.LineEmptyHint, false, this._configurationService); + const configHintNL = observableConfigValue(InlineChatConfigKeys.LineNLHint, false, this._configurationService); const showDataObs = derived(r => { const ghostState = ghostCtrl?.model.read(r)?.state.read(r); @@ -222,8 +221,6 @@ export class InlineChatHintsController extends Disposable implements IEditorCont const kb = keyObs.read(r); - configSignal.read(r); - if (ghostState !== undefined || !kb || !position || !model || !textFocus) { return undefined; } @@ -237,12 +234,12 @@ export class InlineChatHintsController extends Disposable implements IEditorCont const isWhitespace = model.getLineLastNonWhitespaceColumn(position.lineNumber) === 0 && model.getValueLength() > 0 && position.column > 1; if (isWhitespace) { - return _configurationService.getValue(InlineChatConfigKeys.LineEmptyHint) + return configHintEmpty.read(r) ? { isEol, isWhitespace, kb, position, model } : undefined; } - if (visible && isEol && _configurationService.getValue(InlineChatConfigKeys.LineNLHint)) { + if (visible && isEol && configHintNL.read(r)) { return { isEol, isWhitespace, kb, position, model }; } From 0f695e4d81b4f89cb8e3833062994fe30c7160f8 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 6 Dec 2024 17:37:47 +0100 Subject: [PATCH 35/37] chore - remove `IChatEditingService#onDidCreateEditingSession` in favor of `currentEditingSessionObs` --- .../chat/browser/actions/chatTitleActions.ts | 6 ++---- .../browser/chatEditing/chatEditingService.ts | 6 ------ .../contrib/chat/browser/chatWidget.ts | 7 ++++++- .../contrib/chatInputRelatedFilesContrib.ts | 18 +++++++++--------- .../contrib/chat/common/chatEditingService.ts | 1 - .../test/browser/inlineChatController.test.ts | 3 ++- 6 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 7d395f051d875..10dd97aff86ac 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Codicon } from '../../../../../base/common/codicons.js'; -import { Event } from '../../../../../base/common/event.js'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../../base/common/map.js'; import { marked } from '../../../../../base/common/marked/marked.js'; +import { waitForState } from '../../../../../base/common/observable.js'; import { basename } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; @@ -465,9 +465,7 @@ export function registerChatTitleActions() { let editingSession = chatEditingService.currentEditingSessionObs.get(); if (!editingSession) { - await Event.toPromise(chatEditingService.onDidCreateEditingSession); - editingSession = chatEditingService.currentEditingSessionObs.get(); - return; + editingSession = await waitForState(chatEditingService.currentEditingSessionObs); } if (!editingSession) { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts index f1204bd40ed3d..34b24c1dbb8e4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts @@ -61,11 +61,6 @@ export class ChatEditingService extends Disposable implements IChatEditingServic return this._currentSessionObs; } - private readonly _onDidCreateEditingSession = this._register(new Emitter()); - get onDidCreateEditingSession() { - return this._onDidCreateEditingSession.event; - } - private readonly _onDidChangeEditingSession = this._register(new Emitter()); public readonly onDidChangeEditingSession = this._onDidChangeEditingSession.event; @@ -220,7 +215,6 @@ export class ChatEditingService extends Disposable implements IChatEditingServic })); this._currentSessionObs.set(session, undefined); - this._onDidCreateEditingSession.fire(session); this._onDidChangeEditingSession.fire(); return session; } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index a8831878ddb8a..800e82d952eb1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -15,6 +15,7 @@ import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, combinedDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../base/common/map.js'; import { Schemas } from '../../../../base/common/network.js'; +import { autorun } from '../../../../base/common/observable.js'; import { extUri, isEqual } from '../../../../base/common/resources.js'; import { isDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; @@ -250,7 +251,11 @@ export class ChatWidget extends Disposable implements IChatWidget { const chatEditingSessionDisposables = this._register(new DisposableStore()); - this._register(this.chatEditingService.onDidCreateEditingSession((session) => { + this._register(autorun(r => { + const session = this.chatEditingService.currentEditingSessionObs.read(r); + if (!session) { + return; + } if (session.chatSessionId !== this.viewModel?.sessionId) { // this chat editing session is for a different chat widget return; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts index ff1938c5b7b04..3f4045b9ce099 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts @@ -7,10 +7,11 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Event } from '../../../../../base/common/event.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../../base/common/map.js'; +import { autorun } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; -import { ChatEditingSessionChangeType, IChatEditingService, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { ChatEditingSessionChangeType, IChatEditingService, IChatEditingSession, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IChatWidgetService } from '../chat.js'; export class ChatRelatedFilesContribution extends Disposable implements IWorkbenchContribution { @@ -25,10 +26,12 @@ export class ChatRelatedFilesContribution extends Disposable implements IWorkben ) { super(); - this._handleNewEditingSession(); - this._register(this.chatEditingService.onDidCreateEditingSession(() => { + this._register(autorun(r => { this.chatEditingSessionDisposables.clear(); - this._handleNewEditingSession(); + const session = this.chatEditingService.currentEditingSessionObs.read(r); + if (session) { + this._handleNewEditingSession(session); + } })); } @@ -95,11 +98,8 @@ export class ChatRelatedFilesContribution extends Disposable implements IWorkben } - private _handleNewEditingSession() { - const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); - if (!currentEditingSession) { - return; - } + private _handleNewEditingSession(currentEditingSession: IChatEditingSession) { + const widget = this.chatWidgetService.getWidgetBySessionId(currentEditingSession.chatSessionId); if (!widget || widget.viewModel?.sessionId !== currentEditingSession.chatSessionId) { return; diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index e102d7525e95a..c0598e39ebabd 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -23,7 +23,6 @@ export interface IChatEditingService { _serviceBrand: undefined; - readonly onDidCreateEditingSession: Event; /** * emitted when a session is created, changed or disposed */ diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index feaba4ca832c7..7d07547770ee6 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -70,6 +70,7 @@ import { IChatEditingService, IChatEditingSession } from '../../../chat/common/c import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { TextModelResolverService } from '../../../../services/textmodelResolver/common/textModelResolverService.js'; import { ChatInputBoxContentProvider } from '../../../chat/browser/chatEdinputInputContentProvider.js'; +import { IObservable, observableValue } from '../../../../../base/common/observable.js'; suite('InlineChatController', function () { @@ -162,7 +163,7 @@ suite('InlineChatController', function () { [IInlineChatSessionService, new SyncDescriptor(InlineChatSessionServiceImpl)], [ICommandService, new SyncDescriptor(TestCommandService)], [IChatEditingService, new class extends mock() { - override onDidCreateEditingSession: Event = Event.None; + override currentEditingSessionObs: IObservable = observableValue(this, null); }], [IInlineChatSavingService, new class extends mock() { override markChanged(session: Session): void { From d89fdcbfa8d0d9242b3fdb3528d792370380128a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 6 Dec 2024 18:43:21 +0100 Subject: [PATCH 36/37] Removing bottom padding on editor hover (#235507) * almost there * fixing padding, but now does not render correctly * adding changes * removing line * renaming class * taking into account borders --- .../hover/browser/contentHoverWidget.ts | 29 +++++++++++-------- src/vs/editor/contrib/hover/browser/hover.css | 10 +++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts b/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts index ad27c1544d58a..ffb8aaa9f115d 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts @@ -19,7 +19,6 @@ import { Emitter } from '../../../../base/common/event.js'; import { RenderedContentHover } from './contentHoverRendered.js'; const HORIZONTAL_SCROLLING_BY = 30; -const CONTAINER_HEIGHT_PADDING = 6; export class ContentHoverWidget extends ResizableContentWidget { @@ -68,6 +67,7 @@ export class ContentHoverWidget extends ResizableContentWidget { dom.append(this._resizableNode.domNode, this._hover.containerDomNode); this._resizableNode.domNode.style.zIndex = '50'; + this._resizableNode.domNode.className = 'monaco-resizable-hover'; this._register(this._editor.onDidLayoutChange(() => { if (this.isVisible) { @@ -117,9 +117,15 @@ export class ContentHoverWidget extends ResizableContentWidget { return ContentHoverWidget._applyDimensions(containerDomNode, width, height); } + private _setScrollableElementDimensions(width: number | string, height: number | string): void { + const scrollbarDomElement = this._hover.scrollbar.getDomNode(); + return ContentHoverWidget._applyDimensions(scrollbarDomElement, width, height); + } + private _setHoverWidgetDimensions(width: number | string, height: number | string): void { - this._setContentsDomNodeDimensions(width, height); this._setContainerDomNodeDimensions(width, height); + this._setScrollableElementDimensions(width, height); + this._setContentsDomNodeDimensions(width, height); this._layoutContentWidget(); } @@ -176,12 +182,11 @@ export class ContentHoverWidget extends ResizableContentWidget { if (!availableSpace) { return; } - // Padding needed in order to stop the resizing down to a smaller height - let maximumHeight = CONTAINER_HEIGHT_PADDING; + const children = this._hover.contentsDomNode.children; + let maximumHeight = children.length - 1; Array.from(this._hover.contentsDomNode.children).forEach((hoverPart) => { maximumHeight += hoverPart.clientHeight; }); - return Math.min(availableSpace, maximumHeight); } @@ -209,7 +214,7 @@ export class ContentHoverWidget extends ResizableContentWidget { const initialWidth = ( typeof this._contentWidth === 'undefined' ? 0 - : this._contentWidth - 2 // - 2 for the borders + : this._contentWidth ); if (overflowing || this._hover.containerDomNode.clientWidth < initialWidth) { @@ -217,7 +222,7 @@ export class ContentHoverWidget extends ResizableContentWidget { const horizontalPadding = 14; return bodyBoxWidth - horizontalPadding; } else { - return this._hover.containerDomNode.clientWidth + 2; + return this._hover.containerDomNode.clientWidth; } } @@ -389,16 +394,16 @@ export class ContentHoverWidget extends ResizableContentWidget { public onContentsChanged(): void { this._removeConstraintsRenderNormally(); - const containerDomNode = this._hover.containerDomNode; + const contentsDomNode = this._hover.contentsDomNode; - let height = dom.getTotalHeight(containerDomNode); - let width = dom.getTotalWidth(containerDomNode); + let height = dom.getTotalHeight(contentsDomNode); + let width = dom.getTotalWidth(contentsDomNode) + 2; this._resizableNode.layout(height, width); this._setHoverWidgetDimensions(width, height); - height = dom.getTotalHeight(containerDomNode); - width = dom.getTotalWidth(containerDomNode); + height = dom.getTotalHeight(contentsDomNode); + width = dom.getTotalWidth(contentsDomNode); this._contentWidth = width; this._updateMinimumWidth(); this._resizableNode.layout(height, width); diff --git a/src/vs/editor/contrib/hover/browser/hover.css b/src/vs/editor/contrib/hover/browser/hover.css index 958f1ee9cf426..b9da587c91840 100644 --- a/src/vs/editor/contrib/hover/browser/hover.css +++ b/src/vs/editor/contrib/hover/browser/hover.css @@ -7,17 +7,15 @@ background-color: var(--vscode-editor-hoverHighlightBackground); } -.monaco-editor .monaco-hover-content { - padding-right: 2px; - padding-bottom: 2px; - box-sizing: border-box; +.monaco-editor .monaco-resizable-hover { + border: 1px solid var(--vscode-editorHoverWidget-border); + border-radius: 3px; + box-sizing: content-box; } .monaco-editor .monaco-hover { color: var(--vscode-editorHoverWidget-foreground); background-color: var(--vscode-editorHoverWidget-background); - border: 1px solid var(--vscode-editorHoverWidget-border); - border-radius: 3px; } .monaco-editor .monaco-hover a { From 17f6bcb7d2d89a03c53319f93b1e677053612d96 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 6 Dec 2024 18:53:29 +0100 Subject: [PATCH 37/37] chore - modernise test runner (#235511) async/wait, remove AMD/ESM split, cleanup --- test/unit/electron/renderer.js | 222 ++++++++++++++++----------------- 1 file changed, 107 insertions(+), 115 deletions(-) diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 7c28a98930cf7..b93d91a78e943 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -5,6 +5,8 @@ /*eslint-env mocha*/ +// @ts-check + const fs = require('fs'); (function () { @@ -24,7 +26,7 @@ const fs = require('fs'); function createSpy(element, cnt) { return function (...args) { if (logging) { - console.log(`calling ${element}: ` + args.slice(0, cnt).join(',') + (withStacks ? (`\n` + new Error().stack.split('\n').slice(2).join('\n')) : '')); + console.log(`calling ${element}: ` + args.slice(0, cnt).join(',') + (withStacks ? (`\n` + new Error().stack?.split('\n').slice(2).join('\n')) : '')); } return originals[element].call(this, ...args); }; @@ -88,9 +90,18 @@ Object.assign(globalThis, { const IS_CI = !!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY; const _tests_glob = '**/test/**/*.test.js'; -let loader; + + +/** + * Loads one or N modules. + * @type {{ + * (module: string|string[]): Promise|Promise; + * _out: string; + * }} + */ +let loadFn; + const _loaderErrors = []; -let _out; function initNls(opts) { if (opts.build) { @@ -101,20 +112,17 @@ function initNls(opts) { } } -function initLoader(opts) { +function initLoadFn(opts) { const outdir = opts.build ? 'out-build' : 'out'; - _out = path.join(__dirname, `../../../${outdir}`); + const out = path.join(__dirname, `../../../${outdir}`); const baseUrl = pathToFileURL(path.join(__dirname, `../../../${outdir}/`)); globalThis._VSCODE_FILE_ROOT = baseUrl.href; // set loader - /** - * @param {string[]} modules - * @param {(...args:any[]) => void} callback - */ - function esmRequire(modules, callback, errorback) { - const tasks = modules.map(mod => { + function importModules(modules) { + const moduleArray = Array.isArray(modules) ? modules : [modules]; + const tasks = moduleArray.map(mod => { const url = new URL(`./${mod}.js`, baseUrl).href; return import(url).catch(err => { console.log(mod, url); @@ -124,35 +132,33 @@ function initLoader(opts) { }); }); - Promise.all(tasks).then(modules => callback(...modules)).catch(errorback); + return Array.isArray(modules) + ? Promise.all(tasks) + : tasks[0]; } - - loader = { require: esmRequire }; + importModules._out = out; + loadFn = importModules; } -function createCoverageReport(opts) { - if (opts.coverage) { - return coverage.createReport(opts.run || opts.runGlob); +async function createCoverageReport(opts) { + if (!opts.coverage) { + return undefined; } - return Promise.resolve(undefined); -} - -function loadWorkbenchTestingUtilsModule() { - return new Promise((resolve, reject) => { - loader.require(['vs/workbench/test/common/utils'], resolve, reject); - }); + return coverage.createReport(opts.run || opts.runGlob); } async function loadModules(modules) { for (const file of modules) { mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE, globalThis, file, mocha); - const m = await new Promise((resolve, reject) => loader.require([file], resolve, reject)); + const m = await loadFn(file); mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_REQUIRE, m, file, mocha); mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_POST_REQUIRE, globalThis, file, mocha); } } -function loadTestModules(opts) { +const globAsync = util.promisify(glob); + +async function loadTestModules(opts) { if (opts.run) { const files = Array.isArray(opts.run) ? opts.run : [opts.run]; @@ -164,17 +170,9 @@ function loadTestModules(opts) { } const pattern = opts.runGlob || _tests_glob; - - return new Promise((resolve, reject) => { - glob(pattern, { cwd: _out }, (err, files) => { - if (err) { - reject(err); - return; - } - const modules = files.map(file => file.replace(/\.js$/, '')); - resolve(modules); - }); - }).then(loadModules); + const files = await globAsync(pattern, { cwd: loadFn._out }); + const modules = files.map(file => file.replace(/\.js$/, '')); + return loadModules(modules); } /** @type Mocha.Test */ @@ -220,7 +218,7 @@ async function loadTests(opts) { console[consoleFn.name] = function (msg) { if (!currentTest) { consoleFn.apply(console, arguments); - } else if (!_allowedTestOutput.some(a => a.test(msg)) && !_allowedTestsWithOutput.has(currentTest.title) && !_allowedSuitesWithOutput.has(currentTest.parent?.title)) { + } else if (!_allowedTestOutput.some(a => a.test(msg)) && !_allowedTestsWithOutput.has(currentTest.title) && !_allowedSuitesWithOutput.has(currentTest.parent?.title ?? '')) { _testsWithUnexpectedOutput = true; consoleFn.apply(console, arguments); } @@ -242,79 +240,74 @@ async function loadTests(opts) { 'Search Model: Search reports timed telemetry on search when error is called' ]); - loader.require(['vs/base/common/errors'], function (errors) { - - const onUnexpectedError = function (err) { - if (err.name === 'Canceled') { - return; // ignore canceled errors that are common - } - - let stack = (err ? err.stack : null); - if (!stack) { - stack = new Error().stack; - } + const errors = await loadFn('vs/base/common/errors'); + const onUnexpectedError = function (err) { + if (err.name === 'Canceled') { + return; // ignore canceled errors that are common + } - _unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack); - }; + let stack = (err ? err.stack : null); + if (!stack) { + stack = new Error().stack; + } - process.on('uncaughtException', error => onUnexpectedError(error)); - process.on('unhandledRejection', (reason, promise) => { - onUnexpectedError(reason); - promise.catch(() => { }); - }); - window.addEventListener('unhandledrejection', event => { - event.preventDefault(); // Do not log to test output, we show an error later when test ends - event.stopPropagation(); + _unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack); + }; - if (!_allowedTestsWithUnhandledRejections.has(currentTest.title)) { - onUnexpectedError(event.reason); - } - }); + process.on('uncaughtException', error => onUnexpectedError(error)); + process.on('unhandledRejection', (reason, promise) => { + onUnexpectedError(reason); + promise.catch(() => { }); + }); + window.addEventListener('unhandledrejection', event => { + event.preventDefault(); // Do not log to test output, we show an error later when test ends + event.stopPropagation(); - errors.setUnexpectedErrorHandler(onUnexpectedError); + if (!_allowedTestsWithUnhandledRejections.has(currentTest.title)) { + onUnexpectedError(event.reason); + } }); + errors.setUnexpectedErrorHandler(onUnexpectedError); //#endregion - return loadWorkbenchTestingUtilsModule().then((workbenchTestingModule) => { - const assertCleanState = workbenchTestingModule.assertCleanState; + const { assertCleanState } = await loadFn('vs/workbench/test/common/utils'); - suite('Tests are using suiteSetup and setup correctly', () => { - test('assertCleanState - check that registries are clean at the start of test running', () => { - assertCleanState(); - }); + suite('Tests are using suiteSetup and setup correctly', () => { + test('assertCleanState - check that registries are clean at the start of test running', () => { + assertCleanState(); }); + }); - setup(async () => { - await perTestCoverage?.startTest(); - }); + setup(async () => { + await perTestCoverage?.startTest(); + }); - teardown(async () => { - await perTestCoverage?.finishTest(currentTest.file, currentTest.fullTitle()); + teardown(async () => { + await perTestCoverage?.finishTest(currentTest.file, currentTest.fullTitle()); - // should not have unexpected output - if (_testsWithUnexpectedOutput && !opts.dev) { - assert.ok(false, 'Error: Unexpected console output in test run. Please ensure no console.[log|error|info|warn] usage in tests or runtime errors.'); - } + // should not have unexpected output + if (_testsWithUnexpectedOutput && !opts.dev) { + assert.ok(false, 'Error: Unexpected console output in test run. Please ensure no console.[log|error|info|warn] usage in tests or runtime errors.'); + } - // should not have unexpected errors - const errors = _unexpectedErrors.concat(_loaderErrors); - if (errors.length) { - for (const error of errors) { - console.error(`Error: Test run should not have unexpected errors:\n${error}`); - } - assert.ok(false, 'Error: Test run should not have unexpected errors.'); + // should not have unexpected errors + const errors = _unexpectedErrors.concat(_loaderErrors); + if (errors.length) { + for (const error of errors) { + console.error(`Error: Test run should not have unexpected errors:\n${error}`); } - }); - - suiteTeardown(() => { // intentionally not in teardown because some tests only cleanup in suiteTeardown + assert.ok(false, 'Error: Test run should not have unexpected errors.'); + } + }); - // should have cleaned up in registries - assertCleanState(); - }); + suiteTeardown(() => { // intentionally not in teardown because some tests only cleanup in suiteTeardown - return loadTestModules(opts); + // should have cleaned up in registries + assertCleanState(); }); + + return loadTestModules(opts); } function serializeSuite(suite) { @@ -403,42 +396,41 @@ class IPCReporter { } } -function runTests(opts) { +async function runTests(opts) { // this *must* come before loadTests, or it doesn't work. if (opts.timeout !== undefined) { mocha.timeout(opts.timeout); } - return loadTests(opts).then(() => { + await loadTests(opts); - if (opts.grep) { - mocha.grep(opts.grep); - } + if (opts.grep) { + mocha.grep(opts.grep); + } - if (!opts.dev) { - mocha.reporter(IPCReporter); - } + if (!opts.dev) { + // @ts-expect-error + mocha.reporter(IPCReporter); + } - const runner = mocha.run(() => { - createCoverageReport(opts).then(() => { - ipcRenderer.send('all done'); - }); - }); + const runner = mocha.run(async () => { + await createCoverageReport(opts) + ipcRenderer.send('all done'); + }); - runner.on('test', test => currentTest = test); + runner.on('test', test => currentTest = test); - if (opts.dev) { - runner.on('fail', (test, err) => { - console.error(test.fullTitle()); - console.error(err.stack); - }); - } - }); + if (opts.dev) { + runner.on('fail', (test, err) => { + console.error(test.fullTitle()); + console.error(err.stack); + }); + } } ipcRenderer.on('run', async (_e, opts) => { initNls(opts); - initLoader(opts); + initLoadFn(opts); await Promise.resolve(globalThis._VSCODE_TEST_INIT);