From bd3bb202d2bab702c2ea74a9b7b7f0f9ab9b2c4d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:13:54 -0700 Subject: [PATCH 001/555] Mouse events wip --- src/vs/editor/browser/controller/mouseHandler.ts | 2 ++ src/vs/editor/browser/controller/mouseTarget.ts | 13 +++++++++++++ src/vs/editor/browser/view.ts | 7 ++++++- .../browser/viewParts/viewLinesGpu/viewLinesGpu.ts | 1 + 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index cd9b92c4ed038..b1e326d835ac6 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -21,11 +21,13 @@ import { ViewEventHandler } from '../../common/viewEventHandler.js'; import { EditorOption } from '../../common/config/editorOptions.js'; import { NavigationCommandRevealType } from '../coreCommands.js'; import { MouseWheelClassifier } from '../../../base/browser/ui/scrollbar/scrollableElement.js'; +import type { ViewLinesGpu } from '../viewParts/viewLinesGpu/viewLinesGpu.js'; export interface IPointerHandlerHelper { viewDomNode: HTMLElement; linesContentDomNode: HTMLElement; viewLinesDomNode: HTMLElement; + viewLinesGpu: ViewLinesGpu | undefined; focusTextArea(): void; dispatchTextAreaEvent(event: CustomEvent): void; diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 737daf6d2947f..d855e852affe3 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -22,6 +22,7 @@ import { PositionAffinity } from '../../common/model.js'; import { InjectedText } from '../../common/modelLineProjectionData.js'; import { Mutable } from '../../../base/common/types.js'; import { Lazy } from '../../../base/common/lazy.js'; +import type { ViewLinesGpu } from '../viewParts/viewLinesGpu/viewLinesGpu.js'; const enum HitTestResultType { Unknown, @@ -238,6 +239,7 @@ export class HitTestContext { public readonly viewModel: IViewModel; public readonly layoutInfo: EditorLayoutInfo; public readonly viewDomNode: HTMLElement; + public readonly viewLinesGpu: ViewLinesGpu | undefined; public readonly lineHeight: number; public readonly stickyTabStops: boolean; public readonly typicalHalfwidthCharacterWidth: number; @@ -251,6 +253,7 @@ export class HitTestContext { const options = context.configuration.options; this.layoutInfo = options.get(EditorOption.layoutInfo); this.viewDomNode = viewHelper.viewDomNode; + this.viewLinesGpu = viewHelper.viewLinesGpu; this.lineHeight = options.get(EditorOption.lineHeight); this.stickyTabStops = options.get(EditorOption.stickyTabStops); this.typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; @@ -574,6 +577,11 @@ export class MouseTargetFactory { result = result || request.fulfillUnknown(); } + // TODO: Need to know about canvas here + if (ctx.viewLinesGpu) { + // ... + } + result = result || MouseTargetFactory._hitTestContentWidget(ctx, resolvedRequest); result = result || MouseTargetFactory._hitTestOverlayWidget(ctx, resolvedRequest); result = result || MouseTargetFactory._hitTestMinimap(ctx, resolvedRequest); @@ -725,6 +733,11 @@ export class MouseTargetFactory { return null; } + // TODO: Need to know about canvas here + if (ctx.viewLinesGpu) { + console.log('hit gpu'); + } + if (ctx.isInTopPadding(request.mouseVerticalOffset)) { return request.fulfillContentEmpty(new Position(1, 1), EMPTY_CONTENT_AFTER_LINES); } diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index b55cfa1e0c110..4279fa8fabedc 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -324,6 +324,7 @@ export class View extends ViewEventHandler { viewDomNode: this.domNode.domNode, linesContentDomNode: this._linesContent.domNode, viewLinesDomNode: this._viewLines.getDomNode().domNode, + viewLinesGpu: this._viewLinesGpu, focusTextArea: () => { this.focus(); @@ -342,6 +343,7 @@ export class View extends ViewEventHandler { this.render(true, false); }, shouldSuppressMouseDownOnViewZone: (viewZoneId: string) => { + // TODO: Defer to viewLinesGpu? return this._viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId); }, shouldSuppressMouseDownOnWidget: (widgetId: string) => { @@ -349,16 +351,19 @@ export class View extends ViewEventHandler { }, getPositionFromDOMInfo: (spanNode: HTMLElement, offset: number) => { this._flushAccumulatedAndRenderNow(); + // TODO: Defer to viewLinesGpu? return this._viewLines.getPositionFromDOMInfo(spanNode, offset); }, visibleRangeForPosition: (lineNumber: number, column: number) => { this._flushAccumulatedAndRenderNow(); - return this._viewLines.visibleRangeForPosition(new Position(lineNumber, column)); + const position = new Position(lineNumber, column); + return this._viewLines.visibleRangeForPosition(position) ?? this._viewLinesGpu?.visibleRangeForPosition(position) ?? null; }, getLineWidth: (lineNumber: number) => { this._flushAccumulatedAndRenderNow(); + // TODO: Defer to viewLinesGpu? return this._viewLines.getLineWidth(lineNumber); } }; diff --git a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts index 693bfc1f5da72..0ebec04d604d9 100644 --- a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts +++ b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts @@ -400,6 +400,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { } linesVisibleRangesForRange(range: Range, includeNewLines: boolean): LineVisibleRanges[] | null { + // TODO: Implement return null; } From 22ee791ce8629104cf784cd7b96027b8abb98aa1 Mon Sep 17 00:00:00 2001 From: Fredrik Anfinsen Date: Sun, 20 Oct 2024 09:06:01 +0200 Subject: [PATCH 002/555] Add support for links 'foo, l' --- .../terminalContrib/links/browser/terminalLinkParsing.ts | 3 ++- .../links/test/browser/terminalLinkParsing.test.ts | 1 + .../links/test/browser/terminalLocalLinkDetector.test.ts | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts index 4ff47d600d311..d93b62aa93813 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts @@ -84,12 +84,13 @@ function generateLinkSuffixRegex(eolOnly: boolean) { // foo#339 // foo#339:12 [#190288] // foo#339.12 + // foo, 339 [#217927] // "foo",339 // "foo",339:12 // "foo",339.12 // "foo",339.12-789 // "foo",339.12-341.789 - `(?::|#| |['"],)${r()}([:.]${c()}(?:-(?:${re()}\\.)?${ce()})?)?` + eolSuffix, + `(?::|#| |['"],|, )${r()}([:.]${c()}(?:-(?:${re()}\\.)?${ce()})?)?` + eolSuffix, // The quotes below are optional [#171652] // "foo", line 339 [#40468] // "foo", line 339, col 12 diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts index 352a8d17bf6c0..9d4684a66fa6c 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts @@ -60,6 +60,7 @@ const testLinks: ITestLink[] = [ { link: 'foo 339.12', prefix: undefined, suffix: ' 339.12', hasRow: true, hasCol: true }, { link: 'foo 339.12-789', prefix: undefined, suffix: ' 339.12-789', hasRow: true, hasCol: true, hasRowEnd: false, hasColEnd: true }, { link: 'foo 339.12-341.789', prefix: undefined, suffix: ' 339.12-341.789', hasRow: true, hasCol: true, hasRowEnd: true, hasColEnd: true }, + { link: 'foo, 339', prefix: undefined, suffix: ', 339', hasRow: true, hasCol: false }, // Double quotes { link: '"foo",339', prefix: '"', suffix: '",339', hasRow: true, hasCol: false }, diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLocalLinkDetector.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLocalLinkDetector.test.ts index df3d642769a3e..a2275a18a5915 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLocalLinkDetector.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLocalLinkDetector.test.ts @@ -104,6 +104,7 @@ const supportedLinkFormats: LinkFormatInfo[] = [ { urlFormat: '{0}": line {1}, col {2}', line: '5', column: '3' }, { urlFormat: '{0}({1})', line: '5' }, { urlFormat: '{0} ({1})', line: '5' }, + { urlFormat: '{0}, {1}', line: '5' }, { urlFormat: '{0}({1},{2})', line: '5', column: '3' }, { urlFormat: '{0} ({1},{2})', line: '5', column: '3' }, { urlFormat: '{0}: ({1},{2})', line: '5', column: '3' }, From 4b14c2d0452f3796df05a20075ebecfc35470ccb Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 21 Oct 2024 18:55:14 -0700 Subject: [PATCH 003/555] Use `Array` for union types in `vscode.d.ts` Pedantic but we mostly use `Array` for arrays of unions instead of `(x | y)[]`. Just adding an eslint rule and updating some places to be consistent --- eslint.config.js | 7 ++++++ src/vscode-dts/vscode.d.ts | 22 +++++++++---------- .../vscode.proposed.mappedEditsProvider.d.ts | 4 ++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index c09ba2ab55b09..9ba6289e725de 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -256,6 +256,13 @@ export default tseslint.config( 'local': pluginLocal, }, rules: { + 'no-restricted-syntax': [ + 'warn', + { + 'selector': `TSArrayType > TSUnionType`, + 'message': 'Use Array<...> for arrays of union types.' + }, + ], 'local/vscode-dts-create-func': 'warn', 'local/vscode-dts-literal-or-types': 'warn', 'local/vscode-dts-string-type-literals': 'warn', diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 2ce6f773c9233..8135635cd13a5 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -2698,7 +2698,7 @@ declare module 'vscode' { * We also support returning `Command` for legacy reasons, however all new extensions should return * `CodeAction` object instead. */ - provideCodeActions(document: TextDocument, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult<(Command | T)[]>; + provideCodeActions(document: TextDocument, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult>; /** * Given a code action fill in its {@linkcode CodeAction.edit edit}-property. Changes to @@ -8654,7 +8654,7 @@ declare module 'vscode' { * @param args The command arguments. * @param options Optional options for the started the shell. */ - constructor(command: string | ShellQuotedString, args: (string | ShellQuotedString)[], options?: ShellExecutionOptions); + constructor(command: string | ShellQuotedString, args: Array, options?: ShellExecutionOptions); /** * The shell command line. Is `undefined` if created with a command and arguments. @@ -8669,7 +8669,7 @@ declare module 'vscode' { /** * The shell args. Is `undefined` if created with a full command line. */ - args: (string | ShellQuotedString)[]; + args: Array; /** * The shell options used when the command line is executed in a shell. @@ -19390,7 +19390,7 @@ declare module 'vscode' { * @param content The content of the message. * @param name The optional name of a user for the message. */ - static User(content: string | (LanguageModelTextPart | LanguageModelToolResultPart)[], name?: string): LanguageModelChatMessage; + static User(content: string | Array, name?: string): LanguageModelChatMessage; /** * Utility to create a new assistant message. @@ -19398,7 +19398,7 @@ declare module 'vscode' { * @param content The content of the message. * @param name The optional name of a user for the message. */ - static Assistant(content: string | (LanguageModelTextPart | LanguageModelToolCallPart)[], name?: string): LanguageModelChatMessage; + static Assistant(content: string | Array, name?: string): LanguageModelChatMessage; /** * The role of this message. @@ -19409,7 +19409,7 @@ declare module 'vscode' { * A string or heterogeneous array of things that a message can contain as content. Some parts may be message-type * specific for some models. */ - content: (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart)[]; + content: Array; /** * The optional name of a user for this message. @@ -19423,7 +19423,7 @@ declare module 'vscode' { * @param content The content of the message. * @param name The optional name of a user for the message. */ - constructor(role: LanguageModelChatMessageRole, content: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart)[], name?: string); + constructor(role: LanguageModelChatMessageRole, content: string | Array, name?: string); } /** @@ -19866,13 +19866,13 @@ declare module 'vscode' { /** * The value of the tool result. */ - content: (LanguageModelTextPart | LanguageModelPromptTsxPart | unknown)[]; + content: Array; /** * @param callId The ID of the tool call. * @param content The content of the tool result. */ - constructor(callId: string, content: (LanguageModelTextPart | LanguageModelPromptTsxPart | unknown)[]); + constructor(callId: string, content: Array); } /** @@ -19884,13 +19884,13 @@ declare module 'vscode' { * the future. * @see {@link lm.invokeTool}. */ - content: (LanguageModelTextPart | LanguageModelPromptTsxPart | unknown)[]; + content: Array; /** * Create a LanguageModelToolResult * @param content A list of tool result content parts */ - constructor(content: (LanguageModelTextPart | LanguageModelPromptTsxPart | unknown)[]); + constructor(content: Array); } /** diff --git a/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts b/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts index 7cc3c7ff45a4e..04d95579f4d2e 100644 --- a/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts @@ -31,7 +31,7 @@ declare module 'vscode' { * The conversation that led to the current code block(s). * The last conversation part contains the code block(s) for which the code mapper should provide edits. */ - readonly conversation?: (ConversationRequest | ConversationResponse)[]; + readonly conversation?: Array; } /** @@ -57,7 +57,7 @@ declare module 'vscode' { export interface MappedEditsRequest { readonly codeBlocks: { code: string; resource: Uri; markdownBeforeBlock?: string }[]; - readonly conversation: (ConversationRequest | ConversationResponse)[]; // for every prior response that contains codeblocks, make sure we pass the code as well as the resources based on the reported codemapper URIs + readonly conversation: Array; // for every prior response that contains codeblocks, make sure we pass the code as well as the resources based on the reported codemapper URIs } export interface MappedEditsResponseStream { From f83df5cdec66b07e19881f5e9f7674c38cf69f6d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 26 Oct 2024 17:29:35 +0200 Subject: [PATCH 004/555] Dragging an active view between side bars or panel does not activate it (fix #231922) (#232229) * Dragging an active view between side bars or panel does not activate it (fix #231922) * address feedback --- src/vs/workbench/browser/parts/compositeBar.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 9af8928288c3e..4da7a57e952e7 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -49,16 +49,23 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { if (dragData.type === 'composite') { const currentContainer = this.viewDescriptorService.getViewContainerById(dragData.id)!; const currentLocation = this.viewDescriptorService.getViewContainerLocation(currentContainer); + let moved = false; // ... on the same composite bar if (currentLocation === this.targetContainerLocation) { if (targetCompositeId) { this.moveComposite(dragData.id, targetCompositeId, before); + moved = true; } } // ... on a different composite bar else { this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation, this.getTargetIndex(targetCompositeId, before), 'dnd'); + moved = true; + } + + if (moved) { + this.openComposite(currentContainer.id, true); } } From 2d48f98e8166210a38be99299bb6bbf734571b63 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 26 Oct 2024 09:05:48 -0700 Subject: [PATCH 005/555] fix: "reject" -> "discard" (#232337) --- src/vs/workbench/contrib/chat/browser/chatEditorActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts index b4ad84e28037c..f913099995070 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts @@ -70,7 +70,7 @@ abstract class AcceptDiscardAction extends Action2 { : 'chatEditor.action.reject', title: accept ? localize2('accept', 'Accept Chat Edit') - : localize2('reject', 'Reject Chat Edit'), + : localize2('discard', 'Discard Chat Edit'), category: CHAT_CATEGORY, icon: accept ? Codicon.check From 4edbcca4013249b921e47eac0722fae902704608 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 26 Oct 2024 18:33:11 -0700 Subject: [PATCH 006/555] refactor: deprecate `chat.editing.experimental.enableRestoreFile` (#232346) --- .../contrib/chat/browser/chat.contribution.ts | 6 -- .../browser/chatEditing/chatEditingActions.ts | 58 +----------- .../contrib/chat/browser/chatListRenderer.ts | 8 +- .../contrib/chat/browser/chatWidget.ts | 10 +-- .../contrib/chat/browser/media/chat.css | 19 ---- .../contrib/chat/common/chatEditingService.ts | 1 - .../contrib/chat/common/chatModel.ts | 89 +------------------ .../contrib/chat/common/chatServiceImpl.ts | 6 +- .../contrib/chat/common/chatViewModel.ts | 21 +---- 9 files changed, 14 insertions(+), 204 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 22755a3f6f7c1..6193c62fb827b 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -134,12 +134,6 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize('chat.editing.confirmEditRequestRetry', "Whether to show a confirmation before retrying a request and its associated edits."), default: true, }, - 'chat.editing.experimental.enableRestoreFile': { - type: 'boolean', - scope: ConfigurationScope.APPLICATION, - markdownDescription: nls.localize('chat.editing.enableRestoreFile', "Whether to show a toggle to restore an earlier version of a file that was edited in a chat editing session request."), - default: false, - }, 'chat.experimental.detectParticipant.enabled': { type: 'boolean', description: nls.localize('chat.experimental.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index 3fa5ba9d642fe..d6101195773c9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -21,8 +21,8 @@ import { IListService } from '../../../../../platform/list/browser/listService.j import { GroupsOrder, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { ChatAgentLocation } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_ITEM_ID, CONTEXT_LAST_ITEM_ID, CONTEXT_REQUEST, CONTEXT_RESPONSE } from '../../common/chatContextKeys.js'; -import { applyingChatEditsContextKey, applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingResourceContextKey, chatEditingWidgetFileStateContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, isChatRequestCheckpointed, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_ITEM_ID, CONTEXT_LAST_ITEM_ID, CONTEXT_REQUEST } from '../../common/chatContextKeys.js'; +import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingResourceContextKey, chatEditingWidgetFileStateContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IChatService } from '../../common/chatService.js'; import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; import { CHAT_CATEGORY } from '../actions/chatActions.js'; @@ -334,60 +334,6 @@ registerAction2(class AddFilesToWorkingSetAction extends Action2 { } }); - -registerAction2(class RestoreWorkingSetAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.chat.restoreFile', - title: localize2('chat.restoreSnapshot.label', 'Restore File Snapshot'), - f1: false, - icon: Codicon.target, - shortTitle: localize2('chat.restoreSnapshot.shortTitle', 'Restore Snapshot'), - toggled: { - condition: isChatRequestCheckpointed, - title: localize2('chat.restoreSnapshot.title', 'Using Snapshot').value, - tooltip: localize('chat.restoreSnapshot.tooltip', 'Toggle to use a previous snapshot of an edited file in your next request') - }, - precondition: ContextKeyExpr.and(applyingChatEditsContextKey.negate(), CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), - menu: { - id: MenuId.ChatMessageFooter, - group: 'navigation', - order: 1000, - when: ContextKeyExpr.and(ContextKeyExpr.equals('config.chat.editing.experimental.enableRestoreFile', true), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_RESPONSE, ContextKeyExpr.notIn(CONTEXT_ITEM_ID.key, CONTEXT_LAST_ITEM_ID.key)) - } - }); - } - - override run(accessor: ServicesAccessor, ...args: any[]): void { - const chatEditingService = accessor.get(IChatEditingService); - const item = args[0]; - if (!isResponseVM(item)) { - return; - } - - const { session, requestId } = item.model; - const shouldUnsetCheckpoint = requestId === session.checkpoint?.id; - if (shouldUnsetCheckpoint) { - // Unset the existing checkpoint - session.setCheckpoint(undefined); - } else { - session.setCheckpoint(requestId); - } - - // The next request is associated with the working set snapshot representing - // the 'good state' from this checkpointed request - const chatService = accessor.get(IChatService); - const chatModel = chatService.getSession(item.sessionId); - const chatRequests = chatModel?.getRequests(); - const snapshot = chatRequests?.find((v, i) => i > 0 && chatRequests[i - 1]?.id === requestId); - if (!shouldUnsetCheckpoint && snapshot !== undefined) { - chatEditingService.restoreSnapshot(snapshot.id); - } else if (shouldUnsetCheckpoint) { - chatEditingService.restoreSnapshot(undefined); - } - } -}); - registerAction2(class RemoveAction extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index cce9be8dd264f..c0463254b731c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -44,7 +44,6 @@ import { IWorkbenchIssueService } from '../../issue/common/issue.js'; import { annotateSpecialMarkdownContent } from '../common/annotations.js'; import { ChatAgentLocation, IChatAgentMetadata } from '../common/chatAgents.js'; import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_ITEM_ID, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND, CONTEXT_RESPONSE_ERROR, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from '../common/chatContextKeys.js'; -import { isChatRequestCheckpointed } from '../common/chatEditingService.js'; import { IChatRequestVariableEntry, IChatTextEditGroup } from '../common/chatModel.js'; import { chatSubcommandLeader } from '../common/chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatConfirmation, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatTask, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData } from '../common/chatService.js'; @@ -89,7 +88,6 @@ interface IChatListItemTemplate { readonly templateDisposables: IDisposable; readonly elementDisposables: DisposableStore; readonly agentHover: ChatAgentHover; - readonly disabledOverlay: HTMLElement; } interface IItemHeightChangeParams { @@ -242,7 +240,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const requestId = isRequestVM(element) ? element.id : element.requestId; - const checkpointedRequestId = this._viewModel?.model.checkpoint?.id; return element.dataId + // Ensure re-rendering an element once slash commands are loaded, so the colorization can be applied. `${(isRequestVM(element)) /* && !!this.lastSlashCommands ? '_scLoaded' : '' */}` + @@ -516,12 +514,8 @@ export class ChatWidget extends Disposable implements IChatWidget { `${isResponseVM(element) && element.renderData ? `_${this.visibleChangeCount}` : ''}` + // Re-render once content references are loaded (isResponseVM(element) ? `_${element.contentReferences.length}` : '') + - // Re-render if element becomes enabled/disabled due to checkpointing - `_${element.isDisabled ? '1' : '0'}` + // Re-render if element becomes hidden due to undo/redo `_${element.isHidden ? '1' : '0'}` + - // Re-render if element checkpoint state changed - `_${requestId === checkpointedRequestId ? '1' : '0'}` + // Rerender request if we got new content references in the response // since this may change how we render the corresponding attachments in the request (isRequestVM(element) && element.contentReferences ? `_${element.contentReferences?.length}` : ''); @@ -973,7 +967,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const requests = this.viewModel.model.getRequests(); for (let i = requests.length - 1; i >= 0; i -= 1) { const request = requests[i]; - if (request.isDisabled || request.isHidden) { + if (request.isHidden) { this.chatService.removeRequest(this.viewModel.sessionId, request.id); } } @@ -1037,8 +1031,6 @@ export class ChatWidget extends Disposable implements IChatWidget { const responses = this.viewModel?.getItems().filter(isResponseVM); const lastResponse = responses?.[responses.length - 1]; this.chatAccessibilityService.acceptResponse(lastResponse, requestId, options?.isVoiceInput); - // Keep the checkpoint toggled on until the response is complete to help the user keep their place in the chat history - this.viewModel?.model.setCheckpoint(undefined); if (lastResponse?.result?.nextQuestion) { const { prompt, participant, command } = lastResponse.result.nextQuestion; const question = formatChatQuestion(this.chatAgentService, this.location, prompt, participant, command); diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index c6bfe4cbada84..9907d5c5d8e59 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -24,10 +24,6 @@ -webkit-user-select: text; } -.interactive-item-container.disabled { - filter: blur(2px); -} - .interactive-item-container .header { display: flex; align-items: center; @@ -1043,21 +1039,6 @@ have to be updated for changes to the rules above, or to support more deeply nes margin: 0; } -.disabled-overlay { - display: none; -} - -.disabled-overlay.disabled { - position: absolute; - width: 100%; - height: 100%; - background-color: rgba(64, 64, 64, 0.2); - pointer-events: none; - display: flex; - cursor: default; - z-index: 1; -} - .interactive-response .interactive-response-codicon-details { display: flex; align-items: start; diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index b5e6d173dda4e..1fad277514f73 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -105,7 +105,6 @@ export const decidedChatEditingResourceContextKey = new RawContextKey( export const chatEditingResourceContextKey = new RawContextKey('chatEditingResource', undefined); export const inChatEditingSessionContextKey = new RawContextKey('inChatEditingSession', undefined); export const applyingChatEditsContextKey = new RawContextKey('isApplyingChatEdits', undefined); -export const isChatRequestCheckpointed = new RawContextKey('isChatRequestCheckpointed', false); export const hasUndecidedChatEditingResourceContextKey = new RawContextKey('hasUndecidedChatEditingResource', false); export const hasAppliedChatEditsContextKey = new RawContextKey('hasAppliedChatEdits', false); export const applyingChatEditsFailedContextKey = new RawContextKey('applyingChatEditsFailed', false); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 6e32e1b7f93e5..cc7d9371cf369 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -85,7 +85,6 @@ export interface IChatRequestModel { readonly workingSet?: URI[]; readonly isCompleteAddedRequest: boolean; readonly response?: IChatResponseModel; - isDisabled: boolean; isHidden: boolean; } @@ -160,7 +159,6 @@ export interface IChatResponseModel { readonly response: IResponse; readonly isComplete: boolean; readonly isCanceled: boolean; - isDisabled: boolean; readonly isHidden: boolean; readonly isCompleteAddedRequest: boolean; /** A stale response is one that has been persisted and rehydrated, so e.g. Commands that have their arguments stored in the EH are gone. */ @@ -179,8 +177,6 @@ export class ChatRequestModel implements IChatRequestModel { public response: ChatResponseModel | undefined; - public isDisabled: boolean = false; - public readonly id: string; public get session() { @@ -420,10 +416,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._session; } - public get isDisabled() { - return this._isDisabled; - } - public get isHidden() { return this._isHidden; } @@ -522,7 +514,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel private _voteDownReason?: ChatAgentVoteDownReason, private _result?: IChatAgentResult, followups?: ReadonlyArray, - private _isDisabled: boolean = false, public readonly isCompleteAddedRequest = false, private _isHidden: boolean = false ) { @@ -534,16 +525,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel this._followups = followups ? [...followups] : undefined; this._response = this._register(new Response(_response)); this._register(this._response.onDidChangeValue(() => this._onDidChange.fire())); - this._register(this._session.onDidChange((e) => { - if (e.kind === 'setCheckpoint') { - const isDisabled = e.disabledResponseIds.has(this.id); - const didChange = this._isDisabled !== isDisabled; - this._isDisabled = isDisabled; - if (didChange) { - this._onDidChange.fire(); - } - } - })); this.id = 'response_' + ChatResponseModel.nextId++; } @@ -643,10 +624,8 @@ export interface IChatModel { readonly sampleQuestions: IChatFollowup[] | undefined; readonly requestInProgress: boolean; readonly inputPlaceholder?: string; - readonly checkpoint: IChatRequestModel | undefined; - setCheckpoint(requestId: string | undefined): void; disableRequests(requestIds: ReadonlyArray): void; - getRequests(includeDisabledRequests?: boolean): IChatRequestModel[]; + getRequests(): IChatRequestModel[]; toExport(): IExportableChatData; toJSON(): ISerializableChatData; } @@ -792,7 +771,6 @@ export type IChatChangeEvent = | IChatAddResponseEvent | IChatSetAgentEvent | IChatMoveEvent - | IChatSetCheckpointEvent | IChatSetHiddenEvent ; @@ -835,12 +813,6 @@ export interface IChatRemoveRequestEvent { reason: ChatRequestRemovalReason; } -export interface IChatSetCheckpointEvent { - kind: 'setCheckpoint'; - disabledRequestIds: Set; - disabledResponseIds: Set; -} - export interface IChatSetHiddenEvent { kind: 'setHidden'; hiddenRequestIds: Set; @@ -1019,8 +991,8 @@ export class ChatModel extends Disposable implements IChatModel { const result = 'responseErrorDetails' in raw ? // eslint-disable-next-line local/code-no-dangerous-type-assertions { errorDetails: raw.responseErrorDetails } as IChatAgentResult : raw.result; - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.voteDownReason, result, raw.followups, request.isDisabled); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.voteDownReason, result, raw.followups); request.response.applyReference(revive(raw.usedContext)); } @@ -1108,19 +1080,8 @@ export class ChatModel extends Disposable implements IChatModel { return this._isInitializedDeferred.p; } - getRequests(includeDisabledRequests = true): ChatRequestModel[] { - if (includeDisabledRequests) { - return this._requests; - } - - const requests: ChatRequestModel[] = []; - for (const request of this._requests) { - if (request.isDisabled) { - break; - } - requests.push(request); - } - return requests; + getRequests(): ChatRequestModel[] { + return this._requests; } private _checkpoint: ChatRequestModel | undefined = undefined; @@ -1128,48 +1089,6 @@ export class ChatModel extends Disposable implements IChatModel { return this._checkpoint; } - setCheckpoint(requestId: string | undefined) { - let checkpoint: ChatRequestModel | undefined; - let checkpointIndex = -1; - if (requestId !== undefined) { - this._requests.forEach((request, index) => { - if (request.id === requestId) { - checkpointIndex = index; - checkpoint = request; - } - }); - - if (!checkpoint) { - return; // Invalid request ID - } - } - - const disabledRequestIds = new Set(); - const disabledResponseIds = new Set(); - for (let i = this._requests.length - 1; i >= 0; i -= 1) { - const request = this._requests[i]; - if (this._checkpoint && !checkpoint) { - // The user removed the checkpoint - request.isDisabled = false; - } else if (checkpoint && i > checkpointIndex) { - request.isDisabled = true; - disabledRequestIds.add(request.id); - if (request.response) { - disabledResponseIds.add(request.response.id); - } - } else if (checkpoint && i <= checkpointIndex) { - request.isDisabled = false; - } - } - - this._checkpoint = checkpoint; - this._onDidChange.fire({ - kind: 'setCheckpoint', - disabledRequestIds, - disabledResponseIds - }); - } - disableRequests(requestIds: ReadonlyArray) { this._requests.forEach((request) => { const isHidden = requestIds.includes(request.id); diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 957b924667f98..fff7f35789fed 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -539,7 +539,7 @@ export class ChatService extends Disposable implements IChatService { const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); const commandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart); - const requests = [...model.getRequests(false)]; + const requests = [...model.getRequests()]; let gotProgress = false; const requestType = commandPart ? 'slashCommand' : 'string'; @@ -677,14 +677,14 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken); - chatTitlePromise = model.getRequests(false).length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model.getRequests(false), model.sessionId, location, agent.id), CancellationToken.None) : undefined; + chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model.getRequests(), model.sessionId, location, agent.id), CancellationToken.None) : undefined; } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { variables: [] }, attempt); completeResponseCreated(); // contributed slash commands // TODO: spell this out in the UI const history: IChatMessage[] = []; - for (const request of model.getRequests(false)) { + for (const request of model.getRequests()) { if (!request.response) { continue; } diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index be75cae4eed2a..e12486c5c35ba 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -28,7 +28,7 @@ export function isResponseVM(item: unknown): item is IChatResponseViewModel { return !!item && typeof (item as IChatResponseViewModel).setVote !== 'undefined'; } -export type IChatViewModelChangeEvent = IChatAddRequestEvent | IChangePlaceholderEvent | IChatSessionInitEvent | IChatSetCheckpointEvent | IChatSetHiddenEvent | null; +export type IChatViewModelChangeEvent = IChatAddRequestEvent | IChangePlaceholderEvent | IChatSessionInitEvent | IChatSetHiddenEvent | null; export interface IChatAddRequestEvent { kind: 'addRequest'; @@ -42,10 +42,6 @@ export interface IChatSessionInitEvent { kind: 'initialize'; } -export interface IChatSetCheckpointEvent { - kind: 'setCheckpoint'; -} - export interface IChatSetHiddenEvent { kind: 'setHidden'; } @@ -78,7 +74,6 @@ export interface IChatRequestViewModel { readonly contentReferences?: ReadonlyArray; readonly workingSet?: ReadonlyArray; readonly confirmation?: string; - readonly isDisabled?: boolean; readonly isHidden: boolean; readonly isCompleteAddedRequest: boolean; } @@ -182,7 +177,6 @@ export interface IChatResponseViewModel { readonly errorDetails?: IChatResponseErrorDetails; readonly result?: IChatAgentResult; readonly contentUpdateTimings?: IChatLiveUpdateData; - readonly isDisabled: boolean; readonly isHidden: boolean; readonly isCompleteAddedRequest: boolean; renderData?: IChatResponseRenderData; @@ -283,9 +277,8 @@ export class ChatViewModel extends Disposable implements IChatViewModel { const modelEventToVmEvent: IChatViewModelChangeEvent = e.kind === 'addRequest' ? { kind: 'addRequest' } : e.kind === 'initialize' ? { kind: 'initialize' } - : e.kind === 'setCheckpoint' ? { kind: 'setCheckpoint' } - : e.kind === 'setHidden' ? { kind: 'setHidden' } - : null; + : e.kind === 'setHidden' ? { kind: 'setHidden' } + : null; this._onDidChange.fire(modelEventToVmEvent); })); } @@ -381,10 +374,6 @@ export class ChatRequestViewModel implements IChatRequestViewModel { return this._model.confirmation; } - get isDisabled() { - return this._model.isDisabled; - } - get isCompleteAddedRequest() { return this._model.isCompleteAddedRequest; } @@ -482,10 +471,6 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.isCanceled; } - get isDisabled() { - return this._model.isDisabled; - } - get isHidden() { return this._model.isHidden; } From 64f36d05decea75ff4875e5f54306f931ec4213c Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 26 Oct 2024 20:13:37 -0700 Subject: [PATCH 007/555] fix: rerender chat edits file list if entry state changes (#232352) --- .../chat/browser/chatEditing/chatEditingSession.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index e8905eb58180e..862cc70a0f5a6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -8,7 +8,7 @@ import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { Emitter } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { ResourceMap, ResourceSet } from '../../../../../base/common/map.js'; -import { derived, IObservable, ITransaction, observableValue, transaction } from '../../../../../base/common/observable.js'; +import { autorun, derived, IObservable, ITransaction, observableValue, transaction } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { isCodeEditor, isDiffEditor } from '../../../../../editor/browser/editorBrowser.js'; import { IBulkEditService } from '../../../../../editor/browser/services/bulkEditService.js'; @@ -147,6 +147,13 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio this._register(this._editorService.onDidCloseEditor((e) => { this._trackCurrentEditorsInWorkingSet(e); })); + this._register(autorun(reader => { + const entries = this.entries.read(reader); + entries.forEach(entry => { + entry.state.read(reader); + }); + this._onDidChange.fire(); + })); } private _trackCurrentEditorsInWorkingSet(e?: IEditorCloseEvent) { From 3e22072fc5422c5d373df45239867bb4f89f6aaa Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sun, 27 Oct 2024 01:17:58 -0700 Subject: [PATCH 008/555] fix: sync revert from SCM with chat editing list state (#232356) --- .../chatEditingModifiedFileEntry.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index dfd387cc97be1..328ee43902d60 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -25,7 +25,7 @@ import { IModelService } from '../../../../../editor/common/services/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { IModelContentChange, IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; import { localize } from '../../../../../nls.js'; -import { IFileService } from '../../../../../platform/files/common/files.js'; +import { FileOperation, IFileService } from '../../../../../platform/files/common/files.js'; import { editorSelectionBackground } from '../../../../../platform/theme/common/colorRegistry.js'; import { IUndoRedoService } from '../../../../../platform/undoRedo/common/undoRedo.js'; import { IChatAgentResult } from '../../common/chatAgents.js'; @@ -41,6 +41,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie public readonly docSnapshot: ITextModel; private readonly doc: ITextModel; + private readonly originalContent; private readonly _onDidDelete = this._register(new Emitter()); public get onDidDelete() { @@ -128,6 +129,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie this.createdInRequestId = this._telemetryInfo.requestId; } this.doc = resourceRef.object.textEditorModel; + this.originalContent = this.doc.getValue(); const docSnapshot = this.docSnapshot = this._register( modelService.createModel( createTextBufferFactoryFromSnapshot(this.doc.createSnapshot()), @@ -150,6 +152,12 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie this._register(resourceRef); this._register(this.doc.onDidChangeContent(e => this._mirrorEdits(e))); + this._register(this._fileService.onDidRunOperation(e => { + if (e.operation === FileOperation.DELETE && kind === ChatEditKind.Created && e.resource.toString() === this.resource.toString()) { + this._onDidDelete.fire(); + this.dispose(); + } + })); this._register(toDisposable(() => { this._clearCurrentEditLineDecoration(); @@ -238,6 +246,25 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie this.docSnapshot.applyEdits(edits); this._edit = e_ai.tryRebase(e_user_r); } + + } + + if (!this.isCurrentlyBeingModified.get()) { + const didResetToOriginalContent = this.doc.getValue() === this.originalContent; + const currentState = this._stateObs.get(); + switch (currentState) { + case WorkingSetEntryState.Accepted: + case WorkingSetEntryState.Modified: + if (didResetToOriginalContent) { + this._stateObs.set(WorkingSetEntryState.Rejected, undefined); + break; + } + case WorkingSetEntryState.Rejected: + if (event.isUndoing && !didResetToOriginalContent) { + this._stateObs.set(WorkingSetEntryState.Modified, undefined); + break; + } + } } this._updateDiffInfoSeq(!this._isEditFromUs); From 7aac19c43cac6bd74f8136c34b4802a539a1731b Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Sun, 27 Oct 2024 09:11:20 -0700 Subject: [PATCH 009/555] fix: Dont show chat panel on ext updates (#232327) --- .../contrib/chat/browser/actions/chatGettingStarted.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts b/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts index 1cecb8cd9a318..1868e212d7507 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts @@ -10,7 +10,7 @@ import { ICommandService } from '../../../../../platform/commands/common/command import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; import { CHAT_OPEN_ACTION_ID } from './chatActions.js'; -import { IExtensionManagementService } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; +import { IExtensionManagementService, InstallOperation } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; export class ChatGettingStartedContribution extends Disposable implements IWorkbenchContribution { @@ -36,7 +36,7 @@ export class ChatGettingStartedContribution extends Disposable implements IWorkb this._register(this.extensionManagementService.onDidInstallExtensions(async (result) => { for (const e of result) { - if (ExtensionIdentifier.equals(this.productService.gitHubEntitlement!.extensionId, e.identifier.id)) { + if (ExtensionIdentifier.equals(this.productService.gitHubEntitlement!.extensionId, e.identifier.id) && e.operation === InstallOperation.Install) { this.recentlyInstalled = true; return; } From f506989d9e2f33d396b8c09522d8e0d61033f4ab Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Sun, 27 Oct 2024 23:02:09 +0100 Subject: [PATCH 010/555] Use async find provider only for file scheme (#232372) * Use async find provider only for file scheme (#230592) * only use asyncfindprovider for file scheme * add vscode remote * Use async find provider only for file scheme (#230592) * only use asyncfindprovider for file scheme * add vscode remote * :lipstick: --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 623aa97aadec9..7e7dc5aebafe9 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -59,6 +59,7 @@ import { basename, relativePath } from '../../../../../base/common/resources.js' import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; import { getExcludes, ISearchComplete, ISearchConfiguration, ISearchService, QueryType } from '../../../../services/search/common/search.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { Schemas } from '../../../../../base/common/network.js'; function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem[]): boolean { @@ -561,6 +562,8 @@ export class ExplorerView extends ViewPane implements IExplorerView { const getFileNestingSettings = (item?: ExplorerItem) => this.configurationService.getValue({ resource: item?.root.resource }).explorer.fileNesting; + const rootsSupportFindProvider = this.explorerService.roots.every(root => root.resource.scheme === Schemas.file || root.resource.scheme === Schemas.vscodeRemote); + this.tree = >this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer], this.instantiationService.createInstance(ExplorerDataSource, this.filter), { compressionEnabled: isCompressionEnabled(), @@ -608,7 +611,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { }, paddingBottom: ExplorerDelegate.ITEM_HEIGHT, overrideStyles: this.getLocationBasedColors().listOverrideStyles, - findResultsProvider: this.instantiationService.createInstance(ExplorerFindProvider), + findResultsProvider: rootsSupportFindProvider ? this.instantiationService.createInstance(ExplorerFindProvider) : undefined, }); this._register(this.tree); this._register(this.themeService.onDidColorThemeChange(() => this.tree.rerender())); From 8717c1129ed1ac8791f9f509069829aa00eef3fb Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sun, 27 Oct 2024 16:15:03 -0700 Subject: [PATCH 011/555] fix: sync revert of chat-created file from SCM with chat editing list state (#232375) --- .../browser/chatEditing/chatEditingModifiedFileEntry.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index 328ee43902d60..e2d1841f33c39 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -25,7 +25,7 @@ import { IModelService } from '../../../../../editor/common/services/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { IModelContentChange, IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; import { localize } from '../../../../../nls.js'; -import { FileOperation, IFileService } from '../../../../../platform/files/common/files.js'; +import { IFileService } from '../../../../../platform/files/common/files.js'; import { editorSelectionBackground } from '../../../../../platform/theme/common/colorRegistry.js'; import { IUndoRedoService } from '../../../../../platform/undoRedo/common/undoRedo.js'; import { IChatAgentResult } from '../../common/chatAgents.js'; @@ -152,8 +152,9 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie this._register(resourceRef); this._register(this.doc.onDidChangeContent(e => this._mirrorEdits(e))); - this._register(this._fileService.onDidRunOperation(e => { - if (e.operation === FileOperation.DELETE && kind === ChatEditKind.Created && e.resource.toString() === this.resource.toString()) { + this._register(this._fileService.watch(this.resource)); + this._register(this._fileService.onDidFilesChange(e => { + if (e.affects(this.resource) && kind === ChatEditKind.Created && e.gotDeleted()) { this._onDidDelete.fire(); this.dispose(); } From a2a9cb3c1960decafa2b6bb92aa562ae68634121 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 28 Oct 2024 10:05:41 +0100 Subject: [PATCH 012/555] inline chat should react to widget clearing (#232390) * For the clear command, use `lastFocusedWidget` over `openView` re https://github.com/microsoft/vscode-copilot/issues/9865 * inline chat should react to widget clearing --- .../chat/browser/actions/chatClearActions.ts | 20 +++++++------------ .../browser/inlineChatController.ts | 11 +++++++++- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 65205a0b0c67b..3682d4075cd34 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -58,7 +58,7 @@ export function registerNewChatActions() { title: localize2('chat.newChat.label', "New Chat"), category: CHAT_CATEGORY, icon: Codicon.plus, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession)), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -85,21 +85,15 @@ export function registerNewChatActions() { const context = args[0]; const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const widgetService = accessor.get(IChatWidgetService); + + let widget = widgetService.lastFocusedWidget; + if (isChatViewTitleActionContext(context)) { - const widget = widgetService.getWidgetBySessionId(context.sessionId); // Is running in the Chat view title - announceChatCleared(accessibilitySignalService); - if (widget) { - widget.clear(); - widget.focusInput(); - } - } else { - // Is running from f1 or keybinding - const viewsService = accessor.get(IViewsService); - - const chatView = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; - const widget = chatView.widget; + widget = widgetService.getWidgetBySessionId(context.sessionId); + } + if (widget) { announceChatCleared(accessibilitySignalService); widget.clear(); widget.focusInput(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index fed6152dba7e8..5cae592917316 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -185,7 +185,16 @@ export class InlineChatController implements IEditorContribution { } } - return this._store.add(_instaService.createInstance(InlineChatZoneWidget, location, this._editor)); + const zone = _instaService.createInstance(InlineChatZoneWidget, location, this._editor); + this._store.add(zone); + this._store.add(zone.widget.chatWidget.onDidClear(async () => { + const r = this.joinCurrentRun(); + this.cancelSession(); + await r; + this.run(); + })); + + return zone; }); this._store.add(this._editor.onDidChangeModel(async e => { From 3bfd5dd4e43e1f8e8d719968d47989e9da39fde6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 28 Oct 2024 10:33:50 +0100 Subject: [PATCH 013/555] chore - split language status into two files (#232391) also fix https://github.com/microsoft/vscode/issues/231946 by replace icons with the spinner so that width stays constant --- .../browser/languageStatus.contribution.ts | 443 +----------------- .../languageStatus/browser/languageStatus.ts | 442 +++++++++++++++++ 2 files changed, 447 insertions(+), 438 deletions(-) create mode 100644 src/vs/workbench/contrib/languageStatus/browser/languageStatus.ts diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index d30090b966a10..d1edba9a891ec 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -3,443 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import './media/languageStatus.css'; -import * as dom from '../../../../base/browser/dom.js'; -import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; -import { Disposable, DisposableStore, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; -import Severity from '../../../../base/common/severity.js'; -import { getCodeEditor, ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; -import { localize, localize2 } from '../../../../nls.js'; -import { Registry } from '../../../../platform/registry/common/platform.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js'; -import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { ILanguageStatus, ILanguageStatusService } from '../../../services/languageStatus/common/languageStatusService.js'; -import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; -import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, ShowTooltipCommand, StatusbarAlignment, StatusbarEntryKind } from '../../../services/statusbar/browser/statusbar.js'; -import { parseLinkedText } from '../../../../base/common/linkedText.js'; -import { Link } from '../../../../platform/opener/browser/link.js'; -import { IOpenerService } from '../../../../platform/opener/common/opener.js'; -import { MarkdownString } from '../../../../base/common/htmlContent.js'; -import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; -import { Action } from '../../../../base/common/actions.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; -import { equals } from '../../../../base/common/arrays.js'; -import { URI } from '../../../../base/common/uri.js'; -import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; -import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; -import { IAccessibilityInformation } from '../../../../platform/accessibility/common/accessibility.js'; -import { IEditorGroupsService, IEditorPart } from '../../../services/editor/common/editorGroupsService.js'; -import { IHoverService, nativeHoverDelegate } from '../../../../platform/hover/browser/hover.js'; -import { Event } from '../../../../base/common/event.js'; -import { joinStrings } from '../../../../base/common/strings.js'; +import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { LanguageStatusContribution, ResetAction } from './languageStatus.js'; -class LanguageStatusViewModel { - constructor( - readonly combined: readonly ILanguageStatus[], - readonly dedicated: readonly ILanguageStatus[] - ) { } - - isEqual(other: LanguageStatusViewModel) { - return equals(this.combined, other.combined) && equals(this.dedicated, other.dedicated); - } -} - -class StoredCounter { - - constructor(@IStorageService private readonly _storageService: IStorageService, private readonly _key: string) { } - - get value() { - return this._storageService.getNumber(this._key, StorageScope.PROFILE, 0); - } - - increment(): number { - const n = this.value + 1; - this._storageService.store(this._key, n, StorageScope.PROFILE, StorageTarget.MACHINE); - return n; - } -} - -class LanguageStatusContribution extends Disposable implements IWorkbenchContribution { - - constructor( - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - ) { - super(); - - for (const part of editorGroupService.parts) { - this.createLanguageStatus(part); - } - - this._register(editorGroupService.onDidCreateAuxiliaryEditorPart(part => this.createLanguageStatus(part))); - } - - private createLanguageStatus(part: IEditorPart): void { - const disposables = new DisposableStore(); - Event.once(part.onWillDispose)(() => disposables.dispose()); - - const scopedInstantiationService = this.editorGroupService.getScopedInstantiationService(part); - disposables.add(scopedInstantiationService.createInstance(LanguageStatus)); - } -} - -class LanguageStatus { - - private static readonly _id = 'status.languageStatus'; - - private static readonly _keyDedicatedItems = 'languageStatus.dedicated'; - - private readonly _disposables = new DisposableStore(); - private readonly _interactionCounter: StoredCounter; - - private _dedicated = new Set(); - - private _model?: LanguageStatusViewModel; - private _combinedEntry?: IStatusbarEntryAccessor; - private _dedicatedEntries = new Map(); - private readonly _renderDisposables = new DisposableStore(); - - constructor( - @ILanguageStatusService private readonly _languageStatusService: ILanguageStatusService, - @IStatusbarService private readonly _statusBarService: IStatusbarService, - @IEditorService private readonly _editorService: IEditorService, - @IHoverService private readonly _hoverService: IHoverService, - @IOpenerService private readonly _openerService: IOpenerService, - @IStorageService private readonly _storageService: IStorageService, - ) { - _storageService.onDidChangeValue(StorageScope.PROFILE, LanguageStatus._keyDedicatedItems, this._disposables)(this._handleStorageChange, this, this._disposables); - this._restoreState(); - this._interactionCounter = new StoredCounter(_storageService, 'languageStatus.interactCount'); - - _languageStatusService.onDidChange(this._update, this, this._disposables); - _editorService.onDidActiveEditorChange(this._update, this, this._disposables); - this._update(); - - _statusBarService.onDidChangeEntryVisibility(e => { - if (!e.visible && this._dedicated.has(e.id)) { - this._dedicated.delete(e.id); - this._update(); - this._storeState(); - } - }, undefined, this._disposables); - - } - - dispose(): void { - this._disposables.dispose(); - this._combinedEntry?.dispose(); - dispose(this._dedicatedEntries.values()); - this._renderDisposables.dispose(); - } - - // --- persisting dedicated items - - private _handleStorageChange() { - this._restoreState(); - this._update(); - } - - private _restoreState(): void { - const raw = this._storageService.get(LanguageStatus._keyDedicatedItems, StorageScope.PROFILE, '[]'); - try { - const ids = JSON.parse(raw); - this._dedicated = new Set(ids); - } catch { - this._dedicated.clear(); - } - } - - private _storeState(): void { - if (this._dedicated.size === 0) { - this._storageService.remove(LanguageStatus._keyDedicatedItems, StorageScope.PROFILE); - } else { - const raw = JSON.stringify(Array.from(this._dedicated.keys())); - this._storageService.store(LanguageStatus._keyDedicatedItems, raw, StorageScope.PROFILE, StorageTarget.USER); - } - } - - // --- language status model and UI - - private _createViewModel(editor: ICodeEditor | null): LanguageStatusViewModel { - if (!editor?.hasModel()) { - return new LanguageStatusViewModel([], []); - } - const all = this._languageStatusService.getLanguageStatus(editor.getModel()); - const combined: ILanguageStatus[] = []; - const dedicated: ILanguageStatus[] = []; - for (const item of all) { - if (this._dedicated.has(item.id)) { - dedicated.push(item); - } - combined.push(item); - } - return new LanguageStatusViewModel(combined, dedicated); - } - - private _update(): void { - const editor = getCodeEditor(this._editorService.activeTextEditorControl); - const model = this._createViewModel(editor); - - if (this._model?.isEqual(model)) { - return; - } - this._renderDisposables.clear(); - - this._model = model; - - // update when editor language changes - editor?.onDidChangeModelLanguage(this._update, this, this._renderDisposables); - - // combined status bar item is a single item which hover shows - // each status item - if (model.combined.length === 0) { - // nothing - this._combinedEntry?.dispose(); - this._combinedEntry = undefined; - - } else { - const [first] = model.combined; - const showSeverity = first.severity >= Severity.Warning; - const text = LanguageStatus._severityToComboCodicon(first.severity); - - let isOneBusy = false; - const ariaLabels: string[] = []; - const element = document.createElement('div'); - for (const status of model.combined) { - const isPinned = model.dedicated.includes(status); - element.appendChild(this._renderStatus(status, showSeverity, isPinned, this._renderDisposables)); - ariaLabels.push(LanguageStatus._accessibilityInformation(status).label); - isOneBusy = isOneBusy || (!isPinned && status.busy); // unpinned items contribute to the busy-indicator of the composite status item - } - - const props: IStatusbarEntry = { - name: localize('langStatus.name', "Editor Language Status"), - ariaLabel: localize('langStatus.aria', "Editor Language Status: {0}", ariaLabels.join(', next: ')), - tooltip: element, - command: ShowTooltipCommand, - text: computeText(text, isOneBusy), - }; - if (!this._combinedEntry) { - this._combinedEntry = this._statusBarService.addEntry(props, LanguageStatus._id, StatusbarAlignment.RIGHT, { id: 'status.editor.mode', alignment: StatusbarAlignment.LEFT, compact: true }); - } else { - this._combinedEntry.update(props); - } - - // animate the status bar icon whenever language status changes, repeat animation - // when severity is warning or error, don't show animation when showing progress/busy - const userHasInteractedWithStatus = this._interactionCounter.value >= 3; - const targetWindow = dom.getWindow(editor?.getContainerDomNode()); - const node = targetWindow.document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus A>SPAN.codicon'); - const container = targetWindow.document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus'); - if (dom.isHTMLElement(node) && container) { - const _wiggle = 'wiggle'; - const _flash = 'flash'; - if (!isOneBusy) { - // wiggle icon when severe or "new" - node.classList.toggle(_wiggle, showSeverity || !userHasInteractedWithStatus); - this._renderDisposables.add(dom.addDisposableListener(node, 'animationend', _e => node.classList.remove(_wiggle))); - // flash background when severe - container.classList.toggle(_flash, showSeverity); - this._renderDisposables.add(dom.addDisposableListener(container, 'animationend', _e => container.classList.remove(_flash))); - } else { - node.classList.remove(_wiggle); - container.classList.remove(_flash); - } - } - - // track when the hover shows (this is automagic and DOM mutation spying is needed...) - // use that as signal that the user has interacted/learned language status items work - if (!userHasInteractedWithStatus) { - const hoverTarget = targetWindow.document.querySelector('.monaco-workbench .context-view'); - if (dom.isHTMLElement(hoverTarget)) { - const observer = new MutationObserver(() => { - if (targetWindow.document.contains(element)) { - this._interactionCounter.increment(); - observer.disconnect(); - } - }); - observer.observe(hoverTarget, { childList: true, subtree: true }); - this._renderDisposables.add(toDisposable(() => observer.disconnect())); - } - } - } - - // dedicated status bar items are shows as-is in the status bar - const newDedicatedEntries = new Map(); - for (const status of model.dedicated) { - const props = LanguageStatus._asStatusbarEntry(status); - let entry = this._dedicatedEntries.get(status.id); - if (!entry) { - entry = this._statusBarService.addEntry(props, status.id, StatusbarAlignment.RIGHT, { id: 'status.editor.mode', alignment: StatusbarAlignment.RIGHT }); - } else { - entry.update(props); - this._dedicatedEntries.delete(status.id); - } - newDedicatedEntries.set(status.id, entry); - } - dispose(this._dedicatedEntries.values()); - this._dedicatedEntries = newDedicatedEntries; - } - - private _renderStatus(status: ILanguageStatus, showSeverity: boolean, isPinned: boolean, store: DisposableStore): HTMLElement { - - const parent = document.createElement('div'); - parent.classList.add('hover-language-status'); - - const severity = document.createElement('div'); - severity.classList.add('severity', `sev${status.severity}`); - severity.classList.toggle('show', showSeverity); - const severityText = LanguageStatus._severityToSingleCodicon(status.severity); - dom.append(severity, ...renderLabelWithIcons(severityText)); - parent.appendChild(severity); - - const element = document.createElement('div'); - element.classList.add('element'); - parent.appendChild(element); - - const left = document.createElement('div'); - left.classList.add('left'); - element.appendChild(left); - - const label = document.createElement('span'); - label.classList.add('label'); - const labelValue = typeof status.label === 'string' ? status.label : status.label.value; - dom.append(label, ...renderLabelWithIcons(computeText(labelValue, status.busy))); - left.appendChild(label); - - const detail = document.createElement('span'); - detail.classList.add('detail'); - this._renderTextPlus(detail, status.detail, store); - left.appendChild(detail); - - const right = document.createElement('div'); - right.classList.add('right'); - element.appendChild(right); - - // -- command (if available) - const { command } = status; - if (command) { - store.add(new Link(right, { - label: command.title, - title: command.tooltip, - href: URI.from({ - scheme: 'command', path: command.id, query: command.arguments && JSON.stringify(command.arguments) - }).toString() - }, { hoverDelegate: nativeHoverDelegate }, this._hoverService, this._openerService)); - } - - // -- pin - const actionBar = new ActionBar(right, { hoverDelegate: nativeHoverDelegate }); - const actionLabel: string = isPinned ? localize('unpin', "Remove from Status Bar") : localize('pin', "Add to Status Bar"); - actionBar.setAriaLabel(actionLabel); - store.add(actionBar); - let action: Action; - if (!isPinned) { - action = new Action('pin', actionLabel, ThemeIcon.asClassName(Codicon.pin), true, () => { - this._dedicated.add(status.id); - this._statusBarService.updateEntryVisibility(status.id, true); - this._update(); - this._storeState(); - }); - } else { - action = new Action('unpin', actionLabel, ThemeIcon.asClassName(Codicon.pinned), true, () => { - this._dedicated.delete(status.id); - this._statusBarService.updateEntryVisibility(status.id, false); - this._update(); - this._storeState(); - }); - } - actionBar.push(action, { icon: true, label: false }); - store.add(action); - - return parent; - } - - private static _severityToComboCodicon(sev: Severity): string { - switch (sev) { - case Severity.Error: return '$(bracket-error)'; - case Severity.Warning: return '$(bracket-dot)'; - default: return '$(bracket)'; - } - } - - private static _severityToSingleCodicon(sev: Severity): string { - switch (sev) { - case Severity.Error: return '$(error)'; - case Severity.Warning: return '$(info)'; - default: return '$(check)'; - } - } - - private _renderTextPlus(target: HTMLElement, text: string, store: DisposableStore): void { - for (const node of parseLinkedText(text).nodes) { - if (typeof node === 'string') { - const parts = renderLabelWithIcons(node); - dom.append(target, ...parts); - } else { - store.add(new Link(target, node, undefined, this._hoverService, this._openerService)); - } - } - } - - private static _accessibilityInformation(status: ILanguageStatus): IAccessibilityInformation { - if (status.accessibilityInfo) { - return status.accessibilityInfo; - } - const textValue = typeof status.label === 'string' ? status.label : status.label.value; - if (status.detail) { - return { label: localize('aria.1', '{0}, {1}', textValue, status.detail) }; - } else { - return { label: localize('aria.2', '{0}', textValue) }; - } - } - - // --- - - private static _asStatusbarEntry(item: ILanguageStatus): IStatusbarEntry { - - let kind: StatusbarEntryKind | undefined; - if (item.severity === Severity.Warning) { - kind = 'warning'; - } else if (item.severity === Severity.Error) { - kind = 'error'; - } - - const textValue = typeof item.label === 'string' ? item.label : item.label.shortValue; - - return { - name: localize('name.pattern', '{0} (Language Status)', item.name), - text: computeText(textValue, item.busy), - ariaLabel: LanguageStatus._accessibilityInformation(item).label, - role: item.accessibilityInfo?.role, - tooltip: item.command?.tooltip || new MarkdownString(item.detail, { isTrusted: true, supportThemeIcons: true }), - kind, - command: item.command - }; - } -} - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LanguageStatusContribution, LifecyclePhase.Restored); - -registerAction2(class extends Action2 { - - constructor() { - super({ - id: 'editor.inlayHints.Reset', - title: localize2('reset', "Reset Language Status Interaction Counter"), - category: Categories.View, - f1: true - }); - } - - run(accessor: ServicesAccessor): void { - accessor.get(IStorageService).remove('languageStatus.interactCount', StorageScope.PROFILE); - } -}); - -function computeText(text: string, loading: boolean): string { - return joinStrings([text !== '' && text, loading && '$(loading~spin)'], '\u00A0\u00A0'); -} +registerWorkbenchContribution2(LanguageStatusContribution.Id, LanguageStatusContribution, WorkbenchPhase.AfterRestored); +registerAction2(ResetAction); diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.ts new file mode 100644 index 0000000000000..fa52ec0c810eb --- /dev/null +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.ts @@ -0,0 +1,442 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './media/languageStatus.css'; +import * as dom from '../../../../base/browser/dom.js'; +import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { Disposable, DisposableStore, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; +import Severity from '../../../../base/common/severity.js'; +import { getCodeEditor, ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { ILanguageStatus, ILanguageStatusService } from '../../../services/languageStatus/common/languageStatusService.js'; +import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, ShowTooltipCommand, StatusbarAlignment, StatusbarEntryKind } from '../../../services/statusbar/browser/statusbar.js'; +import { parseLinkedText } from '../../../../base/common/linkedText.js'; +import { Link } from '../../../../platform/opener/browser/link.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { MarkdownString } from '../../../../base/common/htmlContent.js'; +import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { Action } from '../../../../base/common/actions.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { equals } from '../../../../base/common/arrays.js'; +import { URI } from '../../../../base/common/uri.js'; +import { Action2 } from '../../../../platform/actions/common/actions.js'; +import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; +import { IAccessibilityInformation } from '../../../../platform/accessibility/common/accessibility.js'; +import { IEditorGroupsService, IEditorPart } from '../../../services/editor/common/editorGroupsService.js'; +import { IHoverService, nativeHoverDelegate } from '../../../../platform/hover/browser/hover.js'; +import { Event } from '../../../../base/common/event.js'; + +class LanguageStatusViewModel { + + constructor( + readonly combined: readonly ILanguageStatus[], + readonly dedicated: readonly ILanguageStatus[] + ) { } + + isEqual(other: LanguageStatusViewModel) { + return equals(this.combined, other.combined) && equals(this.dedicated, other.dedicated); + } +} + +class StoredCounter { + + constructor(@IStorageService private readonly _storageService: IStorageService, private readonly _key: string) { } + + get value() { + return this._storageService.getNumber(this._key, StorageScope.PROFILE, 0); + } + + increment(): number { + const n = this.value + 1; + this._storageService.store(this._key, n, StorageScope.PROFILE, StorageTarget.MACHINE); + return n; + } +} + +export class LanguageStatusContribution extends Disposable implements IWorkbenchContribution { + + static readonly Id = 'status.languageStatus'; + + constructor( + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + ) { + super(); + + for (const part of editorGroupService.parts) { + this.createLanguageStatus(part); + } + + this._register(editorGroupService.onDidCreateAuxiliaryEditorPart(part => this.createLanguageStatus(part))); + } + + private createLanguageStatus(part: IEditorPart): void { + const disposables = new DisposableStore(); + Event.once(part.onWillDispose)(() => disposables.dispose()); + + const scopedInstantiationService = this.editorGroupService.getScopedInstantiationService(part); + disposables.add(scopedInstantiationService.createInstance(LanguageStatus)); + } +} + +class LanguageStatus { + + private static readonly _id = 'status.languageStatus'; + + private static readonly _keyDedicatedItems = 'languageStatus.dedicated'; + + private readonly _disposables = new DisposableStore(); + private readonly _interactionCounter: StoredCounter; + + private _dedicated = new Set(); + + private _model?: LanguageStatusViewModel; + private _combinedEntry?: IStatusbarEntryAccessor; + private _dedicatedEntries = new Map(); + private readonly _renderDisposables = new DisposableStore(); + + constructor( + @ILanguageStatusService private readonly _languageStatusService: ILanguageStatusService, + @IStatusbarService private readonly _statusBarService: IStatusbarService, + @IEditorService private readonly _editorService: IEditorService, + @IHoverService private readonly _hoverService: IHoverService, + @IOpenerService private readonly _openerService: IOpenerService, + @IStorageService private readonly _storageService: IStorageService, + ) { + _storageService.onDidChangeValue(StorageScope.PROFILE, LanguageStatus._keyDedicatedItems, this._disposables)(this._handleStorageChange, this, this._disposables); + this._restoreState(); + this._interactionCounter = new StoredCounter(_storageService, 'languageStatus.interactCount'); + + _languageStatusService.onDidChange(this._update, this, this._disposables); + _editorService.onDidActiveEditorChange(this._update, this, this._disposables); + this._update(); + + _statusBarService.onDidChangeEntryVisibility(e => { + if (!e.visible && this._dedicated.has(e.id)) { + this._dedicated.delete(e.id); + this._update(); + this._storeState(); + } + }, undefined, this._disposables); + + } + + dispose(): void { + this._disposables.dispose(); + this._combinedEntry?.dispose(); + dispose(this._dedicatedEntries.values()); + this._renderDisposables.dispose(); + } + + // --- persisting dedicated items + + private _handleStorageChange() { + this._restoreState(); + this._update(); + } + + private _restoreState(): void { + const raw = this._storageService.get(LanguageStatus._keyDedicatedItems, StorageScope.PROFILE, '[]'); + try { + const ids = JSON.parse(raw); + this._dedicated = new Set(ids); + } catch { + this._dedicated.clear(); + } + } + + private _storeState(): void { + if (this._dedicated.size === 0) { + this._storageService.remove(LanguageStatus._keyDedicatedItems, StorageScope.PROFILE); + } else { + const raw = JSON.stringify(Array.from(this._dedicated.keys())); + this._storageService.store(LanguageStatus._keyDedicatedItems, raw, StorageScope.PROFILE, StorageTarget.USER); + } + } + + // --- language status model and UI + + private _createViewModel(editor: ICodeEditor | null): LanguageStatusViewModel { + if (!editor?.hasModel()) { + return new LanguageStatusViewModel([], []); + } + const all = this._languageStatusService.getLanguageStatus(editor.getModel()); + const combined: ILanguageStatus[] = []; + const dedicated: ILanguageStatus[] = []; + for (const item of all) { + if (this._dedicated.has(item.id)) { + dedicated.push(item); + } + combined.push(item); + } + return new LanguageStatusViewModel(combined, dedicated); + } + + private _update(): void { + const editor = getCodeEditor(this._editorService.activeTextEditorControl); + const model = this._createViewModel(editor); + + if (this._model?.isEqual(model)) { + return; + } + this._renderDisposables.clear(); + + this._model = model; + + // update when editor language changes + editor?.onDidChangeModelLanguage(this._update, this, this._renderDisposables); + + // combined status bar item is a single item which hover shows + // each status item + if (model.combined.length === 0) { + // nothing + this._combinedEntry?.dispose(); + this._combinedEntry = undefined; + + } else { + const [first] = model.combined; + const showSeverity = first.severity >= Severity.Warning; + const text = LanguageStatus._severityToComboCodicon(first.severity); + + let isOneBusy = false; + const ariaLabels: string[] = []; + const element = document.createElement('div'); + for (const status of model.combined) { + const isPinned = model.dedicated.includes(status); + element.appendChild(this._renderStatus(status, showSeverity, isPinned, this._renderDisposables)); + ariaLabels.push(LanguageStatus._accessibilityInformation(status).label); + isOneBusy = isOneBusy || (!isPinned && status.busy); // unpinned items contribute to the busy-indicator of the composite status item + } + + const props: IStatusbarEntry = { + name: localize('langStatus.name', "Editor Language Status"), + ariaLabel: localize('langStatus.aria', "Editor Language Status: {0}", ariaLabels.join(', next: ')), + tooltip: element, + command: ShowTooltipCommand, + text: computeText(text, isOneBusy), + }; + if (!this._combinedEntry) { + this._combinedEntry = this._statusBarService.addEntry(props, LanguageStatus._id, StatusbarAlignment.RIGHT, { id: 'status.editor.mode', alignment: StatusbarAlignment.LEFT, compact: true }); + } else { + this._combinedEntry.update(props); + } + + // animate the status bar icon whenever language status changes, repeat animation + // when severity is warning or error, don't show animation when showing progress/busy + const userHasInteractedWithStatus = this._interactionCounter.value >= 3; + const targetWindow = dom.getWindow(editor?.getContainerDomNode()); + const node = targetWindow.document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus A>SPAN.codicon'); + const container = targetWindow.document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus'); + if (dom.isHTMLElement(node) && container) { + const _wiggle = 'wiggle'; + const _flash = 'flash'; + if (!isOneBusy) { + // wiggle icon when severe or "new" + node.classList.toggle(_wiggle, showSeverity || !userHasInteractedWithStatus); + this._renderDisposables.add(dom.addDisposableListener(node, 'animationend', _e => node.classList.remove(_wiggle))); + // flash background when severe + container.classList.toggle(_flash, showSeverity); + this._renderDisposables.add(dom.addDisposableListener(container, 'animationend', _e => container.classList.remove(_flash))); + } else { + node.classList.remove(_wiggle); + container.classList.remove(_flash); + } + } + + // track when the hover shows (this is automagic and DOM mutation spying is needed...) + // use that as signal that the user has interacted/learned language status items work + if (!userHasInteractedWithStatus) { + const hoverTarget = targetWindow.document.querySelector('.monaco-workbench .context-view'); + if (dom.isHTMLElement(hoverTarget)) { + const observer = new MutationObserver(() => { + if (targetWindow.document.contains(element)) { + this._interactionCounter.increment(); + observer.disconnect(); + } + }); + observer.observe(hoverTarget, { childList: true, subtree: true }); + this._renderDisposables.add(toDisposable(() => observer.disconnect())); + } + } + } + + // dedicated status bar items are shows as-is in the status bar + const newDedicatedEntries = new Map(); + for (const status of model.dedicated) { + const props = LanguageStatus._asStatusbarEntry(status); + let entry = this._dedicatedEntries.get(status.id); + if (!entry) { + entry = this._statusBarService.addEntry(props, status.id, StatusbarAlignment.RIGHT, { id: 'status.editor.mode', alignment: StatusbarAlignment.RIGHT }); + } else { + entry.update(props); + this._dedicatedEntries.delete(status.id); + } + newDedicatedEntries.set(status.id, entry); + } + dispose(this._dedicatedEntries.values()); + this._dedicatedEntries = newDedicatedEntries; + } + + private _renderStatus(status: ILanguageStatus, showSeverity: boolean, isPinned: boolean, store: DisposableStore): HTMLElement { + + const parent = document.createElement('div'); + parent.classList.add('hover-language-status'); + + const severity = document.createElement('div'); + severity.classList.add('severity', `sev${status.severity}`); + severity.classList.toggle('show', showSeverity); + const severityText = LanguageStatus._severityToSingleCodicon(status.severity); + dom.append(severity, ...renderLabelWithIcons(severityText)); + parent.appendChild(severity); + + const element = document.createElement('div'); + element.classList.add('element'); + parent.appendChild(element); + + const left = document.createElement('div'); + left.classList.add('left'); + element.appendChild(left); + + const label = document.createElement('span'); + label.classList.add('label'); + const labelValue = typeof status.label === 'string' ? status.label : status.label.value; + dom.append(label, ...renderLabelWithIcons(computeText(labelValue, status.busy))); + left.appendChild(label); + + const detail = document.createElement('span'); + detail.classList.add('detail'); + this._renderTextPlus(detail, status.detail, store); + left.appendChild(detail); + + const right = document.createElement('div'); + right.classList.add('right'); + element.appendChild(right); + + // -- command (if available) + const { command } = status; + if (command) { + store.add(new Link(right, { + label: command.title, + title: command.tooltip, + href: URI.from({ + scheme: 'command', path: command.id, query: command.arguments && JSON.stringify(command.arguments) + }).toString() + }, { hoverDelegate: nativeHoverDelegate }, this._hoverService, this._openerService)); + } + + // -- pin + const actionBar = new ActionBar(right, { hoverDelegate: nativeHoverDelegate }); + const actionLabel: string = isPinned ? localize('unpin', "Remove from Status Bar") : localize('pin', "Add to Status Bar"); + actionBar.setAriaLabel(actionLabel); + store.add(actionBar); + let action: Action; + if (!isPinned) { + action = new Action('pin', actionLabel, ThemeIcon.asClassName(Codicon.pin), true, () => { + this._dedicated.add(status.id); + this._statusBarService.updateEntryVisibility(status.id, true); + this._update(); + this._storeState(); + }); + } else { + action = new Action('unpin', actionLabel, ThemeIcon.asClassName(Codicon.pinned), true, () => { + this._dedicated.delete(status.id); + this._statusBarService.updateEntryVisibility(status.id, false); + this._update(); + this._storeState(); + }); + } + actionBar.push(action, { icon: true, label: false }); + store.add(action); + + return parent; + } + + private static _severityToComboCodicon(sev: Severity): string { + switch (sev) { + case Severity.Error: return '$(bracket-error)'; + case Severity.Warning: return '$(bracket-dot)'; + default: return '$(bracket)'; + } + } + + private static _severityToSingleCodicon(sev: Severity): string { + switch (sev) { + case Severity.Error: return '$(error)'; + case Severity.Warning: return '$(info)'; + default: return '$(check)'; + } + } + + private _renderTextPlus(target: HTMLElement, text: string, store: DisposableStore): void { + for (const node of parseLinkedText(text).nodes) { + if (typeof node === 'string') { + const parts = renderLabelWithIcons(node); + dom.append(target, ...parts); + } else { + store.add(new Link(target, node, undefined, this._hoverService, this._openerService)); + } + } + } + + private static _accessibilityInformation(status: ILanguageStatus): IAccessibilityInformation { + if (status.accessibilityInfo) { + return status.accessibilityInfo; + } + const textValue = typeof status.label === 'string' ? status.label : status.label.value; + if (status.detail) { + return { label: localize('aria.1', '{0}, {1}', textValue, status.detail) }; + } else { + return { label: localize('aria.2', '{0}', textValue) }; + } + } + + // --- + + private static _asStatusbarEntry(item: ILanguageStatus): IStatusbarEntry { + + let kind: StatusbarEntryKind | undefined; + if (item.severity === Severity.Warning) { + kind = 'warning'; + } else if (item.severity === Severity.Error) { + kind = 'error'; + } + + const textValue = typeof item.label === 'string' ? item.label : item.label.shortValue; + + return { + name: localize('name.pattern', '{0} (Language Status)', item.name), + text: computeText(textValue, item.busy), + ariaLabel: LanguageStatus._accessibilityInformation(item).label, + role: item.accessibilityInfo?.role, + tooltip: item.command?.tooltip || new MarkdownString(item.detail, { isTrusted: true, supportThemeIcons: true }), + kind, + command: item.command + }; + } +} + +export class ResetAction extends Action2 { + + constructor() { + super({ + id: 'editor.inlayHints.Reset', + title: localize2('reset', "Reset Language Status Interaction Counter"), + category: Categories.View, + f1: true + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IStorageService).remove('languageStatus.interactCount', StorageScope.PROFILE); + } +} + +function computeText(text: string, loading: boolean): string { + return loading ? '$(loading~spin)' : text; +} From a14300ec9f7d04fbbaa9f896d46626110baa2b90 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 28 Oct 2024 11:42:14 +0100 Subject: [PATCH 014/555] move signature verification to shared process (#232402) --- src/vs/code/electron-main/app.ts | 13 ------------- .../sharedProcess/sharedProcessMain.ts | 9 +-------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 603edccc2c572..dd1e90bc65b3a 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -119,7 +119,6 @@ import { IAuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/ele import { AuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.js'; import { normalizeNFC } from '../../base/common/normalization.js'; import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js'; -import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from '../../platform/extensionManagement/node/extensionSignatureVerificationService.js'; /** * The main VS Code application. There will only ever be one instance, @@ -1115,11 +1114,6 @@ export class CodeApplication extends Disposable { // Dev Only: CSS service (for ESM) services.set(ICSSDevelopmentService, new SyncDescriptor(CSSDevelopmentService, undefined, true)); - if (this.productService.quality !== 'stable') { - // extensions signature verification service - services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService, undefined, true)); - } - // Init services that require it await Promises.settled([ backupMainService.initialize(), @@ -1161,13 +1155,6 @@ export class CodeApplication extends Disposable { mainProcessElectronServer.registerChannel('userDataProfiles', userDataProfilesService); sharedProcessClient.then(client => client.registerChannel('userDataProfiles', userDataProfilesService)); - if (this.productService.quality !== 'stable') { - // Extension signature verification service - const extensionSignatureVerificationService = accessor.get(IExtensionSignatureVerificationService); - sharedProcessClient.then(client => client.registerChannel('signatureVerificationService', - ProxyChannel.fromService(extensionSignatureVerificationService, disposables))); - } - // Update const updateChannel = new UpdateChannel(accessor.get(IUpdateService)); mainProcessElectronServer.registerChannel('update', updateChannel); diff --git a/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts index 80e8fdab8104c..2a99598a73de7 100644 --- a/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts @@ -326,14 +326,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { // Extension Management services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true)); - - if (productService.quality === 'stable') { - services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService, undefined, true)); - } else { - // Do extension signature verification in the main process in insiders - services.set(IExtensionSignatureVerificationService, ProxyChannel.toService(mainProcessService.getChannel('signatureVerificationService'))); - } - + services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService, undefined, true)); services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService, undefined, true)); // Extension Gallery From d29233066e5108f2a0d42ae5b59aa06fff0b860c Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 28 Oct 2024 21:04:00 +0900 Subject: [PATCH 015/555] fix: deb package dependency of libgdk-pixbuf (#232397) --- build/linux/debian/calculate-deps.js | 8 +++++++- build/linux/debian/calculate-deps.ts | 8 +++++++- build/linux/debian/dep-lists.js | 3 --- build/linux/debian/dep-lists.ts | 3 --- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/build/linux/debian/calculate-deps.js b/build/linux/debian/calculate-deps.js index bbcb6bfc3de47..57934e65799f5 100644 --- a/build/linux/debian/calculate-deps.js +++ b/build/linux/debian/calculate-deps.js @@ -72,13 +72,19 @@ function calculatePackageDeps(binaryPath, arch, chromiumSysroot, vscodeSysroot) // libgcc-s1 is a dependency of libc6. This hack can be removed once // support for Debian Buster and Ubuntu Bionic are dropped. // + // libgdk-pixbuf package has been renamed from libgdk-pixbuf2.0-0 to + // libgdk-pixbuf-2.0-0 in recent distros. Since we only ship a single + // linux package we cannot declare a dependeny on it. We can safely + // exclude this dependency as GTK depends on it and we depend on GTK. + // // Remove kerberos native module related dependencies as the versions // computed from sysroot will not satisfy the minimum supported distros // Refs https://github.com/microsoft/vscode/issues/188881. // TODO(deepak1556): remove this workaround in favor of computing the // versions from build container for native modules. const filteredDeps = depsStr.split(', ').filter(dependency => { - return !dependency.startsWith('libgcc-s1'); + return !dependency.startsWith('libgcc-s1') && + !dependency.startsWith('libgdk-pixbuf'); }).sort(); const requires = new Set(filteredDeps); return requires; diff --git a/build/linux/debian/calculate-deps.ts b/build/linux/debian/calculate-deps.ts index 92f8065f26293..c44e241388bbe 100644 --- a/build/linux/debian/calculate-deps.ts +++ b/build/linux/debian/calculate-deps.ts @@ -84,13 +84,19 @@ function calculatePackageDeps(binaryPath: string, arch: DebianArchString, chromi // libgcc-s1 is a dependency of libc6. This hack can be removed once // support for Debian Buster and Ubuntu Bionic are dropped. // + // libgdk-pixbuf package has been renamed from libgdk-pixbuf2.0-0 to + // libgdk-pixbuf-2.0-0 in recent distros. Since we only ship a single + // linux package we cannot declare a dependeny on it. We can safely + // exclude this dependency as GTK depends on it and we depend on GTK. + // // Remove kerberos native module related dependencies as the versions // computed from sysroot will not satisfy the minimum supported distros // Refs https://github.com/microsoft/vscode/issues/188881. // TODO(deepak1556): remove this workaround in favor of computing the // versions from build container for native modules. const filteredDeps = depsStr.split(', ').filter(dependency => { - return !dependency.startsWith('libgcc-s1'); + return !dependency.startsWith('libgcc-s1') && + !dependency.startsWith('libgdk-pixbuf'); }).sort(); const requires = new Set(filteredDeps); return requires; diff --git a/build/linux/debian/dep-lists.js b/build/linux/debian/dep-lists.js index 4ee215b225864..3bb58fb1215d7 100644 --- a/build/linux/debian/dep-lists.js +++ b/build/linux/debian/dep-lists.js @@ -39,7 +39,6 @@ exports.referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', @@ -77,7 +76,6 @@ exports.referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', @@ -116,7 +114,6 @@ exports.referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', diff --git a/build/linux/debian/dep-lists.ts b/build/linux/debian/dep-lists.ts index 6c8f053d26b3e..e3d78d1139ad5 100644 --- a/build/linux/debian/dep-lists.ts +++ b/build/linux/debian/dep-lists.ts @@ -39,7 +39,6 @@ export const referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', @@ -77,7 +76,6 @@ export const referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', @@ -116,7 +114,6 @@ export const referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', From 3a0028d2f3ea392476685fa88a56f79826e4dd28 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 28 Oct 2024 13:10:00 +0100 Subject: [PATCH 016/555] Unable to disable/remap Escape key closing CommentThreadWidget (#232407) Fixes #231674 --- .../comments/browser/commentThreadHeader.ts | 3 ++- .../comments/browser/commentThreadWidget.ts | 12 +++------- .../browser/commentThreadZoneWidget.ts | 4 ++++ .../comments/browser/commentsController.ts | 8 ++----- .../browser/commentsEditorContribution.ts | 23 ++++++++++++++++++- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts b/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts index c76c842f122ff..3f42e0cdf323f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts @@ -23,6 +23,7 @@ import { IContextMenuService } from '../../../../platform/contextview/browser/co import { MarshalledId } from '../../../../base/common/marshallingIds.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { MarshalledCommentThread } from '../../../common/comments.js'; +import { CommentCommandId } from '../common/commentCommandIds.js'; const collapseIcon = registerIcon('review-comment-collapse', Codicon.chevronUp, nls.localize('collapseIcon', 'Icon to collapse a review comment.')); const COLLAPSE_ACTION_CLASS = 'expand-review-action ' + ThemeIcon.asClassName(collapseIcon); @@ -68,7 +69,7 @@ export class CommentThreadHeader extends Disposable { this._register(this._actionbarWidget); const collapseClass = threadHasComments(this._commentThread.comments) ? COLLAPSE_ACTION_CLASS : DELETE_ACTION_CLASS; - this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), collapseClass, true, () => this._delegate.collapse()); + this._collapseAction = new Action(CommentCommandId.Hide, nls.localize('label.collapse', "Collapse"), collapseClass, true, () => this._delegate.collapse()); if (!threadHasComments(this._commentThread.comments)) { const commentsChanged: MutableDisposable = this._register(new MutableDisposable()); commentsChanged.value = this._commentThread.onDidChangeComments(() => { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index ebad6e3f24bde..7bac752e389aa 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -36,7 +36,6 @@ import { AccessibilityVerbositySettingId } from '../../accessibility/browser/acc import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; import { LayoutableEditor } from './simpleCommentEditor.js'; -import { DomEmitter } from '../../../../base/browser/event.js'; import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; @@ -157,14 +156,6 @@ export class CommentThreadWidget extends } this.currentThreadListeners(); - this._register(new DomEmitter(this.container, 'keydown').event(e => { - if (dom.isKeyboardEvent(e) && e.key === 'Escape') { - if (Range.isIRange(this.commentThread.range) && isCodeEditor(this._parentEditor)) { - this._parentEditor.setSelection(this.commentThread.range); - } - this.collapse(); - } - })); } private _setAriaLabel(): void { @@ -384,6 +375,9 @@ export class CommentThreadWidget extends } collapse() { + if (Range.isIRange(this.commentThread.range) && isCodeEditor(this._parentEditor)) { + this._parentEditor.setSelection(this.commentThread.range); + } this._containerDelegate.collapse(); } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 4ef7bbc6025c1..6790337c79aca 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -504,6 +504,10 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._refresh(this._commentThreadWidget.getDimensions()); } + collapseAndFocusRange() { + this._commentThreadWidget.collapse(); + } + override hide() { if (this._isExpanded) { this._isExpanded = false; diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index c4cb1896a5386..6fc73d49776fc 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -1388,12 +1388,8 @@ export class CommentController implements IEditorContribution { } } - public closeWidget(): void { - this._commentWidgets?.forEach(widget => widget.hide()); - if (this.editor) { - this.editor.focus(); - this.editor.revealRangeInCenter(this.editor.getSelection()!); - } + public collapseAndFocusRange(threadId: string): void { + this._commentWidgets?.find(widget => widget.commentThread.threadId === threadId)?.collapseAndFocusRange(); } private removeCommentWidgetsAndStoreCache() { diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 7574e10e00787..ce0458cd9421e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -421,11 +421,32 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.EditorContrib, primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - when: ctxCommentEditorFocused, + when: ContextKeyExpr.or(ctxCommentEditorFocused, CommentContextKeys.commentFocused), handler: (accessor, args) => { const activeCodeEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (activeCodeEditor instanceof SimpleCommentEditor) { activeCodeEditor.getParentThread().collapse(); + } else if (activeCodeEditor) { + const controller = CommentController.get(activeCodeEditor); + if (!controller) { + return; + } + const notificationService = accessor.get(INotificationService); + const commentService = accessor.get(ICommentService); + let error = false; + try { + const activeComment = commentService.lastActiveCommentcontroller?.activeComment; + if (!activeComment) { + error = true; + } else { + controller.collapseAndFocusRange(activeComment.thread.threadId); + } + } catch (e) { + error = true; + } + if (error) { + notificationService.error(nls.localize('comments.focusCommand.error', "The cursor must be on a line with a comment to focus the comment")); + } } } }); From db6dfc6e4c5fc351d800e4bb9891f2e228d8f335 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 28 Oct 2024 05:47:43 -0700 Subject: [PATCH 017/555] Fix __dirname access in server.cli.ts Fixes #230584 --- src/vs/server/node/server.cli.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index 6e2da224dcfa4..f49b5e0adaba9 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -15,6 +15,7 @@ import { createWaitMarkerFileSync } from '../../platform/environment/node/wait.j import { PipeCommand } from '../../workbench/api/node/extHostCLIServer.js'; import { hasStdinWithoutTty, getStdinFilePath, readFromStdin } from '../../platform/environment/node/stdin.js'; import { DeferredPromise } from '../../base/common/async.js'; +import { FileAccess } from '../../base/common/network.js'; const __dirname = dirname(url.fileURLToPath(import.meta.url)); @@ -150,7 +151,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise { console.error(err.message || err.stack || err); From 860914e308c250f1619c6447621f328d8e01c8b2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 28 Oct 2024 13:53:34 +0100 Subject: [PATCH 018/555] Failed to fetch (fix #232401) (#232410) --- .../chatInstallEntitlement.contribution.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts b/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts index be612959e179e..0c103eead9d18 100644 --- a/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts +++ b/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts @@ -16,6 +16,7 @@ import { IRequestService, asText } from '../../../../platform/request/common/req import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { CONTEXT_CHAT_INSTALL_ENTITLED } from './chatContextKeys.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { IRequestContext } from '../../../../base/parts/request/common/request.js'; // TODO@bpasero revisit this flow @@ -115,13 +116,18 @@ class ChatInstallEntitlementContribution extends Disposable implements IWorkbenc const cts = new CancellationTokenSource(); this._register(toDisposable(() => cts.dispose(true))); - const context = await this.requestService.request({ - type: 'GET', - url: this.productService.gitHubEntitlement!.entitlementUrl, - headers: { - 'Authorization': `Bearer ${session.accessToken}` - } - }, cts.token); + let context: IRequestContext; + try { + context = await this.requestService.request({ + type: 'GET', + url: this.productService.gitHubEntitlement!.entitlementUrl, + headers: { + 'Authorization': `Bearer ${session.accessToken}` + } + }, cts.token); + } catch (error) { + return false; + } if (context.res.statusCode && context.res.statusCode !== 200) { return false; From 65dc3932c4c938fac7b42d3541781f03b705bca3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 28 Oct 2024 14:06:46 +0100 Subject: [PATCH 019/555] replace `tail`-util with `Array.at(-1)` (#232412) --- src/vs/base/browser/touch.ts | 27 +++++++++---------- .../parts/editor/breadcrumbsControl.ts | 3 +-- .../contrib/bulkEdit/browser/bulkFileEdits.ts | 3 +-- .../inlineChat/browser/inlineChatWidget.ts | 8 +++--- .../contrib/scm/browser/scmHistoryViewPane.ts | 4 +-- .../contrib/snippets/browser/snippetsFile.ts | 3 +-- .../preferences/common/preferencesModels.ts | 4 +-- 7 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index 05e815ddd47a2..ac526347c908f 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -5,7 +5,6 @@ import * as DomUtils from './dom.js'; import { mainWindow } from './window.js'; -import * as arrays from '../common/arrays.js'; import { memoize } from '../common/decorators.js'; import { Event as EventUtils } from '../common/event.js'; import { Disposable, IDisposable, markAsSingleton, toDisposable } from '../common/lifecycle.js'; @@ -192,28 +191,28 @@ export class Gesture extends Disposable { holdTime = Date.now() - data.initialTimeStamp; if (holdTime < Gesture.HOLD_DELAY - && Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)!) < 30 - && Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)!) < 30) { + && Math.abs(data.initialPageX - data.rollingPageX.at(-1)!) < 30 + && Math.abs(data.initialPageY - data.rollingPageY.at(-1)!) < 30) { const evt = this.newGestureEvent(EventType.Tap, data.initialTarget); - evt.pageX = arrays.tail(data.rollingPageX)!; - evt.pageY = arrays.tail(data.rollingPageY)!; + evt.pageX = data.rollingPageX.at(-1)!; + evt.pageY = data.rollingPageY.at(-1)!; this.dispatchEvent(evt); } else if (holdTime >= Gesture.HOLD_DELAY - && Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)!) < 30 - && Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)!) < 30) { + && Math.abs(data.initialPageX - data.rollingPageX.at(-1)!) < 30 + && Math.abs(data.initialPageY - data.rollingPageY.at(-1)!) < 30) { const evt = this.newGestureEvent(EventType.Contextmenu, data.initialTarget); - evt.pageX = arrays.tail(data.rollingPageX)!; - evt.pageY = arrays.tail(data.rollingPageY)!; + evt.pageX = data.rollingPageX.at(-1)!; + evt.pageY = data.rollingPageY.at(-1)!; this.dispatchEvent(evt); } else if (activeTouchCount === 1) { - const finalX = arrays.tail(data.rollingPageX)!; - const finalY = arrays.tail(data.rollingPageY)!; + const finalX = data.rollingPageX.at(-1)!; + const finalY = data.rollingPageY.at(-1)!; - const deltaT = arrays.tail(data.rollingTimestamps)! - data.rollingTimestamps[0]; + const deltaT = data.rollingTimestamps.at(-1)! - data.rollingTimestamps[0]; const deltaX = finalX - data.rollingPageX[0]; const deltaY = finalY - data.rollingPageY[0]; @@ -345,8 +344,8 @@ export class Gesture extends Disposable { const data = this.activeTouches[touch.identifier]; const evt = this.newGestureEvent(EventType.Change, data.initialTarget); - evt.translationX = touch.pageX - arrays.tail(data.rollingPageX)!; - evt.translationY = touch.pageY - arrays.tail(data.rollingPageY)!; + evt.translationX = touch.pageX - data.rollingPageX.at(-1)!; + evt.translationY = touch.pageY - data.rollingPageY.at(-1)!; evt.pageX = touch.pageX; evt.pageY = touch.pageY; this.dispatchEvent(evt); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 8fa44fe2e1bfe..c976a9565e3ff 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -6,7 +6,6 @@ import * as dom from '../../../../base/browser/dom.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent, IBreadcrumbsWidgetStyles } from '../../../../base/browser/ui/breadcrumbs/breadcrumbsWidget.js'; -import { tail } from '../../../../base/common/arrays.js'; import { timeout } from '../../../../base/common/async.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { combinedDisposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; @@ -638,7 +637,7 @@ function focusAndSelectHandler(accessor: ServicesAccessor, select: boolean): voi const breadcrumbs = accessor.get(IBreadcrumbsService); const widget = breadcrumbs.getWidget(groups.activeGroup.id); if (widget) { - const item = tail(widget.getItems()); + const item = widget.getItems().at(-1); widget.setFocused(item); if (select) { widget.setSelection(item, BreadcrumbsControl.Payload_Pick); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts index c61d10b2b817f..237b768fbd424 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts @@ -16,7 +16,6 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { VSBuffer } from '../../../../base/common/buffer.js'; import { ResourceFileEdit } from '../../../../editor/browser/services/bulkEditService.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; -import { tail } from '../../../../base/common/arrays.js'; import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; import { Schemas } from '../../../../base/common/network.js'; @@ -362,7 +361,7 @@ export class BulkFileEdits { for (let i = 1; i < edits.length; i++) { const edit = edits[i]; - const lastGroup = tail(groups); + const lastGroup = groups.at(-1); if (lastGroup?.[0].type === edit.type) { lastGroup.push(edit); } else { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 9fab09b8f489e..3881a693a6936 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -8,7 +8,7 @@ import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/ac import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IAction } from '../../../../base/common/actions.js'; -import { isNonEmptyArray, tail } from '../../../../base/common/arrays.js'; +import { isNonEmptyArray } from '../../../../base/common/arrays.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; @@ -431,10 +431,10 @@ export class InlineChatWidget { return undefined; } const items = viewModel.getItems().filter(i => isResponseVM(i)); - if (!items.length) { + const item = items.at(-1); + if (!item) { return; } - const item = items[items.length - 1]; return viewModel.codeBlockModelCollection.get(viewModel.sessionId, item, codeBlockIndex)?.model; } @@ -443,7 +443,7 @@ export class InlineChatWidget { if (!isNonEmptyArray(requests)) { return undefined; } - return tail(requests)?.response?.response.toString(); + return requests.at(-1)?.response?.response.toString(); } diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 037a2e40c4d96..82b67c18261f7 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -45,7 +45,7 @@ import { Sequencer, Throttler } from '../../../../base/common/async.js'; import { URI } from '../../../../base/common/uri.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { ActionRunner, IAction, IActionRunner } from '../../../../base/common/actions.js'; -import { delta, groupBy, tail } from '../../../../base/common/arrays.js'; +import { delta, groupBy } from '../../../../base/common/arrays.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { IProgressService } from '../../../../platform/progress/common/progress.js'; import { ContextKeys } from './scmViewPane.js'; @@ -698,7 +698,7 @@ class SCMHistoryTreeDataSource extends Disposable implements IAsyncDataSource 0) { children.push({ repository, diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index 1ebc253173cbe..98829a81bf08a 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -14,7 +14,6 @@ import { ExtensionIdentifier, IExtensionDescription } from '../../../../platform import { IExtensionResourceLoaderService } from '../../../../platform/extensionResourceLoader/common/extensionResourceLoader.js'; import { relativePath } from '../../../../base/common/resources.js'; import { isObject } from '../../../../base/common/types.js'; -import { tail } from '../../../../base/common/arrays.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { WindowIdleValue, getActiveWindow } from '../../../../base/browser/dom.js'; @@ -54,7 +53,7 @@ class SnippetBodyInsights { if (textmateSnippet.placeholders.length === 0) { this.isTrivial = true; } else if (placeholderMax === 0) { - const last = tail(textmateSnippet.children); + const last = textmateSnippet.children.at(-1); this.isTrivial = last instanceof Placeholder && last.isFinalTabstop; } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index e97874375fae9..c43b666ad0926 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { tail, coalesce } from '../../../../base/common/arrays.js'; +import { coalesce } from '../../../../base/common/arrays.js'; import { IStringDictionary } from '../../../../base/common/collections.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { JSONVisitor, visit } from '../../../../base/common/json.js'; @@ -840,7 +840,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements .sort((a, b) => a.order - b.order); const nonEmptyResultGroups = resultGroups.filter(group => group.result.filterMatches.length); - const startLine = tail(this.settingsGroups)!.range.endLineNumber + 2; + const startLine = this.settingsGroups.at(-1)!.range.endLineNumber + 2; const { settingsGroups: filteredGroups, matches } = this.writeResultGroups(nonEmptyResultGroups, startLine); const metadata = this.collectMetadata(resultGroups); From 71d55cd282a3c7c139736295a8e7b36e9a834d78 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 28 Oct 2024 14:38:51 +0100 Subject: [PATCH 020/555] chore - clean up `LanguageModelChatProvider` (step 1) (#232414) * chore - clean up `LanguageModelChatProvider` (step 1) * fix compilo --- .../vscode-api-tests/src/singlefolder-tests/lm.test.ts | 4 ++-- src/vs/workbench/api/common/extHostLanguageModels.ts | 8 ++------ src/vscode-dts/vscode.proposed.chatProvider.d.ts | 3 ++- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts index 178119a1197c7..8ab5c5949a15f 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts @@ -26,7 +26,7 @@ suite('lm', function () { test('lm request and stream', async function () { - let p: vscode.Progress | undefined; + let p: vscode.Progress | undefined; const defer = new DeferredPromise(); disposables.push(vscode.lm.registerChatModelProvider('test-lm', { @@ -69,7 +69,7 @@ suite('lm', function () { assert.strictEqual(responseText, ''); assert.strictEqual(streamDone, false); - p.report({ index: 0, part: 'Hello' }); + p.report({ index: 0, part: new vscode.LanguageModelTextPart('Hello') }); defer.complete(); await pp; diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 4a4384668a37c..b8aa1aab7b4b5 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -233,15 +233,11 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } else { - const progress2 = new Progress(async fragment => { - progress.report({ index: fragment.index, part: new extHostTypes.LanguageModelTextPart(fragment.part) }); - }); - p = Promise.resolve(data.provider.provideLanguageModelResponse( messages.map(typeConvert.LanguageModelChatMessage.to), - options?.modelOptions ?? {}, + options, ExtensionIdentifier.toKey(from), - progress2, + progress, token )); } diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 3ab4fb3d3dab7..851a0f2ad036a 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -24,8 +24,9 @@ declare module 'vscode' { onDidReceiveLanguageModelResponse2?: Event<{ readonly extensionId: string; readonly participant?: string; readonly tokenCount?: number }>; - provideLanguageModelResponse(messages: LanguageModelChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + provideLanguageModelResponse(messages: LanguageModelChatMessage[], options: LanguageModelChatRequestOptions, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + /** @deprecated */ provideLanguageModelResponse2?(messages: LanguageModelChatMessage[], options: LanguageModelChatRequestOptions, extensionId: string, progress: Progress, token: CancellationToken): Thenable; provideTokenCount(text: string | LanguageModelChatMessage, token: CancellationToken): Thenable; From 7e688863a85e474177980258f2534eb63103ff20 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 28 Oct 2024 06:57:42 -0700 Subject: [PATCH 021/555] Move fish SI script out of xdg named dir Fixes #232408 --- build/gulpfile.reh.js | 2 +- build/gulpfile.vscode.js | 2 +- src/vs/code/node/cli.ts | 2 +- src/vs/platform/terminal/node/terminalEnvironment.ts | 4 ++-- src/vs/server/node/server.cli.ts | 2 +- .../fish/vendor_conf.d => }/shellIntegration.fish | 0 6 files changed, 6 insertions(+), 6 deletions(-) rename src/vs/workbench/contrib/terminal/common/scripts/{fish_xdg_data/fish/vendor_conf.d => }/shellIntegration.fish (100%) diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 53ef6f38b02cf..7ab8b138da8e2 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -72,7 +72,7 @@ const serverResourceIncludes = [ 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-profile.zsh', 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh', 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-login.zsh', - 'out-build/vs/workbench/contrib/terminal/common/scripts/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish', ]; diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index da753e9c265ce..f75608ba412a2 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -74,7 +74,7 @@ const vscodeResourceIncludes = [ 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', // Terminal shell integration - 'out-build/vs/workbench/contrib/terminal/common/scripts/fish_xdg_data/fish/vendor_conf.d/*.fish', + 'out-build/vs/workbench/contrib/terminal/common/scripts/*.fish', 'out-build/vs/workbench/contrib/terminal/common/scripts/*.ps1', 'out-build/vs/workbench/contrib/terminal/common/scripts/*.psm1', 'out-build/vs/workbench/contrib/terminal/common/scripts/*.sh', diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index d3c4d5104d8a4..e91bea07d6a83 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -109,7 +109,7 @@ export async function main(argv: string[]): Promise { // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh)"` case 'zsh': file = 'shellIntegration-rc.zsh'; break; // Usage: `string match -q "$TERM_PROGRAM" "vscode"; and . (code --locate-shell-integration-path fish)` - case 'fish': file = 'fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish'; break; + case 'fish': file = 'shellIntegration.fish'; break; default: throw new Error('Error using --locate-shell-integration-path: Invalid shell type'); } console.log(join(getAppRoot(), 'out', 'vs', 'workbench', 'contrib', 'terminal', 'common', 'scripts', file)); diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index cfedd6496b73d..bcc1dc553a18b 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -337,8 +337,8 @@ shellIntegrationArgs.set(ShellIntegrationExecutable.PwshLogin, ['-l', '-noexit', shellIntegrationArgs.set(ShellIntegrationExecutable.Zsh, ['-i']); shellIntegrationArgs.set(ShellIntegrationExecutable.ZshLogin, ['-il']); shellIntegrationArgs.set(ShellIntegrationExecutable.Bash, ['--init-file', '{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh']); -shellIntegrationArgs.set(ShellIntegrationExecutable.Fish, ['--init-command', '. {0}/out/vs/workbench/contrib/terminal/common/scripts/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish']); -shellIntegrationArgs.set(ShellIntegrationExecutable.FishLogin, ['-l', '--init-command', '. {0}/out/vs/workbench/contrib/terminal/common/scripts/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish']); +shellIntegrationArgs.set(ShellIntegrationExecutable.Fish, ['--init-command', '. {0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish']); +shellIntegrationArgs.set(ShellIntegrationExecutable.FishLogin, ['-l', '--init-command', '. {0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish']); const pwshLoginArgs = ['-login', '-l']; const shLoginArgs = ['--login', '-l']; const shInteractiveArgs = ['-i', '--interactive']; diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index 6e2da224dcfa4..5da0f1bc63373 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -147,7 +147,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise Date: Mon, 28 Oct 2024 15:35:02 +0100 Subject: [PATCH 022/555] TreeView: Maximum call stack size exceeded error (#232265) Fixes #230361 --- src/vs/base/browser/ui/tree/asyncDataTree.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 21523d1cc87ff..e1d8be5f9d107 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -25,6 +25,7 @@ import { CancellationToken, CancellationTokenSource } from '../../../common/canc import { IObjectTreeModel } from './objectTreeModel.js'; import { IContextViewProvider } from '../contextview/contextview.js'; import { FuzzyScore } from '../../../common/filters.js'; +import { splice } from '../../../common/arrays.js'; interface IAsyncDataTreeNode { element: TInput | T; @@ -1448,7 +1449,7 @@ export class AsyncDataTree implements IDisposable this.nodes.set(child.element as T, child); } - node.children.splice(0, node.children.length, ...children); + splice(node.children, 0, node.children.length, children); // TODO@joao this doesn't take filter into account if (node !== this.root && this.autoExpandSingleChildren && children.length === 1 && childrenToRefresh.length === 0) { From 0561ca03c895239131a526d8731da0e53027167c Mon Sep 17 00:00:00 2001 From: Lionel Jouin Date: Mon, 28 Oct 2024 15:54:18 +0100 Subject: [PATCH 023/555] Fix: go grammar update (#232142) (#232335) npm --prefix extensions/go run update-grammar (#232142) Signed-off-by: Lionel Jouin --- extensions/go/cgmanifest.json | 4 +- extensions/go/syntaxes/go.tmLanguage.json | 98 ++++++++++++++++++++++- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/extensions/go/cgmanifest.json b/extensions/go/cgmanifest.json index ae7228fa1aea4..5276d2824a718 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": "ce0d1d19653dc264d8e53f97cb45cc8d1689f221" + "commitHash": "32bbaebcf218fa552e8f0397401e12f6e94fa3c5" } }, "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.7" + "version": "0.7.8" } ], "version": 1 diff --git a/extensions/go/syntaxes/go.tmLanguage.json b/extensions/go/syntaxes/go.tmLanguage.json index 8ae31fdbe31c0..ed6ead03480da 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/ce0d1d19653dc264d8e53f97cb45cc8d1689f221", + "version": "https://github.com/worlpaker/go-syntax/commit/32bbaebcf218fa552e8f0397401e12f6e94fa3c5", "name": "Go", "scopeName": "source.go", "patterns": [ @@ -1370,7 +1370,53 @@ } }, { - "include": "#parameter-variable-types" + "begin": "(?:([\\w\\.\\*]+)?(\\[))", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + }, + "2": { + "name": "punctuation.definition.begin.bracket.square.go" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square.go" + } + }, + "patterns": [ + { + "include": "#generic_param_types" + } + ] + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.round.go" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.go" + } + }, + "patterns": [ + { + "include": "#function_param_types" + } + ] }, { "comment": "other types", @@ -1483,7 +1529,53 @@ } }, { - "include": "#parameter-variable-types" + "begin": "(?:([\\w\\.\\*]+)?(\\[))", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + }, + "2": { + "name": "punctuation.definition.begin.bracket.square.go" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square.go" + } + }, + "patterns": [ + { + "include": "#generic_param_types" + } + ] + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.round.go" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.go" + } + }, + "patterns": [ + { + "include": "#function_param_types" + } + ] }, { "comment": "other types", From 5295c9d541fa20a743cbc24672d6aa65ace24912 Mon Sep 17 00:00:00 2001 From: Logicer Date: Tue, 29 Oct 2024 02:59:25 +1100 Subject: [PATCH 024/555] Fix grammar in activeOnStart description (#197536) fix grammar in activeOnStart description --- .../workbench/contrib/tasks/common/problemMatcher.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index f302f2517b1c5..4f3842a5670da 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -779,9 +779,9 @@ export namespace Config { export interface IBackgroundMonitor { /** - * If set to true the watcher is in active mode when the task - * starts. This is equals of issuing a line that matches the - * beginsPattern. + * If set to true the watcher starts in active mode. This is the + * same as outputting a line that matches beginsPattern when the + * task starts. */ activeOnStart?: boolean; @@ -1323,7 +1323,7 @@ export namespace Schemas { properties: { activeOnStart: { type: 'boolean', - description: localize('ProblemMatcherSchema.background.activeOnStart', 'If set to true the background monitor is in active mode when the task starts. This is equals of issuing a line that matches the beginsPattern') + description: localize('ProblemMatcherSchema.background.activeOnStart', 'If set to true the background monitor starts in active mode. This is the same as outputting a line that matches beginsPattern when the task starts.') }, beginsPattern: { oneOf: [ @@ -1353,7 +1353,7 @@ export namespace Schemas { properties: { activeOnStart: { type: 'boolean', - description: localize('ProblemMatcherSchema.watching.activeOnStart', 'If set to true the watcher is in active mode when the task starts. This is equals of issuing a line that matches the beginPattern') + description: localize('ProblemMatcherSchema.watching.activeOnStart', 'If set to true the watcher starts in active mode. This is the same as outputting a line that matches beginsPattern when the task starts.') }, beginsPattern: { oneOf: [ From ed3722cde121776583ade4676874d335c0eabffc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 28 Oct 2024 09:50:26 -0700 Subject: [PATCH 025/555] Pick up latest TS for building VS Code (#232423) --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index bbc6e5fc43c6c..79141a89465b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -152,7 +152,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^5.7.0-dev.20241021", + "typescript": "^5.7.0-dev.20241028", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", @@ -17212,9 +17212,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.7.0-dev.20241021", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.0-dev.20241021.tgz", - "integrity": "sha512-nf5PGykGkdF2Palp0anP/jjLiqM7jdLaIyhpq1Y8bhHnClE1JR2eHXrame54dWeaX0ZMc3NF/TD59xtVhZiuMA==", + "version": "5.7.0-dev.20241028", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.0-dev.20241028.tgz", + "integrity": "sha512-2bRur4Sn9Y5JQYZjE2Nfy4W/Cf5b8ppVkyNxWludTdPN/xuAR9kk4si+51AWFo+tCJup9qymMafRqTTtdqvKhQ==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 6318e32f16716..8da10e14c030e 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^5.7.0-dev.20241021", + "typescript": "^5.7.0-dev.20241028", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", From 983f0c0d1c8d7bf9cc15ac410fde256454d08efa Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 28 Oct 2024 10:56:47 -0700 Subject: [PATCH 026/555] Remove 'unknown' from LanguageModelToolResult#constructor (#232428) Remove 'unknown' from LanguageModelToolResult#constructor (#232284) It should be on the content property but no need to be in the constructor. --- src/vs/workbench/api/common/extHostTypes.ts | 2 +- src/vscode-dts/vscode.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 6a8faefd64e1a..ddf1a67ab12eb 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4770,7 +4770,7 @@ export class LanguageModelError extends Error { } export class LanguageModelToolResult { - constructor(public content: (LanguageModelTextPart | LanguageModelPromptTsxPart | unknown)[]) { } + constructor(public content: (LanguageModelTextPart | LanguageModelPromptTsxPart)[]) { } toJSON() { return { diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index f356b973f06c3..ac9bdc7848f46 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -19897,7 +19897,7 @@ declare module 'vscode' { * Create a LanguageModelToolResult * @param content A list of tool result content parts */ - constructor(content: (LanguageModelTextPart | LanguageModelPromptTsxPart | unknown)[]); + constructor(content: (LanguageModelTextPart | LanguageModelPromptTsxPart)[]); } /** From e488ce9ac6c3bfaced72afd1bb2834f12ca8c1fc Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 28 Oct 2024 10:57:06 -0700 Subject: [PATCH 027/555] Ensure element is not rendered to 0px when updating height async (#232427) Ensure element is not rendered to 0px when updating height async (#232281) This can happen because templateParts are now not disposed when an element is unregistered, but we have to keep their listeners around because they are reused on the next render. A more complete non-candidate fix might be to check whether the element is in the DOM before rendering it at all. But the safe fix here is to just make sure it doesn't go to 0, so that the list doesn't skip rendering it entirely. Fix microsoft/vscode-copilot#9260 --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index c0463254b731c..f0bf629d7203a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -578,7 +578,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Mon, 28 Oct 2024 11:03:20 -0700 Subject: [PATCH 028/555] Remove backcompat workaround (#232429) Remove backcompat workaround (#232302) From https://github.com/microsoft/vscode/pull/232023 Fix #232301 --- src/vs/workbench/api/common/extHostTypes.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index ddf1a67ab12eb..72ecbbd8d926a 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4663,14 +4663,8 @@ export class LanguageModelToolCallPart implements vscode.LanguageModelToolCallPa parameters: any; constructor(callId: string, name: string, input: any) { - // TODO TEMP- swapped the order of these two arguments, trying to preserve the behavior for a build or two - if (name.startsWith('call_')) { - this.name = callId; - this.callId = name; - } else { - this.callId = callId; - this.name = name; - } + this.callId = callId; + this.name = name; this.input = input; // TODO@API backwards compat, remove From b3e8a4c9ef60243cde456631b0cd9d998e538f3b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:09:16 -0700 Subject: [PATCH 029/555] Add 200 debounce time for mouse to find hover before hiding it Fixes #228121 --- .../browser/services/hoverService/hoverWidget.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index b46758c352f93..17ac7723a45ef 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -641,8 +641,15 @@ class CompositeMouseTracker extends Widget { get isMouseIn(): boolean { return this._isMouseIn; } + /** + * @param _elements The target elements to track mouse in/out events on. + * @param _eventDebounceDelay The delay in ms to debounce the event firing. This is used to + * allow a short period for the mouse to move into the hover or a nearby target element. For + * example hovering a scroll bar will not hide the hover immediately. + */ constructor( - private _elements: HTMLElement[] + private _elements: HTMLElement[], + private _eventDebounceDelay: number = 200 ) { super(); this._elements.forEach(n => this.onmouseover(n, () => this._onTargetMouseOver(n))); @@ -663,7 +670,7 @@ class CompositeMouseTracker extends Widget { this._clearEvaluateMouseStateTimeout(target); // Evaluate whether the mouse is still outside asynchronously such that other mouse targets // have the opportunity to first their mouse in event. - this._mouseTimeout = dom.getWindow(target).setTimeout(() => this._fireIfMouseOutside(), 0); + this._mouseTimeout = dom.getWindow(target).setTimeout(() => this._fireIfMouseOutside(), this._eventDebounceDelay); } private _clearEvaluateMouseStateTimeout(target: HTMLElement): void { From 4679606b3049fe81ac1a4c2030d04b383496fd82 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 28 Oct 2024 11:12:01 -0700 Subject: [PATCH 030/555] Don't try watching non writable file systems (#232433) For ts and markdown, skip watching on file systems that are not writable as these files should not change --- .../src/client/fileWatchingManager.ts | 17 +++++++++++------ .../src/tsServer/fileWatchingManager.ts | 17 +++++++++++------ .../src/utils/dispose.ts | 18 +++++++++++++++--- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/extensions/markdown-language-features/src/client/fileWatchingManager.ts b/extensions/markdown-language-features/src/client/fileWatchingManager.ts index dabb8f1fc880b..e2010edda8a45 100644 --- a/extensions/markdown-language-features/src/client/fileWatchingManager.ts +++ b/extensions/markdown-language-features/src/client/fileWatchingManager.ts @@ -11,7 +11,7 @@ import { Schemes } from '../util/schemes'; type DirWatcherEntry = { readonly uri: vscode.Uri; - readonly listeners: IDisposable[]; + readonly disposables: readonly IDisposable[]; }; @@ -28,6 +28,11 @@ export class FileWatcherManager { }>(); create(id: number, uri: vscode.Uri, watchParentDirs: boolean, listeners: { create?: () => void; change?: () => void; delete?: () => void }): void { + // Non-writable file systems do not support file watching + if (!vscode.workspace.fs.isWritableFileSystem(uri.scheme)) { + return; + } + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*'), !listeners.create, !listeners.change, !listeners.delete); const parentDirWatchers: DirWatcherEntry[] = []; this._fileWatchers.set(id, { watcher, dirWatchers: parentDirWatchers }); @@ -39,7 +44,7 @@ export class FileWatcherManager { if (watchParentDirs && uri.scheme !== Schemes.untitled) { // We need to watch the parent directories too for when these are deleted / created for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) { - const dirWatcher: DirWatcherEntry = { uri: dirUri, listeners: [] }; + const disposables: IDisposable[] = []; let parentDirWatcher = this._dirWatchers.get(dirUri); if (!parentDirWatcher) { @@ -51,7 +56,7 @@ export class FileWatcherManager { parentDirWatcher.refCount++; if (listeners.create) { - dirWatcher.listeners.push(parentDirWatcher.watcher.onDidCreate(async () => { + disposables.push(parentDirWatcher.watcher.onDidCreate(async () => { // Just because the parent dir was created doesn't mean our file was created try { const stat = await vscode.workspace.fs.stat(uri); @@ -67,10 +72,10 @@ export class FileWatcherManager { if (listeners.delete) { // When the parent dir is deleted, consider our file deleted too // TODO: this fires if the file previously did not exist and then the parent is deleted - dirWatcher.listeners.push(parentDirWatcher.watcher.onDidDelete(listeners.delete)); + disposables.push(parentDirWatcher.watcher.onDidDelete(listeners.delete)); } - parentDirWatchers.push(dirWatcher); + parentDirWatchers.push({ uri: dirUri, disposables }); } } } @@ -79,7 +84,7 @@ export class FileWatcherManager { const entry = this._fileWatchers.get(id); if (entry) { for (const dirWatcher of entry.dirWatchers) { - disposeAll(dirWatcher.listeners); + disposeAll(dirWatcher.disposables); const dirWatcherEntry = this._dirWatchers.get(dirWatcher.uri); if (dirWatcherEntry) { diff --git a/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts b/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts index 27b9ec3d7f6c6..847584a300cb8 100644 --- a/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts +++ b/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts @@ -12,7 +12,7 @@ import { ResourceMap } from '../utils/resourceMap'; interface DirWatcherEntry { readonly uri: vscode.Uri; - readonly listeners: IDisposable[]; + readonly disposables: readonly IDisposable[]; } @@ -49,6 +49,11 @@ export class FileWatcherManager implements IDisposable { create(id: number, uri: vscode.Uri, watchParentDirs: boolean, isRecursive: boolean, listeners: { create?: (uri: vscode.Uri) => void; change?: (uri: vscode.Uri) => void; delete?: (uri: vscode.Uri) => void }): void { this.logger.trace(`Creating file watcher for ${uri.toString()}`); + // Non-writable file systems do not support file watching + if (!vscode.workspace.fs.isWritableFileSystem(uri.scheme)) { + return; + } + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, isRecursive ? '**' : '*'), !listeners.create, !listeners.change, !listeners.delete); const parentDirWatchers: DirWatcherEntry[] = []; this._fileWatchers.set(id, { uri, watcher, dirWatchers: parentDirWatchers }); @@ -60,7 +65,7 @@ export class FileWatcherManager implements IDisposable { if (watchParentDirs && uri.scheme !== Schemes.untitled) { // We need to watch the parent directories too for when these are deleted / created for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) { - const dirWatcher: DirWatcherEntry = { uri: dirUri, listeners: [] }; + const disposables: IDisposable[] = []; let parentDirWatcher = this._dirWatchers.get(dirUri); if (!parentDirWatcher) { @@ -73,7 +78,7 @@ export class FileWatcherManager implements IDisposable { parentDirWatcher.refCount++; if (listeners.create) { - dirWatcher.listeners.push(parentDirWatcher.watcher.onDidCreate(async () => { + disposables.push(parentDirWatcher.watcher.onDidCreate(async () => { // Just because the parent dir was created doesn't mean our file was created try { const stat = await vscode.workspace.fs.stat(uri); @@ -89,10 +94,10 @@ export class FileWatcherManager implements IDisposable { if (listeners.delete) { // When the parent dir is deleted, consider our file deleted too // TODO: this fires if the file previously did not exist and then the parent is deleted - dirWatcher.listeners.push(parentDirWatcher.watcher.onDidDelete(listeners.delete)); + disposables.push(parentDirWatcher.watcher.onDidDelete(listeners.delete)); } - parentDirWatchers.push(dirWatcher); + parentDirWatchers.push({ uri: dirUri, disposables }); } } } @@ -104,7 +109,7 @@ export class FileWatcherManager implements IDisposable { this.logger.trace(`Deleting file watcher for ${entry.uri}`); for (const dirWatcher of entry.dirWatchers) { - disposeAll(dirWatcher.listeners); + disposeAll(dirWatcher.disposables); const dirWatcherEntry = this._dirWatchers.get(dirWatcher.uri); if (dirWatcherEntry) { diff --git a/extensions/typescript-language-features/src/utils/dispose.ts b/extensions/typescript-language-features/src/utils/dispose.ts index a3730ee4540a6..e7687bb6941ef 100644 --- a/extensions/typescript-language-features/src/utils/dispose.ts +++ b/extensions/typescript-language-features/src/utils/dispose.ts @@ -5,11 +5,23 @@ import * as vscode from 'vscode'; -export function disposeAll(disposables: vscode.Disposable[]) { + +export function disposeAll(disposables: Iterable) { + const errors: any[] = []; + for (const disposable of disposables) { - disposable.dispose(); + try { + disposable.dispose(); + } catch (e) { + errors.push(e); + } + } + + if (errors.length === 1) { + throw errors[0]; + } else if (errors.length > 1) { + throw new AggregateError(errors, 'Encountered errors while disposing of store'); } - disposables.length = 0; } export interface IDisposable { From 313deb4f9085e622143470b0a7e78d66c12c89ce Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 28 Oct 2024 13:33:44 -0500 Subject: [PATCH 031/555] move things from `TerminalChatController` -> `TerminalChatWidget`, rm terminal specific code from `VoiceChatActions`, `ChatWidget` (#232278) --- .../contrib/chat/browser/chatWidget.ts | 3 +- .../actions/voiceChatActions.ts | 91 +----- .../chat/browser/terminalChat.ts | 1 - .../browser/terminalChatAccessibilityHelp.ts | 2 +- .../chat/browser/terminalChatActions.ts | 72 +---- .../chat/browser/terminalChatController.ts | 209 +------------ .../chat/browser/terminalChatWidget.ts | 276 +++++++++++++++++- 7 files changed, 286 insertions(+), 368 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 50825c961e31e..825e80a7cc0ce 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -32,7 +32,6 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground } from '../../../../platform/theme/common/colorRegistry.js'; import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { TerminalChatController } from '../../terminal/terminalContribChatExports.js'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService, IChatWelcomeMessageContent, isChatWelcomeMessageContent } from '../common/chatAgents.js'; import { CONTEXT_CHAT_INPUT_HAS_AGENT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_QUICK_CHAT, CONTEXT_LAST_ITEM_ID, CONTEXT_RESPONSE_FILTERED } from '../common/chatContextKeys.js'; import { ChatEditingSessionState, IChatEditingService, IChatEditingSession } from '../common/chatEditingService.js'; @@ -1243,7 +1242,7 @@ export class ChatWidgetService extends Disposable implements IChatWidgetService readonly onDidAddWidget: Event = this._onDidAddWidget.event; get lastFocusedWidget(): IChatWidget | undefined { - return TerminalChatController.activeChatController?.chatWidget ?? this._lastFocusedWidget; + return this._lastFocusedWidget; } getAllWidgets(location: ChatAgentLocation): ReadonlyArray { diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 3935d4ed23552..12ed7d13047b1 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -46,8 +46,6 @@ import { InlineChatController } from '../../../inlineChat/browser/inlineChatCont import { CTX_INLINE_CHAT_FOCUSED, MENU_INLINE_CHAT_WIDGET_SECONDARY } from '../../../inlineChat/common/inlineChat.js'; import { NOTEBOOK_EDITOR_FOCUSED } from '../../../notebook/common/notebookContextKeys.js'; import { HasSpeechProvider, ISpeechService, KeywordRecognitionStatus, SpeechToTextInProgress, SpeechToTextStatus, TextToSpeechStatus, TextToSpeechInProgress as GlobalTextToSpeechInProgress } from '../../../speech/common/speechService.js'; -import { ITerminalService } from '../../../terminal/browser/terminal.js'; -import { TerminalChatContextKeys, TerminalChatController } from '../../../terminal/terminalContribChatExports.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IHostService } from '../../../../services/host/browser/host.js'; import { IWorkbenchLayoutService, Parts } from '../../../../services/layout/browser/layoutService.js'; @@ -59,15 +57,13 @@ import { renderStringAsPlaintext } from '../../../../../base/browser/markdownRen //#region Speech to Text -type VoiceChatSessionContext = 'view' | 'inline' | 'terminal' | 'quick' | 'editor'; -const VoiceChatSessionContexts: VoiceChatSessionContext[] = ['view', 'inline', 'terminal', 'quick', 'editor']; - -const TerminalChatExecute = MenuId.for('terminalChatInput'); // unfortunately, terminal decided to go with their own menu (https://github.com/microsoft/vscode/issues/208789) +type VoiceChatSessionContext = 'view' | 'inline' | 'quick' | 'editor'; +const VoiceChatSessionContexts: VoiceChatSessionContext[] = ['view', 'inline', 'quick', 'editor']; // Global Context Keys (set on global context key service) const CanVoiceChat = ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, HasSpeechProvider); const FocusInChatInput = ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT); -const AnyChatRequestInProgress = ContextKeyExpr.or(CONTEXT_CHAT_REQUEST_IN_PROGRESS, TerminalChatContextKeys.requestActive); +const AnyChatRequestInProgress = CONTEXT_CHAT_REQUEST_IN_PROGRESS; // Scoped Context Keys (set on per-chat-context scoped context key service) const ScopedVoiceChatGettingReady = new RawContextKey('scopedVoiceChatGettingReady', false, { type: 'boolean', description: localize('scopedVoiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat. This key is only defined scoped, per chat context.") }); @@ -106,12 +102,11 @@ class VoiceChatSessionControllerFactory { const quickChatService = accessor.get(IQuickChatService); const layoutService = accessor.get(IWorkbenchLayoutService); const editorService = accessor.get(IEditorService); - const terminalService = accessor.get(ITerminalService); const viewsService = accessor.get(IViewsService); switch (context) { case 'focused': { - const controller = VoiceChatSessionControllerFactory.doCreateForFocusedChat(terminalService, chatWidgetService, layoutService); + const controller = VoiceChatSessionControllerFactory.doCreateForFocusedChat(chatWidgetService, layoutService); return controller ?? VoiceChatSessionControllerFactory.create(accessor, 'view'); // fallback to 'view' } case 'view': { @@ -143,18 +138,7 @@ class VoiceChatSessionControllerFactory { return undefined; } - private static doCreateForFocusedChat(terminalService: ITerminalService, chatWidgetService: IChatWidgetService, layoutService: IWorkbenchLayoutService): IVoiceChatSessionController | undefined { - - // 1.) probe terminal chat which is not part of chat widget service - const activeInstance = terminalService.activeInstance; - if (activeInstance) { - const terminalChat = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - if (terminalChat?.hasFocus()) { - return VoiceChatSessionControllerFactory.doCreateForTerminalChat(terminalChat); - } - } - - // 2.) otherwise go via chat widget service + private static doCreateForFocusedChat(chatWidgetService: IChatWidgetService, layoutService: IWorkbenchLayoutService): IVoiceChatSessionController | undefined { const chatWidget = chatWidgetService.lastFocusedWidget; if (chatWidget?.hasInputFocus()) { @@ -217,23 +201,6 @@ class VoiceChatSessionControllerFactory { updateState: VoiceChatSessionControllerFactory.createChatContextKeyController(chatWidget.scopedContextKeyService, context) }; } - - private static doCreateForTerminalChat(terminalChat: TerminalChatController): IVoiceChatSessionController { - const context = 'terminal'; - return { - context, - scopedContextKeyService: terminalChat.scopedContextKeyService, - onDidAcceptInput: terminalChat.onDidAcceptInput, - onDidHideInput: terminalChat.onDidHide, - focusInput: () => terminalChat.focus(), - acceptInput: () => terminalChat.acceptInput(true), - updateInput: text => terminalChat.updateInput(text, false), - getInput: () => terminalChat.getInput(), - setInputPlaceholder: text => terminalChat.setPlaceholder(text), - clearInputPlaceholder: () => terminalChat.resetPlaceholder(), - updateState: VoiceChatSessionControllerFactory.createChatContextKeyController(terminalChat.scopedContextKeyService, context) - }; - } } interface IVoiceChatSession { @@ -610,17 +577,7 @@ export class StartVoiceChatAction extends Action2 { when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession).negate(), menuCondition), group: 'navigation', order: 2 - }, - { - id: TerminalChatExecute, - when: ContextKeyExpr.and( - HasSpeechProvider, - ScopedChatSynthesisInProgress.negate(), // hide when text to speech is in progress - AnyScopedVoiceChatInProgress?.negate(), // hide when voice chat is in progress - ), - group: 'navigation', - order: -1 - }, + } ] }); } @@ -668,13 +625,7 @@ export class StopListeningAction extends Action2 { when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), AnyScopedVoiceChatInProgress), group: 'navigation', order: 2 - }, { - - id: TerminalChatExecute, - when: AnyScopedVoiceChatInProgress, - group: 'navigation', - order: -1 - }, + } ] }); } @@ -742,22 +693,6 @@ class ChatSynthesizerSessionController { private static doCreateForFocusedChat(accessor: ServicesAccessor, response: IChatResponseModel): IChatSynthesizerSessionController { const chatWidgetService = accessor.get(IChatWidgetService); const contextKeyService = accessor.get(IContextKeyService); - const terminalService = accessor.get(ITerminalService); - - // 1.) probe terminal chat which is not part of chat widget service - const activeInstance = terminalService.activeInstance; - if (activeInstance) { - const terminalChat = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - if (terminalChat?.hasFocus()) { - return { - onDidHideChat: terminalChat.onDidHide, - contextKeyService: terminalChat.scopedContextKeyService, - response - }; - } - } - - // 2.) otherwise go via chat widget service let chatWidget = chatWidgetService.getWidgetBySessionId(response.session.sessionId); if (chatWidget?.location === ChatAgentLocation.Editor) { // TODO@bpasero workaround for https://github.com/microsoft/vscode/issues/212785 @@ -1010,12 +945,6 @@ export class StopReadAloud extends Action2 { when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), ScopedChatSynthesisInProgress), group: 'navigation', order: 2 - }, - { - id: TerminalChatExecute, - when: ScopedChatSynthesisInProgress, - group: 'navigation', - order: -1 } ] }); @@ -1363,12 +1292,6 @@ export class InstallSpeechProviderForVoiceChatAction extends BaseInstallSpeechPr when: ContextKeyExpr.and(HasSpeechProvider.negate(), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession).negate()), group: 'navigation', order: 2 - }, - { - id: TerminalChatExecute, - when: HasSpeechProvider.negate(), - group: 'navigation', - order: -1 } ] }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 40e5b339243a4..971cafc9cbb8e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -22,7 +22,6 @@ export const enum TerminalChatCommandId { NextFromHistory = 'workbench.action.terminal.chat.nextFromHistory', } -export const MENU_TERMINAL_CHAT_INPUT = MenuId.for('terminalChatInput'); export const MENU_TERMINAL_CHAT_WIDGET = MenuId.for('terminalChatWidget'); export const MENU_TERMINAL_CHAT_WIDGET_STATUS = MenuId.for('terminalChatWidget.status'); export const MENU_TERMINAL_CHAT_WIDGET_TOOLBAR = MenuId.for('terminalChatWidget.toolbar'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index 20ccc9c9e255c..5017c56364db2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -31,7 +31,7 @@ export class TerminalChatAccessibilityHelp implements IAccessibleViewImplentatio AccessibleViewProviderId.TerminalChat, { type: AccessibleViewType.Help }, () => helpText, - () => TerminalChatController.get(instance)?.focus(), + () => TerminalChatController.get(instance)?.terminalChatWidget?.focus(), AccessibilityVerbositySettingId.TerminalChat, ); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 885239c600974..761e762b43b76 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -14,7 +14,7 @@ import { CTX_INLINE_CHAT_EMPTY } from '../../../inlineChat/common/inlineChat.js' import { isDetachedTerminalInstance } from '../../../terminal/browser/terminal.js'; import { registerActiveXtermAction } from '../../../terminal/browser/terminalActions.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from './terminalChat.js'; +import { MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from './terminalChat.js'; import { TerminalChatController } from './terminalChatController.js'; registerActiveXtermAction({ @@ -44,13 +44,13 @@ registerActiveXtermAction({ if (typeof opts === 'object' && opts !== null && 'query' in opts && typeof opts.query === 'string') { contr?.updateInput(opts.query, false); if (!('isPartialQuery' in opts && opts.isPartialQuery)) { - contr?.acceptInput(); + contr?.terminalChatWidget?.acceptInput(); } } } - contr?.reveal(); + contr?.terminalChatWidget?.reveal(); } }); @@ -78,7 +78,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - contr?.clear(); + contr?.terminalChatWidget?.clear(); } }); @@ -106,7 +106,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - contr?.clear(); + contr?.terminalChatWidget?.clear(); } }); @@ -138,7 +138,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - contr?.acceptCommand(true); + contr?.terminalChatWidget?.acceptCommand(true); } }); @@ -168,7 +168,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - contr?.acceptCommand(true); + contr?.terminalChatWidget?.acceptCommand(true); } }); @@ -200,7 +200,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - contr?.acceptCommand(false); + contr?.terminalChatWidget?.acceptCommand(false); } }); @@ -230,7 +230,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - contr?.acceptCommand(false); + contr?.terminalChatWidget?.acceptCommand(false); } }); @@ -263,56 +263,6 @@ registerActiveXtermAction({ } }); -registerActiveXtermAction({ - id: TerminalChatCommandId.MakeRequest, - title: localize2('makeChatRequest', 'Make Chat Request'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.requestActive.negate(), - CTX_INLINE_CHAT_EMPTY.negate() - ), - icon: Codicon.send, - keybinding: { - when: ContextKeyExpr.and(TerminalChatContextKeys.focused, TerminalChatContextKeys.requestActive.negate(), historyNavigationVisible.isEqualTo(false)), - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyCode.Enter - }, - menu: { - id: MENU_TERMINAL_CHAT_INPUT, - group: 'navigation', - order: 1, - when: TerminalChatContextKeys.requestActive.negate(), - }, - run: (_xterm, _accessor, activeInstance) => { - if (isDetachedTerminalInstance(activeInstance)) { - return; - } - const contr = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - contr?.acceptInput(); - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.Cancel, - title: localize2('cancelChat', 'Cancel Chat'), - precondition: ContextKeyExpr.and( - TerminalChatContextKeys.requestActive, - ), - icon: Codicon.stopCircle, - menu: { - id: MENU_TERMINAL_CHAT_INPUT, - group: 'navigation', - when: TerminalChatContextKeys.requestActive, - }, - run: (_xterm, _accessor, activeInstance) => { - if (isDetachedTerminalInstance(activeInstance)) { - return; - } - const contr = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - contr?.cancel(); - } -}); - registerActiveXtermAction({ id: TerminalChatCommandId.PreviousFromHistory, title: localize2('previousFromHitory', 'Previous From History'), @@ -328,7 +278,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - contr?.populateHistory(true); + contr?.terminalChatWidget?.populateHistory(true); } }); @@ -347,6 +297,6 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - contr?.populateHistory(false); + contr?.terminalChatWidget?.populateHistory(false); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 17e77a4a945b7..f01da1158af8b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -4,36 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; -import { CancellationTokenSource } from '../../../../../base/common/cancellation.js'; -import { Emitter, Event } from '../../../../../base/common/event.js'; import { Lazy } from '../../../../../base/common/lazy.js'; -import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; -import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { IChatCodeBlockContextProviderService, IChatWidget, showChatView } from '../../../chat/browser/chat.js'; +import { IChatCodeBlockContextProviderService, showChatView } from '../../../chat/browser/chat.js'; import { IChatProgress, IChatService } from '../../../chat/common/chatService.js'; import { isDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from '../../../terminal/browser/terminal.js'; import { TerminalChatWidget } from './terminalChatWidget.js'; -import { CancelablePromise, createCancelablePromise, DeferredPromise } from '../../../../../base/common/async.js'; -import { assertType } from '../../../../../base/common/types.js'; -import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; -import { ChatAgentLocation } from '../../../chat/common/chatAgents.js'; -import { ChatModel, IChatResponseModel } from '../../../chat/common/chatModel.js'; import type { ITerminalContributionContext } from '../../../terminal/browser/terminalExtensions.js'; -import { TerminalChatContextKeys } from './terminalChat.js'; - -const enum Message { - None = 0, - AcceptSession = 1 << 0, - CancelSession = 1 << 1, - PauseSession = 1 << 2, - CancelRequest = 1 << 3, - CancelInput = 1 << 4, - AcceptInput = 1 << 5, - ReturnInput = 1 << 6, -} export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.chat'; @@ -47,9 +28,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr */ static activeChatController?: TerminalChatController; - private static _storageKey = 'terminal-inline-chat-history'; - private static _promptHistory: string[] = []; - /** * The chat widget for the controller, this is lazy as we don't want to instantiate it until * both it's required and xterm is ready. @@ -62,41 +40,18 @@ export class TerminalChatController extends Disposable implements ITerminalContr */ get terminalChatWidget(): TerminalChatWidget | undefined { return this._terminalChatWidget?.value; } - /** - * The base chat widget for the controller, this will be undefined if xterm is not ready yet (ie. the - * terminal is still initializing). - */ - get chatWidget(): IChatWidget | undefined { return this._terminalChatWidget?.value.inlineChatWidget?.chatWidget; } - - private readonly _requestActiveContextKey: IContextKey; - private readonly _responseContainsCodeBlockContextKey: IContextKey; - private readonly _responseContainsMulitpleCodeBlocksContextKey: IContextKey; - - private _messages = this._store.add(new Emitter()); - private _lastResponseContent: string | undefined; get lastResponseContent(): string | undefined { return this._lastResponseContent; } - readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.AcceptInput, this._store); - get onDidHide() { return this.terminalChatWidget?.onDidHide ?? Event.None; } - private _terminalAgentName = 'terminal'; - private readonly _model: MutableDisposable = this._register(new MutableDisposable()); - get scopedContextKeyService(): IContextKeyService { return this._terminalChatWidget?.value.inlineChatWidget.scopedContextKeyService ?? this._contextKeyService; } - private _sessionCtor: CancelablePromise | undefined; - private _historyOffset: number = -1; - private _historyCandidate: string = ''; - private _historyUpdate: (prompt: string) => void; - private _currentRequestId: string | undefined; - private _activeRequestCts?: CancellationTokenSource; constructor( private readonly _ctx: ITerminalContributionContext, @@ -104,16 +59,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IChatService private readonly _chatService: IChatService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IStorageService private readonly _storageService: IStorageService, @ITerminalService private readonly _terminalService: ITerminalService, @IViewsService private readonly _viewsService: IViewsService, ) { super(); - this._requestActiveContextKey = TerminalChatContextKeys.requestActive.bindTo(this._contextKeyService); - this._responseContainsCodeBlockContextKey = TerminalChatContextKeys.responseContainsCodeBlock.bindTo(this._contextKeyService); - this._responseContainsMulitpleCodeBlocksContextKey = TerminalChatContextKeys.responseContainsMultipleCodeBlocks.bindTo(this._contextKeyService); - this._register(chatCodeBlockContextProviderService.registerProvider({ getCodeBlockContext: (editor) => { if (!editor || !this._terminalChatWidget?.hasValue || !this.hasFocus()) { @@ -127,18 +77,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr }; } }, 'terminal')); - - TerminalChatController._promptHistory = JSON.parse(this._storageService.get(TerminalChatController._storageKey, StorageScope.PROFILE, '[]')); - this._historyUpdate = (prompt: string) => { - const idx = TerminalChatController._promptHistory.indexOf(prompt); - if (idx >= 0) { - TerminalChatController._promptHistory.splice(idx, 1); - } - TerminalChatController._promptHistory.unshift(prompt); - this._historyOffset = -1; - this._historyCandidate = ''; - this._storageService.store(TerminalChatController._storageKey, JSON.stringify(TerminalChatController._promptHistory), StorageScope.PROFILE, StorageTarget.USER); - }; } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { @@ -161,21 +99,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr }); } - private async _createSession(): Promise { - this._sessionCtor = createCancelablePromise(async token => { - if (!this._model.value) { - this._model.value = this._chatService.startSession(ChatAgentLocation.Terminal, token); - const model = this._model.value; - if (model) { - this._terminalChatWidget?.value.inlineChatWidget.setChatModel(model); - } - if (!this._model.value) { - throw new Error('Failed to start chat session'); - } - } - }); - this._register(toDisposable(() => this._sessionCtor?.cancel())); - } private _forcedPlaceholder: string | undefined = undefined; @@ -200,68 +123,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._updatePlaceholder(); } - clear(): void { - this.cancel(); - this._model.clear(); - this._responseContainsCodeBlockContextKey.reset(); - this._requestActiveContextKey.reset(); - this._terminalChatWidget?.value.hide(); - this._terminalChatWidget?.value.setValue(undefined); - } - - async acceptInput(isVoiceInput?: boolean): Promise { - assertType(this._terminalChatWidget); - if (!this._model.value) { - await this.reveal(); - } - assertType(this._model.value); - this._messages.fire(Message.AcceptInput); - const lastInput = this._terminalChatWidget.value.inlineChatWidget.value; - if (!lastInput) { - return; - } - this._historyUpdate(lastInput); - this._activeRequestCts?.cancel(); - this._activeRequestCts = new CancellationTokenSource(); - const store = new DisposableStore(); - this._requestActiveContextKey.set(true); - let responseContent = ''; - const response = await this._terminalChatWidget.value.inlineChatWidget.chatWidget.acceptInput(lastInput, { isVoiceInput }); - this._currentRequestId = response?.requestId; - const responsePromise = new DeferredPromise(); - try { - this._requestActiveContextKey.set(true); - if (response) { - store.add(response.onDidChange(async () => { - responseContent += response.response.value; - if (response.isCanceled) { - this._requestActiveContextKey.set(false); - responsePromise.complete(undefined); - return; - } - if (response.isComplete) { - this._requestActiveContextKey.set(false); - this._requestActiveContextKey.set(false); - const firstCodeBlock = await this.terminalChatWidget?.inlineChatWidget.getCodeBlockInfo(0); - const secondCodeBlock = await this.terminalChatWidget?.inlineChatWidget.getCodeBlockInfo(1); - this._responseContainsCodeBlockContextKey.set(!!firstCodeBlock); - this._responseContainsMulitpleCodeBlocksContextKey.set(!!secondCodeBlock); - this._terminalChatWidget?.value.inlineChatWidget.updateToolbar(true); - responsePromise.complete(response); - } - })); - } - await responsePromise.p; - this._lastResponseContent = response?.response.getMarkdown(); - return response; - } catch { - this._lastResponseContent = undefined; - return; - } finally { - store.dispose(); - } - } - updateInput(text: string, selectAll = true): void { const widget = this._terminalChatWidget?.value.inlineChatWidget; if (widget) { @@ -272,10 +133,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr } } - getInput(): string { - return this._terminalChatWidget?.value.input() ?? ''; - } - focus(): void { this._terminalChatWidget?.value.focus(); } @@ -284,66 +141,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr return this._terminalChatWidget?.rawValue?.hasFocus() ?? false; } - populateHistory(up: boolean) { - if (!this._terminalChatWidget?.value) { - return; - } - - const len = TerminalChatController._promptHistory.length; - if (len === 0) { - return; - } - - if (this._historyOffset === -1) { - // remember the current value - this._historyCandidate = this._terminalChatWidget.value.inlineChatWidget.value; - } - - const newIdx = this._historyOffset + (up ? 1 : -1); - if (newIdx >= len) { - // reached the end - return; - } - - let entry: string; - if (newIdx < 0) { - entry = this._historyCandidate; - this._historyOffset = -1; - } else { - entry = TerminalChatController._promptHistory[newIdx]; - this._historyOffset = newIdx; - } - - this._terminalChatWidget.value.inlineChatWidget.value = entry; - this._terminalChatWidget.value.inlineChatWidget.selectAll(); - } - - cancel(): void { - this._sessionCtor?.cancel(); - this._sessionCtor = undefined; - this._activeRequestCts?.cancel(); - this._requestActiveContextKey.set(false); - const model = this._terminalChatWidget?.value.inlineChatWidget.getChatModel(); - if (!model?.sessionId) { - return; - } - this._chatService.cancelCurrentRequestForSession(model?.sessionId); - } - - async acceptCommand(shouldExecute: boolean): Promise { - const code = await this.terminalChatWidget?.inlineChatWidget.getCodeBlockInfo(0); - if (!code) { - return; - } - this._terminalChatWidget?.value.acceptCommand(code.textEditorModel.getValue(), shouldExecute); - } - - async reveal(): Promise { - await this._createSession(); - this._terminalChatWidget?.value.reveal(); - this._terminalChatWidget?.value.focus(); - } - async viewInChat(): Promise { //TODO: is this necessary? better way? const widget = await showChatView(this._viewsService); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 2f4192e8fcb12..b31c6de82c68e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -6,7 +6,7 @@ import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { Dimension, getActiveWindow, IFocusTracker, trackFocus } from '../../../../../base/browser/dom.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; -import { Disposable, toDisposable } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { MicrotaskDelay } from '../../../../../base/common/symbols.js'; import './media/terminalChatWidget.css'; import { localize } from '../../../../../nls.js'; @@ -15,15 +15,34 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com import { ChatAgentLocation } from '../../../chat/common/chatAgents.js'; import { InlineChatWidget } from '../../../inlineChat/browser/inlineChatWidget.js'; import { ITerminalInstance, type IXtermTerminal } from '../../../terminal/browser/terminal.js'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from './terminalChat.js'; +import { MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from './terminalChat.js'; import { TerminalStickyScrollContribution } from '../../stickyScroll/browser/terminalStickyScrollContribution.js'; import { MENU_INLINE_CHAT_WIDGET_SECONDARY } from '../../../inlineChat/common/inlineChat.js'; +import { CancelablePromise, createCancelablePromise, DeferredPromise } from '../../../../../base/common/async.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; +import { IChatAcceptInputOptions, showChatView } from '../../../chat/browser/chat.js'; +import { ChatModel, IChatResponseModel } from '../../../chat/common/chatModel.js'; +import { IChatService, IChatProgress } from '../../../chat/common/chatService.js'; +import { CancellationTokenSource } from '../../../../../base/common/cancellation.js'; +import { MenuId } from '../../../../../platform/actions/common/actions.js'; const enum Constants { HorizontalMargin = 10, VerticalMargin = 30 } +const enum Message { + None = 0, + AcceptSession = 1 << 0, + CancelSession = 1 << 1, + PauseSession = 1 << 2, + CancelRequest = 1 << 3, + CancelInput = 1 << 4, + AcceptInput = 1 << 5, + ReturnInput = 1 << 6, +} + const terminalChatPlaceholder = localize('default.placeholder', "Ask how to do something in the terminal"); export class TerminalChatWidget extends Disposable { @@ -40,17 +59,46 @@ export class TerminalChatWidget extends Disposable { private readonly _focusedContextKey: IContextKey; private readonly _visibleContextKey: IContextKey; + private readonly _requestActiveContextKey: IContextKey; + private readonly _responseContainsCodeBlockContextKey: IContextKey; + private readonly _responseContainsMulitpleCodeBlocksContextKey: IContextKey; + + private _messages = this._store.add(new Emitter()); + + private _storageKey = 'terminal-inline-chat-history'; + private _promptHistory: string[] = []; + + private _lastResponseContent: string | undefined; + get lastResponseContent(): string | undefined { + return this._lastResponseContent; + } + + private _terminalAgentName = 'terminal'; + + private readonly _model: MutableDisposable = this._register(new MutableDisposable()); + + private _sessionCtor: CancelablePromise | undefined; + private _historyOffset: number = -1; + private _historyCandidate: string = ''; + private _historyUpdate: (prompt: string) => void; + + private _currentRequestId: string | undefined; + private _activeRequestCts?: CancellationTokenSource; + constructor( private readonly _terminalElement: HTMLElement, private readonly _instance: ITerminalInstance, private readonly _xterm: IXtermTerminal & { raw: RawXtermTerminal }, - @IContextKeyService contextKeyService: IContextKeyService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IChatService private readonly _chatService: IChatService, + @IStorageService private readonly _storageService: IStorageService, + @IViewsService private readonly _viewsService: IViewsService, @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this._focusedContextKey = TerminalChatContextKeys.focused.bindTo(contextKeyService); - this._visibleContextKey = TerminalChatContextKeys.visible.bindTo(contextKeyService); + this._focusedContextKey = TerminalChatContextKeys.focused.bindTo(_contextKeyService); + this._visibleContextKey = TerminalChatContextKeys.visible.bindTo(_contextKeyService); this._container = document.createElement('div'); this._container.classList.add('terminal-inline-chat'); @@ -83,7 +131,7 @@ export class TerminalChatWidget extends Disposable { rendererOptions: { editableCodeBlock: true }, menus: { telemetrySource: 'terminal-inline-chat', - executeToolbar: MENU_TERMINAL_CHAT_INPUT, + executeToolbar: MenuId.ChatExecute, inputSideToolbar: MENU_TERMINAL_CHAT_WIDGET, } } @@ -108,6 +156,22 @@ export class TerminalChatWidget extends Disposable { this._register(this._focusTracker.onDidBlur(() => this._focusedContextKey.set(false))); this.hide(); + + this._requestActiveContextKey = TerminalChatContextKeys.requestActive.bindTo(this._contextKeyService); + this._responseContainsCodeBlockContextKey = TerminalChatContextKeys.responseContainsCodeBlock.bindTo(this._contextKeyService); + this._responseContainsMulitpleCodeBlocksContextKey = TerminalChatContextKeys.responseContainsMultipleCodeBlocks.bindTo(this._contextKeyService); + + this._promptHistory = JSON.parse(this._storageService.get(this._storageKey, StorageScope.PROFILE, '[]')); + this._historyUpdate = (prompt: string) => { + const idx = this._promptHistory.indexOf(prompt); + if (idx >= 0) { + this._promptHistory.splice(idx, 1); + } + this._promptHistory.unshift(prompt); + this._historyOffset = -1; + this._historyCandidate = ''; + this._storageService.store(this._storageKey, JSON.stringify(this._promptHistory), StorageScope.PROFILE, StorageTarget.USER); + }; } private _dimension?: Dimension; @@ -153,7 +217,8 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); } - reveal(): void { + async reveal(): Promise { + await this._createSession(); this._doLayout(this._inlineChatWidget.contentHeight); this._container.classList.remove('hide'); this._visibleContextKey.set(true); @@ -221,22 +286,207 @@ export class TerminalChatWidget extends Disposable { } } focus(): void { - this._inlineChatWidget.focus(); + this.inlineChatWidget.focus(); } hasFocus(): boolean { return this._inlineChatWidget.hasFocus(); } - input(): string { - return this._inlineChatWidget.value; - } + setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; } - acceptCommand(code: string, shouldExecute: boolean): void { - this._instance.runCommand(code, shouldExecute); + + async acceptCommand(shouldExecute: boolean): Promise { + const code = await this.inlineChatWidget.getCodeBlockInfo(0); + if (!code) { + return; + } + const value = code.textEditorModel.getValue(); + this._instance.runCommand(value, shouldExecute); this.hide(); } + public get focusTracker(): IFocusTracker { return this._focusTracker; } + + private async _createSession(): Promise { + this._sessionCtor = createCancelablePromise(async token => { + if (!this._model.value) { + this._model.value = this._chatService.startSession(ChatAgentLocation.Terminal, token); + const model = this._model.value; + if (model) { + this._inlineChatWidget.setChatModel(model); + } + if (!this._model.value) { + throw new Error('Failed to start chat session'); + } + } + }); + this._register(toDisposable(() => this._sessionCtor?.cancel())); + } + + private _forcedPlaceholder: string | undefined = undefined; + + private _updatePlaceholder(): void { + const inlineChatWidget = this._inlineChatWidget; + if (inlineChatWidget) { + inlineChatWidget.placeholder = this._getPlaceholderText(); + } + } + + private _getPlaceholderText(): string { + return this._forcedPlaceholder ?? ''; + } + + setPlaceholder(text: string): void { + this._forcedPlaceholder = text; + this._updatePlaceholder(); + } + + resetPlaceholder(): void { + this._forcedPlaceholder = undefined; + this._updatePlaceholder(); + } + + clear(): void { + this.cancel(); + this._model.clear(); + this._responseContainsCodeBlockContextKey.reset(); + this._requestActiveContextKey.reset(); + this.hide(); + this.setValue(undefined); + } + + async acceptInput(query?: string, options?: IChatAcceptInputOptions): Promise { + if (!this._model.value) { + await this.reveal(); + } + this._messages.fire(Message.AcceptInput); + const lastInput = this._inlineChatWidget.value; + if (!lastInput) { + return; + } + this._historyUpdate(lastInput); + this._activeRequestCts?.cancel(); + this._activeRequestCts = new CancellationTokenSource(); + const store = new DisposableStore(); + this._requestActiveContextKey.set(true); + let responseContent = ''; + const response = await this._inlineChatWidget.chatWidget.acceptInput(lastInput, { isVoiceInput: options?.isVoiceInput }); + this._currentRequestId = response?.requestId; + const responsePromise = new DeferredPromise(); + try { + this._requestActiveContextKey.set(true); + if (response) { + store.add(response.onDidChange(async () => { + responseContent += response.response.value; + if (response.isCanceled) { + this._requestActiveContextKey.set(false); + responsePromise.complete(undefined); + return; + } + if (response.isComplete) { + this._requestActiveContextKey.set(false); + this._requestActiveContextKey.set(false); + const firstCodeBlock = await this._inlineChatWidget.getCodeBlockInfo(0); + const secondCodeBlock = await this._inlineChatWidget.getCodeBlockInfo(1); + this._responseContainsCodeBlockContextKey.set(!!firstCodeBlock); + this._responseContainsMulitpleCodeBlocksContextKey.set(!!secondCodeBlock); + this._inlineChatWidget.updateToolbar(true); + responsePromise.complete(response); + } + })); + } + await responsePromise.p; + this._lastResponseContent = response?.response.getMarkdown(); + return response; + } catch { + this._lastResponseContent = undefined; + return; + } finally { + store.dispose(); + } + } + + populateHistory(up: boolean) { + if (!this._inlineChatWidget) { + return; + } + + const len = this._promptHistory.length; + if (len === 0) { + return; + } + + if (this._historyOffset === -1) { + // remember the current value + this._historyCandidate = this._inlineChatWidget.value; + } + + const newIdx = this._historyOffset + (up ? 1 : -1); + if (newIdx >= len) { + // reached the end + return; + } + + let entry: string; + if (newIdx < 0) { + entry = this._historyCandidate; + this._historyOffset = -1; + } else { + entry = this._promptHistory[newIdx]; + this._historyOffset = newIdx; + } + + this._inlineChatWidget.value = entry; + this._inlineChatWidget.selectAll(); + } + + cancel(): void { + this._sessionCtor?.cancel(); + this._sessionCtor = undefined; + this._activeRequestCts?.cancel(); + this._requestActiveContextKey.set(false); + const model = this._inlineChatWidget.getChatModel(); + if (!model?.sessionId) { + return; + } + this._chatService.cancelCurrentRequestForSession(model?.sessionId); + } + + async viewInChat(): Promise { + const widget = await showChatView(this._viewsService); + const currentRequest = this._inlineChatWidget.chatWidget.viewModel?.model.getRequests().find(r => r.id === this._currentRequestId); + if (!widget || !currentRequest?.response) { + return; + } + + const message: IChatProgress[] = []; + for (const item of currentRequest.response.response.value) { + if (item.kind === 'textEditGroup') { + for (const group of item.edits) { + message.push({ + kind: 'textEdit', + edits: group, + uri: item.uri + }); + } + } else { + message.push(item); + } + } + + this._chatService.addCompleteRequest(widget!.viewModel!.sessionId, + `@${this._terminalAgentName} ${currentRequest.message.text}`, + currentRequest.variableData, + currentRequest.attempt, + { + message, + result: currentRequest.response!.result, + followups: currentRequest.response!.followups + }); + widget.focusLastMessage(); + this.hide(); + } } From 3ea540ee6924ef330d18e64c34d7426da34f327c Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Mon, 28 Oct 2024 11:36:51 -0700 Subject: [PATCH 032/555] fix: deprecate and migrate `chat.experimental.detectParticipant.enabled` (#232436) --- .../contrib/chat/browser/chat.contribution.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 6193c62fb827b..b1dffb42d20ca 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -76,6 +76,7 @@ import { LanguageModelToolsService } from './languageModelToolsService.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeContributions.js'; import { ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService } from '../common/ignoredFiles.js'; import { ChatGettingStartedContribution } from './actions/chatGettingStarted.js'; +import { Extensions, IConfigurationMigrationRegistry } from '../../../common/configuration.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -136,9 +137,15 @@ configurationRegistry.registerConfiguration({ }, 'chat.experimental.detectParticipant.enabled': { type: 'boolean', + deprecationMessage: nls.localize('chat.experimental.detectParticipant.enabled.deprecated', "This setting is deprecated. Please use `chat.detectParticipant.enabled` instead."), description: nls.localize('chat.experimental.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), default: null }, + 'chat.detectParticipant.enabled': { + type: 'boolean', + description: nls.localize('chat.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), + default: true + }, } }); Registry.as(EditorExtensions.EditorPane).registerEditorPane( @@ -151,6 +158,15 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane new SyncDescriptor(ChatEditorInput) ] ); +Registry.as(Extensions.ConfigurationMigration).registerConfigurationMigrations([ + { + key: 'chat.experimental.detectParticipant.enabled', + migrateFn: (value, _accessor) => ([ + ['chat.experimental.detectParticipant.enabled', { value: undefined }], + ['chat.detectParticipant.enabled', { value: value !== false }] + ]) + } +]); class ChatResolverContribution extends Disposable { From 1330eb738986802e906df1beda0e3a2334fca820 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 28 Oct 2024 20:43:28 +0100 Subject: [PATCH 033/555] WSL terminal CLI is broken (#230584) (#232441) --- src/vs/server/node/server.cli.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index 2704d7a98766d..4c821c003d5cf 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -17,8 +17,6 @@ import { hasStdinWithoutTty, getStdinFilePath, readFromStdin } from '../../platf import { DeferredPromise } from '../../base/common/async.js'; import { FileAccess } from '../../base/common/network.js'; -const __dirname = dirname(url.fileURLToPath(import.meta.url)); - /* * Implements a standalone CLI app that opens VS Code from a remote terminal. * - In integrated terminals for remote windows this connects to the remote server though a pipe. @@ -151,7 +149,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise console.log(err)); return; } From 687f158e289c5a182be5362f952c3fa8a51afaba Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 28 Oct 2024 14:55:40 -0500 Subject: [PATCH 034/555] indicate chat agent name on tab focus, rm toolbar role (#232438) add tabindex to username --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index f0bf629d7203a..f4d6ab305e7aa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -272,10 +272,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Mon, 28 Oct 2024 13:59:56 -0700 Subject: [PATCH 035/555] move search APIs from using `new` to `2` (#232443) * move search APIs from using `new` to `2` --- .../common/extensionsApiProposals.ts | 20 ++++----- .../workbench/api/common/extHost.api.impl.ts | 28 ++++++------- src/vs/workbench/api/common/extHostSearch.ts | 22 +++++----- .../workbench/api/common/extHostWorkspace.ts | 12 +++--- src/vs/workbench/api/node/extHostSearch.ts | 2 +- .../api/test/browser/extHostWorkspace.test.ts | 12 +++--- .../api/test/node/extHostSearch.test.ts | 2 +- .../search/common/fileSearchManager.ts | 6 +-- .../search/common/searchExtConversionTypes.ts | 22 +++++----- .../services/search/common/searchExtTypes.ts | 42 +++++++++---------- .../search/common/textSearchManager.ts | 34 +++++++-------- .../search/node/ripgrepSearchProvider.ts | 10 ++--- .../search/node/ripgrepTextSearchEngine.ts | 24 +++++------ .../services/search/node/textSearchManager.ts | 4 +- .../node/ripgrepTextSearchEngineUtils.test.ts | 30 ++++++------- .../test/node/textSearchManager.test.ts | 6 +-- ...scode.proposed.aiTextSearchProvider2.d.ts} | 6 +-- ... vscode.proposed.fileSearchProvider2.d.ts} | 4 +- ... => vscode.proposed.findTextInFiles2.d.ts} | 26 ++++++------ ... vscode.proposed.textSearchComplete2.d.ts} | 6 +-- ... vscode.proposed.textSearchProvider2.d.ts} | 28 ++++++------- 21 files changed, 173 insertions(+), 173 deletions(-) rename src/vscode-dts/{vscode.proposed.aiTextSearchProviderNew.d.ts => vscode.proposed.aiTextSearchProvider2.d.ts} (83%) rename src/vscode-dts/{vscode.proposed.fileSearchProviderNew.d.ts => vscode.proposed.fileSearchProvider2.d.ts} (95%) rename src/vscode-dts/{vscode.proposed.findTextInFilesNew.d.ts => vscode.proposed.findTextInFiles2.d.ts} (85%) rename src/vscode-dts/{vscode.proposed.textSearchCompleteNew.d.ts => vscode.proposed.textSearchComplete2.d.ts} (90%) rename src/vscode-dts/{vscode.proposed.textSearchProviderNew.d.ts => vscode.proposed.textSearchProvider2.d.ts} (89%) diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 2296726d8e0e5..6ea83f743ad66 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -15,8 +15,8 @@ const _allApiProposals = { aiTextSearchProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts', }, - aiTextSearchProviderNew: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProviderNew.d.ts', + aiTextSearchProvider2: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider2.d.ts', }, attributableCoverage: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.attributableCoverage.d.ts', @@ -200,8 +200,8 @@ const _allApiProposals = { fileSearchProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts', }, - fileSearchProviderNew: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts', + fileSearchProvider2: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts', }, findFiles2: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2.d.ts', @@ -212,8 +212,8 @@ const _allApiProposals = { findTextInFiles: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts', }, - findTextInFilesNew: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFilesNew.d.ts', + findTextInFiles2: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts', }, fsChunks: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts', @@ -365,14 +365,14 @@ const _allApiProposals = { testRelatedCode: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testRelatedCode.d.ts', }, - textSearchCompleteNew: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchCompleteNew.d.ts', + textSearchComplete2: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchComplete2.d.ts', }, textSearchProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts', }, - textSearchProviderNew: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProviderNew.d.ts', + textSearchProvider2: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts', }, timeline: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts', diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 68734bfe120d3..82b336d4c2bf9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -106,7 +106,7 @@ import { ExtensionDescriptionRegistry } from '../../services/extensions/common/e import { UIKind } from '../../services/extensions/common/extensionHostProtocol.js'; import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { ProxyIdentifier } from '../../services/extensions/common/proxyIdentifier.js'; -import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContextNew, TextSearchMatchNew } from '../../services/search/common/searchExtTypes.js'; +import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContext2, TextSearchMatch2 } from '../../services/search/common/searchExtTypes.js'; import type * as vscode from 'vscode'; import { ExtHostCodeMapper } from './extHostCodeMapper.js'; @@ -985,10 +985,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWorkspace.findTextInFiles(query, options || {}, callback, extension.identifier, token); }, - findTextInFilesNew: (query: vscode.TextSearchQueryNew, options?: vscode.FindTextInFilesOptionsNew, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse => { - checkProposedApiEnabled(extension, 'findTextInFilesNew'); - checkProposedApiEnabled(extension, 'textSearchProviderNew'); - return extHostWorkspace.findTextInFilesNew(query, options, extension.identifier, token); + findTextInFiles2: (query: vscode.TextSearchQuery2, options?: vscode.FindTextInFilesOptions2, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse => { + checkProposedApiEnabled(extension, 'findTextInFiles2'); + checkProposedApiEnabled(extension, 'textSearchProvider2'); + return extHostWorkspace.findTextInFiles2(query, options, extension.identifier, token); }, save: (uri) => { return extHostWorkspace.save(uri); @@ -1139,18 +1139,18 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'textSearchProvider'); return extHostSearch.registerAITextSearchProviderOld(scheme, provider); }, - registerFileSearchProviderNew: (scheme: string, provider: vscode.FileSearchProviderNew) => { - checkProposedApiEnabled(extension, 'fileSearchProviderNew'); + registerFileSearchProvider2: (scheme: string, provider: vscode.FileSearchProvider2) => { + checkProposedApiEnabled(extension, 'fileSearchProvider2'); return extHostSearch.registerFileSearchProvider(scheme, provider); }, - registerTextSearchProviderNew: (scheme: string, provider: vscode.TextSearchProviderNew) => { - checkProposedApiEnabled(extension, 'textSearchProviderNew'); + registerTextSearchProvider2: (scheme: string, provider: vscode.TextSearchProvider2) => { + checkProposedApiEnabled(extension, 'textSearchProvider2'); return extHostSearch.registerTextSearchProvider(scheme, provider); }, - registerAITextSearchProviderNew: (scheme: string, provider: vscode.AITextSearchProviderNew) => { + registerAITextSearchProvider2: (scheme: string, provider: vscode.AITextSearchProvider2) => { // there are some dependencies on textSearchProvider, so we need to check for both - checkProposedApiEnabled(extension, 'aiTextSearchProviderNew'); - checkProposedApiEnabled(extension, 'textSearchProviderNew'); + checkProposedApiEnabled(extension, 'aiTextSearchProvider2'); + checkProposedApiEnabled(extension, 'textSearchProvider2'); return extHostSearch.registerAITextSearchProvider(scheme, provider); }, registerRemoteAuthorityResolver: (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { @@ -1792,8 +1792,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I InlineEdit: extHostTypes.InlineEdit, InlineEditTriggerKind: extHostTypes.InlineEditTriggerKind, ExcludeSettingOptions: ExcludeSettingOptions, - TextSearchContextNew: TextSearchContextNew, - TextSearchMatchNew: TextSearchMatchNew, + TextSearchContext2: TextSearchContext2, + TextSearchMatch2: TextSearchMatch2, TextSearchCompleteMessageTypeNew: TextSearchCompleteMessageType, }; }; diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts index fbb111a79d1f1..cac2fb809143b 100644 --- a/src/vs/workbench/api/common/extHostSearch.ts +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -22,9 +22,9 @@ export interface IExtHostSearch extends ExtHostSearchShape { registerTextSearchProviderOld(scheme: string, provider: vscode.TextSearchProvider): IDisposable; registerAITextSearchProviderOld(scheme: string, provider: vscode.AITextSearchProvider): IDisposable; registerFileSearchProviderOld(scheme: string, provider: vscode.FileSearchProvider): IDisposable; - registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProviderNew): IDisposable; - registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProviderNew): IDisposable; - registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProviderNew): IDisposable; + registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider2): IDisposable; + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider2): IDisposable; + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider2): IDisposable; doInternalFileSearchWithCustomCallback(query: IFileQuery, token: CancellationToken, handleFileMatch: (data: URI[]) => void): Promise; } @@ -35,13 +35,13 @@ export class ExtHostSearch implements IExtHostSearch { protected readonly _proxy: MainThreadSearchShape = this.extHostRpc.getProxy(MainContext.MainThreadSearch); protected _handlePool: number = 0; - private readonly _textSearchProvider = new Map(); + private readonly _textSearchProvider = new Map(); private readonly _textSearchUsedSchemes = new Set(); - private readonly _aiTextSearchProvider = new Map(); + private readonly _aiTextSearchProvider = new Map(); private readonly _aiTextSearchUsedSchemes = new Set(); - private readonly _fileSearchProvider = new Map(); + private readonly _fileSearchProvider = new Map(); private readonly _fileSearchUsedSchemes = new Set(); private readonly _fileSearchManager = new FileSearchManager(); @@ -72,7 +72,7 @@ export class ExtHostSearch implements IExtHostSearch { }); } - registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProviderNew): IDisposable { + registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider2): IDisposable { if (this._textSearchUsedSchemes.has(scheme)) { throw new Error(`a text search provider for the scheme '${scheme}' is already registered`); } @@ -104,7 +104,7 @@ export class ExtHostSearch implements IExtHostSearch { }); } - registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProviderNew): IDisposable { + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider2): IDisposable { if (this._aiTextSearchUsedSchemes.has(scheme)) { throw new Error(`an AI text search provider for the scheme '${scheme}'is already registered`); } @@ -136,7 +136,7 @@ export class ExtHostSearch implements IExtHostSearch { }); } - registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProviderNew): IDisposable { + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider2): IDisposable { if (this._fileSearchUsedSchemes.has(scheme)) { throw new Error(`a file search provider for the scheme '${scheme}' is already registered`); } @@ -208,14 +208,14 @@ export class ExtHostSearch implements IExtHostSearch { return provider.name ?? 'AI'; } - protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProviderNew): TextSearchManager { + protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider2): TextSearchManager { return new TextSearchManager({ query, provider }, { readdir: resource => Promise.resolve([]), toCanonicalName: encoding => encoding }, 'textSearchProvider'); } - protected createAITextSearchManager(query: IAITextQuery, provider: vscode.AITextSearchProviderNew): TextSearchManager { + protected createAITextSearchManager(query: IAITextQuery, provider: vscode.AITextSearchProvider2): TextSearchManager { return new TextSearchManager({ query, provider }, { readdir: resource => Promise.resolve([]), toCanonicalName: encoding => encoding diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index abb49a3cc2717..2d2305b81f711 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -34,7 +34,7 @@ import type * as vscode from 'vscode'; import { ExtHostWorkspaceShape, IRelativePatternDto, IWorkspaceData, MainContext, MainThreadMessageOptions, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol.js'; import { revive } from '../../../base/common/marshalling.js'; import { AuthInfo, Credentials } from '../../../platform/request/common/request.js'; -import { ExcludeSettingOptions, TextSearchContextNew, TextSearchMatchNew } from '../../services/search/common/searchExtTypes.js'; +import { ExcludeSettingOptions, TextSearchContext2, TextSearchMatch2 } from '../../services/search/common/searchExtTypes.js'; export interface IExtHostWorkspaceProvider { getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise; @@ -571,8 +571,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac return result.flat(); } - findTextInFilesNew(query: vscode.TextSearchQueryNew, options: vscode.FindTextInFilesOptionsNew | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): vscode.FindTextInFilesResponse { - this._logService.trace(`extHostWorkspace#findTextInFilesNew: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFilesNew`); + findTextInFiles2(query: vscode.TextSearchQuery2, options: vscode.FindTextInFilesOptions2 | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): vscode.FindTextInFilesResponse { + this._logService.trace(`extHostWorkspace#findTextInFiles2: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFiles2`); const getOptions = (include: vscode.GlobPattern | undefined): QueryOptions => { @@ -623,12 +623,12 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac (result, uri) => progressEmitter.fire({ result, uri }), token ); - const asyncIterable = new AsyncIterableObject(async emitter => { + const asyncIterable = new AsyncIterableObject(async emitter => { disposables.add(progressEmitter.event(e => { const result = e.result; const uri = e.uri; if (resultIsMatch(result)) { - emitter.emitOne(new TextSearchMatchNew( + emitter.emitOne(new TextSearchMatch2( uri, result.rangeLocations.map((range) => ({ previewRange: new Range(range.preview.startLineNumber, range.preview.startColumn, range.preview.endLineNumber, range.preview.endColumn), @@ -638,7 +638,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac )); } else { - emitter.emitOne(new TextSearchContextNew( + emitter.emitOne(new TextSearchContext2( uri, result.text, result.lineNumber diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index c91dd9046075a..879d589c7abd9 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -161,7 +161,7 @@ export class NativeExtHostSearch extends ExtHostSearch implements IDisposable { return super.$clearCache(cacheKey); } - protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProviderNew): TextSearchManager { + protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider2): TextSearchManager { return new NativeTextSearchManager(query, provider, undefined, 'textSearchProvider'); } } diff --git a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts index 0c019f59b2c5f..37d95aec3ec8c 100644 --- a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts @@ -1126,7 +1126,7 @@ suite('ExtHostWorkspace', function () { }); }); - suite('findTextInFilesNew -', function () { + suite('findTextInFiles2 -', function () { test('no include', async () => { const root = '/project/foo'; const rpcProtocol = new TestRPCProtocol(); @@ -1144,7 +1144,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - await (ws.findTextInFilesNew({ pattern: 'foo' }, {}, new ExtensionIdentifier('test'))).complete; + await (ws.findTextInFiles2({ pattern: 'foo' }, {}, new ExtensionIdentifier('test'))).complete; assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -1165,7 +1165,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - await (ws.findTextInFilesNew({ pattern: 'foo' }, { include: ['**/files'] }, new ExtensionIdentifier('test'))).complete; + await (ws.findTextInFiles2({ pattern: 'foo' }, { include: ['**/files'] }, new ExtensionIdentifier('test'))).complete; assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -1186,7 +1186,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - await (ws.findTextInFilesNew({ pattern: 'foo' }, { include: [new RelativePattern('/other/folder', 'glob/**')] }, new ExtensionIdentifier('test'))).complete; + await (ws.findTextInFiles2({ pattern: 'foo' }, { include: [new RelativePattern('/other/folder', 'glob/**')] }, new ExtensionIdentifier('test'))).complete; assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -1204,7 +1204,7 @@ suite('ExtHostWorkspace', function () { const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); const token = CancellationToken.Cancelled; - await (ws.findTextInFilesNew({ pattern: 'foo' }, undefined, new ExtensionIdentifier('test'), token)).complete; + await (ws.findTextInFiles2({ pattern: 'foo' }, undefined, new ExtensionIdentifier('test'), token)).complete; assert(!mainThreadCalled, '!mainThreadCalled'); }); @@ -1226,7 +1226,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - await (ws.findTextInFilesNew({ pattern: 'foo' }, { exclude: [new RelativePattern('/other/folder', 'glob/**')] }, new ExtensionIdentifier('test'))).complete; + await (ws.findTextInFiles2({ pattern: 'foo' }, { exclude: [new RelativePattern('/other/folder', 'glob/**')] }, new ExtensionIdentifier('test'))).complete; assert(mainThreadCalled, 'mainThreadCalled'); }); diff --git a/src/vs/workbench/api/test/node/extHostSearch.test.ts b/src/vs/workbench/api/test/node/extHostSearch.test.ts index 75e6b2f34c6bd..94380c10ec4f6 100644 --- a/src/vs/workbench/api/test/node/extHostSearch.test.ts +++ b/src/vs/workbench/api/test/node/extHostSearch.test.ts @@ -170,7 +170,7 @@ suite('ExtHostSearch', () => { this._pfs = mockPFS as any; } - protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProviderNew): TextSearchManager { + protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider2): TextSearchManager { return new NativeTextSearchManager(query, provider, this._pfs); } }); diff --git a/src/vs/workbench/services/search/common/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts index 1b56d2cbbe669..bd539edb41d1e 100644 --- a/src/vs/workbench/services/search/common/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -11,7 +11,7 @@ import * as resources from '../../../../base/common/resources.js'; import { StopWatch } from '../../../../base/common/stopwatch.js'; import { URI } from '../../../../base/common/uri.js'; import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery, QueryGlobTester, resolvePatternsForProvider, hasSiblingFn, excludeToGlobPattern, DEFAULT_MAX_SEARCH_RESULTS } from './search.js'; -import { FileSearchProviderFolderOptions, FileSearchProviderNew, FileSearchProviderOptions } from './searchExtTypes.js'; +import { FileSearchProviderFolderOptions, FileSearchProvider2, FileSearchProviderOptions } from './searchExtTypes.js'; import { OldFileSearchProviderConverter } from './searchExtConversionTypes.js'; import { FolderQuerySearchTree } from './folderQuerySearchTree.js'; @@ -54,7 +54,7 @@ class FileSearchEngine { private globalExcludePattern?: glob.ParsedExpression; - constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionLifecycle?: SessionLifecycle) { + constructor(private config: IFileQuery, private provider: FileSearchProvider2, private sessionLifecycle?: SessionLifecycle) { this.filePattern = config.filePattern; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || undefined; @@ -334,7 +334,7 @@ export class FileSearchManager { private readonly sessions = new Map(); - fileSearch(config: IFileQuery, provider: FileSearchProviderNew, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { + fileSearch(config: IFileQuery, provider: FileSearchProvider2, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { const sessionTokenSource = this.getSessionTokenSource(config.cacheKey); const engine = new FileSearchEngine(config, provider, sessionTokenSource); diff --git a/src/vs/workbench/services/search/common/searchExtConversionTypes.ts b/src/vs/workbench/services/search/common/searchExtConversionTypes.ts index 098b56480cbbd..96bff83165652 100644 --- a/src/vs/workbench/services/search/common/searchExtConversionTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtConversionTypes.ts @@ -13,7 +13,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { URI } from '../../../../base/common/uri.js'; import { IProgress } from '../../../../platform/progress/common/progress.js'; import { DEFAULT_TEXT_SEARCH_PREVIEW_OPTIONS } from './search.js'; -import { Range, FileSearchProviderNew, FileSearchProviderOptions, ProviderResult, TextSearchCompleteNew, TextSearchContextNew, TextSearchMatchNew, TextSearchProviderNew, TextSearchProviderOptions, TextSearchQueryNew, TextSearchResultNew, AITextSearchProviderNew, TextSearchCompleteMessage } from './searchExtTypes.js'; +import { Range, FileSearchProvider2, FileSearchProviderOptions, ProviderResult, TextSearchComplete2, TextSearchContext2, TextSearchMatch2, TextSearchProvider2, TextSearchProviderOptions, TextSearchQuery2, TextSearchResult2, AITextSearchProvider2, TextSearchCompleteMessage } from './searchExtTypes.js'; // old types that are retained for backward compatibility // TODO: delete this when search apis are adopted by all first-party extensions @@ -472,7 +472,7 @@ function newToOldFileProviderOptions(options: FileSearchProviderOptions): FileSe } satisfies FileSearchOptions)); } -export class OldFileSearchProviderConverter implements FileSearchProviderNew { +export class OldFileSearchProviderConverter implements FileSearchProvider2 { constructor(private provider: FileSearchProvider) { } provideFileSearchResults(pattern: string, options: FileSearchProviderOptions, token: CancellationToken): ProviderResult { @@ -517,23 +517,23 @@ export function newToOldPreviewOptions(options: { }; } -export function oldToNewTextSearchResult(result: TextSearchResult): TextSearchResultNew { +export function oldToNewTextSearchResult(result: TextSearchResult): TextSearchResult2 { if (isTextSearchMatch(result)) { const ranges = asArray(result.ranges).map((r, i) => { const previewArr = asArray(result.preview.matches); const matchingPreviewRange = previewArr[i]; return { sourceRange: r, previewRange: matchingPreviewRange }; }); - return new TextSearchMatchNew(result.uri, ranges, result.preview.text); + return new TextSearchMatch2(result.uri, ranges, result.preview.text); } else { - return new TextSearchContextNew(result.uri, result.text, result.lineNumber); + return new TextSearchContext2(result.uri, result.text, result.lineNumber); } } -export class OldTextSearchProviderConverter implements TextSearchProviderNew { +export class OldTextSearchProviderConverter implements TextSearchProvider2 { constructor(private provider: TextSearchProvider) { } - provideTextSearchResults(query: TextSearchQueryNew, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult { + provideTextSearchResults(query: TextSearchQuery2, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult { const progressShim = (oldResult: TextSearchResult) => { if (!validateProviderResult(oldResult)) { @@ -556,19 +556,19 @@ export class OldTextSearchProviderConverter implements TextSearchProviderNew { return { limitHit: e.limitHit, message: coalesce(asArray(e.message)) - } satisfies TextSearchCompleteNew; + } satisfies TextSearchComplete2; }); } } -export class OldAITextSearchProviderConverter implements AITextSearchProviderNew { +export class OldAITextSearchProviderConverter implements AITextSearchProvider2 { public readonly name?: string; constructor(private provider: AITextSearchProvider) { this.name = this.provider.name; } - provideAITextSearchResults(query: string, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult { + provideAITextSearchResults(query: string, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult { const progressShim = (oldResult: TextSearchResult) => { if (!validateProviderResult(oldResult)) { return; @@ -590,7 +590,7 @@ export class OldAITextSearchProviderConverter implements AITextSearchProviderNew return { limitHit: e.limitHit, message: coalesce(asArray(e.message)) - } satisfies TextSearchCompleteNew; + } satisfies TextSearchComplete2; }); } } diff --git a/src/vs/workbench/services/search/common/searchExtTypes.ts b/src/vs/workbench/services/search/common/searchExtTypes.ts index 285f5a395a4ec..7dd13ebc258d3 100644 --- a/src/vs/workbench/services/search/common/searchExtTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtTypes.ts @@ -93,7 +93,7 @@ export type GlobPattern = string | RelativePattern; /** * The parameters of a query for text search. */ -export interface TextSearchQueryNew { +export interface TextSearchQuery2 { /** * The text pattern to search for. */ @@ -214,7 +214,7 @@ export interface TextSearchProviderOptions { /** * Information collected when text search is complete. */ -export interface TextSearchCompleteNew { +export interface TextSearchComplete2 { /** * Whether the search hit the limit on the maximum number of search results. * `maxResults` on [`TextSearchOptions`](#TextSearchOptions) specifies the max number of results. @@ -285,9 +285,9 @@ export interface FileSearchProviderOptions { } /** - * The main match information for a {@link TextSearchResultNew}. + * The main match information for a {@link TextSearchResult2}. */ -export class TextSearchMatchNew { +export class TextSearchMatch2 { /** * @param uri The uri for the matching document. * @param ranges The ranges associated with this match. @@ -301,9 +301,9 @@ export class TextSearchMatchNew { } /** - * The potential context information for a {@link TextSearchResultNew}. + * The potential context information for a {@link TextSearchResult2}. */ -export class TextSearchContextNew { +export class TextSearchContext2 { /** * @param uri The uri for the matching document. * @param text The line of context text. @@ -318,7 +318,7 @@ export class TextSearchContextNew { /** * A result payload for a text search, pertaining to matches within a single file. */ -export type TextSearchResultNew = TextSearchMatchNew | TextSearchContextNew; +export type TextSearchResult2 = TextSearchMatch2 | TextSearchContext2; /** @@ -330,7 +330,7 @@ export type TextSearchResultNew = TextSearchMatchNew | TextSearchContextNew; * The FileSearchProvider will be invoked on every keypress in quickaccess. When `workspace.findFiles` is called, it will be invoked with an empty query string, * and in that case, every file in the folder should be returned. */ -export interface FileSearchProviderNew { +export interface FileSearchProvider2 { /** * Provide the set of files that match a certain file path pattern. * @param query The parameters for this query. @@ -344,7 +344,7 @@ export interface FileSearchProviderNew { /** * A TextSearchProvider provides search results for text results inside files in the workspace. */ -export interface TextSearchProviderNew { +export interface TextSearchProvider2 { /** * Provide results that match the given text pattern. * @param query The parameters for this query. @@ -352,13 +352,13 @@ export interface TextSearchProviderNew { * @param progress A progress callback that must be invoked for all results. * @param token A cancellation token. */ - provideTextSearchResults(query: TextSearchQueryNew, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult; + provideTextSearchResults(query: TextSearchQuery2, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult; } /** * Information collected when text search is complete. */ -export interface TextSearchCompleteNew { +export interface TextSearchComplete2 { /** * Whether the search hit the limit on the maximum number of search results. * `maxResults` on {@linkcode TextSearchOptions} specifies the max number of results. @@ -377,13 +377,13 @@ export interface TextSearchCompleteNew { * * Commands may optionally return { triggerSearch: true } to signal to the editor that the original search should run be again. */ - message?: TextSearchCompleteMessageNew[]; + message?: TextSearchCompleteMessage2[]; } /** * A message regarding a completed search. */ -export interface TextSearchCompleteMessageNew { +export interface TextSearchCompleteMessage2 { /** * Markdown text of the message. */ @@ -409,7 +409,7 @@ export interface TextSearchCompleteMessageNew { * The FileSearchProvider will be invoked on every keypress in quickaccess. When `workspace.findFiles` is called, it will be invoked with an empty query string, * and in that case, every file in the folder should be returned. */ -export interface FileSearchProviderNew { +export interface FileSearchProvider2 { /** * Provide the set of files that match a certain file path pattern. * @param query The parameters for this query. @@ -423,7 +423,7 @@ export interface FileSearchProviderNew { /** * A TextSearchProvider provides search results for text results inside files in the workspace. */ -export interface TextSearchProviderNew { +export interface TextSearchProvider2 { /** * Provide results that match the given text pattern. * @param query The parameters for this query. @@ -431,13 +431,13 @@ export interface TextSearchProviderNew { * @param progress A progress callback that must be invoked for all results. * @param token A cancellation token. */ - provideTextSearchResults(query: TextSearchQueryNew, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult; + provideTextSearchResults(query: TextSearchQuery2, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult; } /** * Information collected when text search is complete. */ -export interface TextSearchCompleteNew { +export interface TextSearchComplete2 { /** * Whether the search hit the limit on the maximum number of search results. * `maxResults` on {@linkcode TextSearchOptions} specifies the max number of results. @@ -456,13 +456,13 @@ export interface TextSearchCompleteNew { * * Commands may optionally return { triggerSearch: true } to signal to the editor that the original search should run be again. */ - message?: TextSearchCompleteMessageNew[]; + message?: TextSearchCompleteMessage2[]; } /** * A message regarding a completed search. */ -export interface TextSearchCompleteMessageNew { +export interface TextSearchCompleteMessage2 { /** * Markdown text of the message. */ @@ -527,7 +527,7 @@ export interface TextSearchCompleteMessage { /** * An AITextSearchProvider provides additional AI text search results in the workspace. */ -export interface AITextSearchProviderNew { +export interface AITextSearchProvider2 { /** * The name of the AI searcher. Will be displayed as `{name} Results` in the Search View. @@ -543,5 +543,5 @@ export interface AITextSearchProviderNew { * @param progress A progress callback that must be invoked for all results. * @param token A cancellation token. */ - provideAITextSearchResults(query: string, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult; + provideAITextSearchResults(query: string, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult; } diff --git a/src/vs/workbench/services/search/common/textSearchManager.ts b/src/vs/workbench/services/search/common/textSearchManager.ts index 8904ef2a1b611..5d8e858833b25 100644 --- a/src/vs/workbench/services/search/common/textSearchManager.ts +++ b/src/vs/workbench/services/search/common/textSearchManager.ts @@ -12,18 +12,18 @@ import * as resources from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; import { FolderQuerySearchTree } from './folderQuerySearchTree.js'; import { DEFAULT_MAX_SEARCH_RESULTS, hasSiblingPromiseFn, IAITextQuery, IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, excludeToGlobPattern, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, ITextSearchStats, QueryGlobTester, QueryType, resolvePatternsForProvider, ISearchRange, DEFAULT_TEXT_SEARCH_PREVIEW_OPTIONS } from './search.js'; -import { AITextSearchProviderNew, TextSearchCompleteNew, TextSearchMatchNew, TextSearchProviderFolderOptions, TextSearchProviderNew, TextSearchProviderOptions, TextSearchQueryNew, TextSearchResultNew } from './searchExtTypes.js'; +import { AITextSearchProvider2, TextSearchComplete2, TextSearchMatch2, TextSearchProviderFolderOptions, TextSearchProvider2, TextSearchProviderOptions, TextSearchQuery2, TextSearchResult2 } from './searchExtTypes.js'; export interface IFileUtils { readdir: (resource: URI) => Promise; toCanonicalName: (encoding: string) => string; } interface IAITextQueryProviderPair { - query: IAITextQuery; provider: AITextSearchProviderNew; + query: IAITextQuery; provider: AITextSearchProvider2; } interface ITextQueryProviderPair { - query: ITextQuery; provider: TextSearchProviderNew; + query: ITextQuery; provider: TextSearchProvider2; } interface FolderQueryInfo { queryTester: QueryGlobTester; @@ -54,14 +54,14 @@ export class TextSearchManager { this.collector = new TextSearchResultsCollector(onProgress); let isCanceled = false; - const onResult = (result: TextSearchResultNew, folderIdx: number) => { + const onResult = (result: TextSearchResult2, folderIdx: number) => { if (isCanceled) { return; } if (!this.isLimitHit) { const resultSize = this.resultSize(result); - if (result instanceof TextSearchMatchNew && typeof this.query.maxResults === 'number' && this.resultCount + resultSize > this.query.maxResults) { + if (result instanceof TextSearchMatch2 && typeof this.query.maxResults === 'number' && this.resultCount + resultSize > this.query.maxResults) { this.isLimitHit = true; isCanceled = true; tokenSource.cancel(); @@ -71,7 +71,7 @@ export class TextSearchManager { const newResultSize = this.resultSize(result); this.resultCount += newResultSize; - const a = result instanceof TextSearchMatchNew; + const a = result instanceof TextSearchMatch2; if (newResultSize > 0 || !a) { this.collector!.add(result, folderIdx); @@ -99,14 +99,14 @@ export class TextSearchManager { }); } - private getMessagesFromResults(result: TextSearchCompleteNew | null | undefined) { + private getMessagesFromResults(result: TextSearchComplete2 | null | undefined) { if (!result?.message) { return []; } if (Array.isArray(result.message)) { return result.message; } return [result.message]; } - private resultSize(result: TextSearchResultNew): number { - if (result instanceof TextSearchMatchNew) { + private resultSize(result: TextSearchResult2): number { + if (result instanceof TextSearchMatch2) { return Array.isArray(result.ranges) ? result.ranges.length : 1; @@ -117,11 +117,11 @@ export class TextSearchManager { } } - private trimResultToSize(result: TextSearchMatchNew, size: number): TextSearchMatchNew { - return new TextSearchMatchNew(result.uri, result.ranges.slice(0, size), result.previewText); + private trimResultToSize(result: TextSearchMatch2, size: number): TextSearchMatch2 { + return new TextSearchMatch2(result.uri, result.ranges.slice(0, size), result.previewText); } - private async doSearch(folderQueries: IFolderQuery[], onResult: (result: TextSearchResultNew, folderIdx: number) => void, token: CancellationToken): Promise { + private async doSearch(folderQueries: IFolderQuery[], onResult: (result: TextSearchResult2, folderIdx: number) => void, token: CancellationToken): Promise { const folderMappings: FolderQuerySearchTree = new FolderQuerySearchTree( folderQueries, (fq, i) => { @@ -133,7 +133,7 @@ export class TextSearchManager { const testingPs: Promise[] = []; const progress = { - report: (result: TextSearchResultNew) => { + report: (result: TextSearchResult2) => { if (result.uri === undefined) { throw Error('Text search result URI is undefined. Please check provider implementation.'); @@ -220,7 +220,7 @@ export class TextSearchManager { } } -function patternInfoToQuery(patternInfo: IPatternInfo): TextSearchQueryNew { +function patternInfoToQuery(patternInfo: IPatternInfo): TextSearchQuery2 { return { isCaseSensitive: patternInfo.isCaseSensitive || false, isRegExp: patternInfo.isRegExp || false, @@ -241,7 +241,7 @@ export class TextSearchResultsCollector { this._batchedCollector = new BatchedCollector(512, items => this.sendItems(items)); } - add(data: TextSearchResultNew, folderIdx: number): void { + add(data: TextSearchResult2, folderIdx: number): void { // Collects TextSearchResults into IInternalFileMatches and collates using BatchedCollector. // This is efficient for ripgrep which sends results back one file at a time. It wouldn't be efficient for other search // providers that send results in random order. We could do this step afterwards instead. @@ -278,9 +278,9 @@ export class TextSearchResultsCollector { } } -function extensionResultToFrontendResult(data: TextSearchResultNew): ITextSearchResult { +function extensionResultToFrontendResult(data: TextSearchResult2): ITextSearchResult { // Warning: result from RipgrepTextSearchEH has fake Range. Don't depend on any other props beyond these... - if (data instanceof TextSearchMatchNew) { + if (data instanceof TextSearchMatch2) { return { previewText: data.previewText, rangeLocations: data.ranges.map(r => ({ diff --git a/src/vs/workbench/services/search/node/ripgrepSearchProvider.ts b/src/vs/workbench/services/search/node/ripgrepSearchProvider.ts index 9811308b833e4..d9577cd7d318a 100644 --- a/src/vs/workbench/services/search/node/ripgrepSearchProvider.ts +++ b/src/vs/workbench/services/search/node/ripgrepSearchProvider.ts @@ -6,19 +6,19 @@ import { CancellationTokenSource, CancellationToken } from '../../../../base/common/cancellation.js'; import { OutputChannel } from './ripgrepSearchUtils.js'; import { RipgrepTextSearchEngine } from './ripgrepTextSearchEngine.js'; -import { TextSearchProviderNew, TextSearchCompleteNew, TextSearchResultNew, TextSearchQueryNew, TextSearchProviderOptions, } from '../common/searchExtTypes.js'; +import { TextSearchProvider2, TextSearchComplete2, TextSearchResult2, TextSearchQuery2, TextSearchProviderOptions, } from '../common/searchExtTypes.js'; import { Progress } from '../../../../platform/progress/common/progress.js'; import { Schemas } from '../../../../base/common/network.js'; import type { RipgrepTextSearchOptions } from '../common/searchExtTypesInternal.js'; -export class RipgrepSearchProvider implements TextSearchProviderNew { +export class RipgrepSearchProvider implements TextSearchProvider2 { private inProgress: Set = new Set(); constructor(private outputChannel: OutputChannel, private getNumThreads: () => Promise) { process.once('exit', () => this.dispose()); } - async provideTextSearchResults(query: TextSearchQueryNew, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): Promise { + async provideTextSearchResults(query: TextSearchQuery2, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): Promise { const numThreads = await this.getNumThreads(); const engine = new RipgrepTextSearchEngine(this.outputChannel, numThreads); @@ -36,13 +36,13 @@ export class RipgrepSearchProvider implements TextSearchProviderNew { // Ripgrep search engine can only provide file-scheme results, but we want to use it to search some schemes that are backed by the filesystem, but with some other provider as the frontend, // case in point vscode-userdata. In these cases we translate the query to a file, and translate the results back to the frontend scheme. const translatedOptions = { ...extendedOptions, folder: folderOption.folder.with({ scheme: Schemas.file }) }; - const progressTranslator = new Progress(data => progress.report({ ...data, uri: data.uri.with({ scheme: folderOption.folder.scheme }) })); + const progressTranslator = new Progress(data => progress.report({ ...data, uri: data.uri.with({ scheme: folderOption.folder.scheme }) })); return this.withToken(token, token => engine.provideTextSearchResultsWithRgOptions(query, translatedOptions, progressTranslator, token)); } else { return this.withToken(token, token => engine.provideTextSearchResultsWithRgOptions(query, extendedOptions, progress, token)); } })).then((e => { - const complete: TextSearchCompleteNew = { + const complete: TextSearchComplete2 = { // todo: get this to actually check limitHit: e.some(complete => !!complete && complete.limitHit) }; diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 3ab8205f2b194..188dc598f75d0 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -14,7 +14,7 @@ import { createRegExp, escapeRegExpCharacters } from '../../../../base/common/st import { URI } from '../../../../base/common/uri.js'; import { Progress } from '../../../../platform/progress/common/progress.js'; import { DEFAULT_MAX_SEARCH_RESULTS, IExtendedExtensionSearchOptions, ITextSearchPreviewOptions, SearchError, SearchErrorCode, serializeSearchError, TextSearchMatch } from '../common/search.js'; -import { Range, TextSearchCompleteNew, TextSearchContextNew, TextSearchMatchNew, TextSearchProviderOptions, TextSearchQueryNew, TextSearchResultNew } from '../common/searchExtTypes.js'; +import { Range, TextSearchComplete2, TextSearchContext2, TextSearchMatch2, TextSearchProviderOptions, TextSearchQuery2, TextSearchResult2 } from '../common/searchExtTypes.js'; import { AST as ReAST, RegExpParser, RegExpVisitor } from 'vscode-regexpp'; import { rgPath } from '@vscode/ripgrep'; import { anchorGlob, IOutputChannel, Maybe, rangeToSearchRange, searchRangeToRange } from './ripgrepSearchUtils.js'; @@ -28,7 +28,7 @@ export class RipgrepTextSearchEngine { constructor(private outputChannel: IOutputChannel, private readonly _numThreads?: number | undefined) { } - provideTextSearchResults(query: TextSearchQueryNew, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): Promise { + provideTextSearchResults(query: TextSearchQuery2, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): Promise { return Promise.all(options.folderOptions.map(folderOption => { const extendedOptions: RipgrepTextSearchOptions = { folderOptions: folderOption, @@ -40,7 +40,7 @@ export class RipgrepTextSearchEngine { }; return this.provideTextSearchResultsWithRgOptions(query, extendedOptions, progress, token); })).then((e => { - const complete: TextSearchCompleteNew = { + const complete: TextSearchComplete2 = { // todo: get this to actually check limitHit: e.some(complete => !!complete && complete.limitHit) }; @@ -48,7 +48,7 @@ export class RipgrepTextSearchEngine { })); } - provideTextSearchResultsWithRgOptions(query: TextSearchQueryNew, options: RipgrepTextSearchOptions, progress: Progress, token: CancellationToken): Promise { + provideTextSearchResultsWithRgOptions(query: TextSearchQuery2, options: RipgrepTextSearchOptions, progress: Progress, token: CancellationToken): Promise { this.outputChannel.appendLine(`provideTextSearchResults ${query.pattern}, ${JSON.stringify({ ...options, ...{ @@ -81,7 +81,7 @@ export class RipgrepTextSearchEngine { let gotResult = false; const ripgrepParser = new RipgrepParser(options.maxResults ?? DEFAULT_MAX_SEARCH_RESULTS, options.folderOptions.folder, newToOldPreviewOptions(options.previewOptions)); - ripgrepParser.on('result', (match: TextSearchResultNew) => { + ripgrepParser.on('result', (match: TextSearchResult2) => { gotResult = true; dataWithoutResult = ''; progress.report(match); @@ -223,7 +223,7 @@ export class RipgrepParser extends EventEmitter { } - override on(event: 'result', listener: (result: TextSearchResultNew) => void): this; + override on(event: 'result', listener: (result: TextSearchResult2) => void): this; override on(event: 'hitLimit', listener: () => void): this; override on(event: string, listener: (...args: any[]) => void): this { super.on(event, listener); @@ -295,7 +295,7 @@ export class RipgrepParser extends EventEmitter { } } - private createTextSearchMatch(data: IRgMatch, uri: URI): TextSearchMatchNew { + private createTextSearchMatch(data: IRgMatch, uri: URI): TextSearchMatch2 { const lineNumber = data.line_number - 1; const fullText = bytesOrTextToString(data.lines); const fullTextBytes = Buffer.from(fullText); @@ -351,7 +351,7 @@ export class RipgrepParser extends EventEmitter { const searchRange = mapArrayOrNot(ranges, rangeToSearchRange); const internalResult = new TextSearchMatch(fullText, searchRange, this.previewOptions); - return new TextSearchMatchNew( + return new TextSearchMatch2( uri, internalResult.rangeLocations.map(e => ( { @@ -362,16 +362,16 @@ export class RipgrepParser extends EventEmitter { internalResult.previewText); } - private createTextSearchContexts(data: IRgMatch, uri: URI): TextSearchContextNew[] { + private createTextSearchContexts(data: IRgMatch, uri: URI): TextSearchContext2[] { const text = bytesOrTextToString(data.lines); const startLine = data.line_number; return text .replace(/\r?\n$/, '') .split('\n') - .map((line, i) => new TextSearchContextNew(uri, line, startLine + i)); + .map((line, i) => new TextSearchContext2(uri, line, startLine + i)); } - private onResult(match: TextSearchResultNew): void { + private onResult(match: TextSearchResult2): void { this.emit('result', match); } } @@ -400,7 +400,7 @@ function getNumLinesAndLastNewlineLength(text: string): { numLines: number; last } // exported for testing -export function getRgArgs(query: TextSearchQueryNew, options: RipgrepTextSearchOptions): string[] { +export function getRgArgs(query: TextSearchQuery2, options: RipgrepTextSearchOptions): string[] { const args = ['--hidden', '--no-require-git']; args.push(query.isCaseSensitive ? '--case-sensitive' : '--ignore-case'); diff --git a/src/vs/workbench/services/search/node/textSearchManager.ts b/src/vs/workbench/services/search/node/textSearchManager.ts index 56744c247b77c..22b096f14f0d0 100644 --- a/src/vs/workbench/services/search/node/textSearchManager.ts +++ b/src/vs/workbench/services/search/node/textSearchManager.ts @@ -6,12 +6,12 @@ import { toCanonicalName } from '../../textfile/common/encoding.js'; import * as pfs from '../../../../base/node/pfs.js'; import { ITextQuery, ITextSearchStats } from '../common/search.js'; -import { TextSearchProviderNew } from '../common/searchExtTypes.js'; +import { TextSearchProvider2 } from '../common/searchExtTypes.js'; import { TextSearchManager } from '../common/textSearchManager.js'; export class NativeTextSearchManager extends TextSearchManager { - constructor(query: ITextQuery, provider: TextSearchProviderNew, _pfs: typeof pfs = pfs, processType: ITextSearchStats['type'] = 'searchProcess') { + constructor(query: ITextQuery, provider: TextSearchProvider2, _pfs: typeof pfs = pfs, processType: ITextSearchStats['type'] = 'searchProcess') { super({ query, provider }, { readdir: resource => _pfs.Promises.readdir(resource.fsPath), toCanonicalName: name => toCanonicalName(name) diff --git a/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test.ts b/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test.ts index 59bdb0ca8f6fa..1223c793f6430 100644 --- a/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test.ts +++ b/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test.ts @@ -7,7 +7,7 @@ import assert from 'assert'; import { joinPath } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; import { fixRegexNewline, IRgMatch, IRgMessage, RipgrepParser, unicodeEscapesToPCRE2, fixNewline, getRgArgs, performBraceExpansionForRipgrep } from '../../node/ripgrepTextSearchEngine.js'; -import { Range, TextSearchMatchNew, TextSearchQueryNew, TextSearchResultNew } from '../../common/searchExtTypes.js'; +import { Range, TextSearchMatch2, TextSearchQuery2, TextSearchResult2 } from '../../common/searchExtTypes.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { RipgrepTextSearchOptions } from '../../common/searchExtTypesInternal.js'; import { DEFAULT_TEXT_SEARCH_PREVIEW_OPTIONS } from '../../common/search.js'; @@ -106,10 +106,10 @@ suite('RipgrepTextSearchEngine', () => { suite('RipgrepParser', () => { const TEST_FOLDER = URI.file('/foo/bar'); - function testParser(inputData: string[], expectedResults: TextSearchResultNew[]): void { + function testParser(inputData: string[], expectedResults: TextSearchResult2[]): void { const testParser = new RipgrepParser(1000, TEST_FOLDER, DEFAULT_TEXT_SEARCH_PREVIEW_OPTIONS); - const actualResults: TextSearchResultNew[] = []; + const actualResults: TextSearchResult2[] = []; testParser.on('result', r => { actualResults.push(r); }); @@ -148,7 +148,7 @@ suite('RipgrepTextSearchEngine', () => { makeRgMatch('file1.js', 'foobar', 4, [{ start: 3, end: 6 }]) ], [ - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'file1.js'), [{ previewRange: new Range(0, 3, 0, 6), @@ -167,7 +167,7 @@ suite('RipgrepTextSearchEngine', () => { makeRgMatch('app2/file3.js', 'foobar', 4, [{ start: 3, end: 6 }]), ], [ - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'file1.js'), [{ previewRange: new Range(0, 3, 0, 6), @@ -175,7 +175,7 @@ suite('RipgrepTextSearchEngine', () => { }], 'foobar' ), - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'app/file2.js'), [{ previewRange: new Range(0, 3, 0, 6), @@ -183,7 +183,7 @@ suite('RipgrepTextSearchEngine', () => { }], 'foobar' ), - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'app2/file3.js'), [{ previewRange: new Range(0, 3, 0, 6), @@ -212,7 +212,7 @@ suite('RipgrepTextSearchEngine', () => { dataStrs[2].substring(25) ], [ - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'file1.js'), [{ previewRange: new Range(0, 3, 0, 7), @@ -220,7 +220,7 @@ suite('RipgrepTextSearchEngine', () => { }], 'foo bar' ), - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'app/file2.js'), [{ previewRange: new Range(0, 3, 0, 6), @@ -228,7 +228,7 @@ suite('RipgrepTextSearchEngine', () => { }], 'foobar' ), - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'app2/file3.js'), [{ previewRange: new Range(0, 3, 0, 6), @@ -247,7 +247,7 @@ suite('RipgrepTextSearchEngine', () => { makeRgMatch('file1.js', '', 5, []), ], [ - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'file1.js'), [ { @@ -257,7 +257,7 @@ suite('RipgrepTextSearchEngine', () => { ], 'foobar' ), - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'file1.js'), [ { @@ -276,7 +276,7 @@ suite('RipgrepTextSearchEngine', () => { makeRgMatch('file1.js', 'foobarbazquux', 4, [{ start: 0, end: 4 }, { start: 6, end: 10 }]), ], [ - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'file1.js'), [ { @@ -299,7 +299,7 @@ suite('RipgrepTextSearchEngine', () => { makeRgMatch('file1.js', 'foo\nbar\nbaz\nquux', 4, [{ start: 0, end: 5 }, { start: 8, end: 13 }]), ], [ - new TextSearchMatchNew( + new TextSearchMatch2( joinPath(TEST_FOLDER, 'file1.js'), [ { @@ -321,7 +321,7 @@ suite('RipgrepTextSearchEngine', () => { test('simple includes', () => { // Only testing the args that come from includes. function testGetRgArgs(includes: string[], expectedFromIncludes: string[]): void { - const query: TextSearchQueryNew = { + const query: TextSearchQuery2 = { pattern: 'test' }; diff --git a/src/vs/workbench/services/search/test/node/textSearchManager.test.ts b/src/vs/workbench/services/search/test/node/textSearchManager.test.ts index eda186aa3e432..190ec3005f396 100644 --- a/src/vs/workbench/services/search/test/node/textSearchManager.test.ts +++ b/src/vs/workbench/services/search/test/node/textSearchManager.test.ts @@ -9,14 +9,14 @@ import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { Progress } from '../../../../../platform/progress/common/progress.js'; import { ITextQuery, QueryType } from '../../common/search.js'; -import { ProviderResult, TextSearchCompleteNew, TextSearchProviderOptions, TextSearchProviderNew, TextSearchQueryNew, TextSearchResultNew } from '../../common/searchExtTypes.js'; +import { ProviderResult, TextSearchComplete2, TextSearchProviderOptions, TextSearchProvider2, TextSearchQuery2, TextSearchResult2 } from '../../common/searchExtTypes.js'; import { NativeTextSearchManager } from '../../node/textSearchManager.js'; suite('NativeTextSearchManager', () => { test('fixes encoding', async () => { let correctEncoding = false; - const provider: TextSearchProviderNew = { - provideTextSearchResults(query: TextSearchQueryNew, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): ProviderResult { + const provider: TextSearchProvider2 = { + provideTextSearchResults(query: TextSearchQuery2, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): ProviderResult { correctEncoding = options.folderOptions[0].encoding === 'windows-1252'; return null; diff --git a/src/vscode-dts/vscode.proposed.aiTextSearchProviderNew.d.ts b/src/vscode-dts/vscode.proposed.aiTextSearchProvider2.d.ts similarity index 83% rename from src/vscode-dts/vscode.proposed.aiTextSearchProviderNew.d.ts rename to src/vscode-dts/vscode.proposed.aiTextSearchProvider2.d.ts index f54a9ef2b146a..05c9c6b04158f 100644 --- a/src/vscode-dts/vscode.proposed.aiTextSearchProviderNew.d.ts +++ b/src/vscode-dts/vscode.proposed.aiTextSearchProvider2.d.ts @@ -7,7 +7,7 @@ declare module 'vscode' { /** * An AITextSearchProvider provides additional AI text search results in the workspace. */ - export interface AITextSearchProviderNew { + export interface AITextSearchProvider2 { /** * The name of the AI searcher. Will be displayed as `{name} Results` in the Search View. */ @@ -22,7 +22,7 @@ declare module 'vscode' { * @param progress A progress callback that must be invoked for all results. * @param token A cancellation token. */ - provideAITextSearchResults(query: string, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): ProviderResult; + provideAITextSearchResults(query: string, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): ProviderResult; } export namespace workspace { @@ -35,6 +35,6 @@ declare module 'vscode' { * @param provider The provider. * @return A {@link Disposable} that unregisters this provider when being disposed. */ - export function registerAITextSearchProviderNew(scheme: string, provider: AITextSearchProviderNew): Disposable; + export function registerAITextSearchProvider2(scheme: string, provider: AITextSearchProvider2): Disposable; } } diff --git a/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts b/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts similarity index 95% rename from src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts rename to src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts index 36b0b482313c7..e23123d376c4a 100644 --- a/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts +++ b/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts @@ -73,7 +73,7 @@ declare module 'vscode' { * * The FileSearchProvider will be invoked on every keypress in quickopen. */ - export interface FileSearchProviderNew { + export interface FileSearchProvider2 { /** * WARNING: VERY EXPERIMENTAL. * @@ -96,6 +96,6 @@ declare module 'vscode' { * @param provider The provider. * @return A {@link Disposable} that unregisters this provider when being disposed. */ - export function registerFileSearchProviderNew(scheme: string, provider: FileSearchProviderNew): Disposable; + export function registerFileSearchProvider2(scheme: string, provider: FileSearchProvider2): Disposable; } } diff --git a/src/vscode-dts/vscode.proposed.findTextInFilesNew.d.ts b/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts similarity index 85% rename from src/vscode-dts/vscode.proposed.findTextInFilesNew.d.ts rename to src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts index 2756dce1aab4e..6fcec3d0a1edb 100644 --- a/src/vscode-dts/vscode.proposed.findTextInFilesNew.d.ts +++ b/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts @@ -7,7 +7,7 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/59924 - export interface FindTextInFilesOptionsNew { + export interface FindTextInFilesOptions2 { /** * An array of {@link GlobPattern GlobPattern} that defines the files to search for. * The glob patterns will be matched against the file paths of files relative to their workspace or {@link baseUri GlobPattern.baseUri} if applicable. @@ -18,9 +18,9 @@ declare module 'vscode' { * For example, consider the following code: * * ```ts - * const ab = findTextInFilesNew('foo', {include: ['*.ts', '*.js']}); - * const a = findTextInFilesNew('foo', {include: ['*.ts']}); - * const b = findTextInFilesNew('foo', {include: ['*.js']}); + * const ab = findTextInFiles2('foo', {include: ['*.ts', '*.js']}); + * const a = findTextInFiles2('foo', {include: ['*.ts']}); + * const b = findTextInFiles2('foo', {include: ['*.js']}); * ``` * * In this, `ab` will be the union of results from `a` and `b`. @@ -35,9 +35,9 @@ declare module 'vscode' { * For example, consider the following code: * * ```ts - * const ab = findTextInFilesNew('foo', {exclude: ['*.ts', '*.js']}); - * const a = findTextInFilesNew('foo', {exclude: ['*.ts']}); - * const b = findTextInFilesNew('foo', {exclude: ['*.js']}); + * const ab = findTextInFiles2('foo', {exclude: ['*.ts', '*.js']}); + * const a = findTextInFiles2('foo', {exclude: ['*.ts']}); + * const b = findTextInFiles2('foo', {exclude: ['*.js']}); * ``` * * In this, `ab` will be the intersection of results from `a` and `b`. @@ -73,12 +73,12 @@ declare module 'vscode' { */ local?: boolean; /** - * Use ignore files at the parent directory. When set to `true`, {@link FindTextInFilesOptionsNew.useIgnoreFiles.local} must be `true`. + * Use ignore files at the parent directory. When set to `true`, {@link FindTextInFilesOptions2.useIgnoreFiles.local} must be `true`. * May default to `search.useParentIgnoreFiles` setting if not set. */ parent?: boolean; /** - * Use global ignore files. When set to `true`, {@link FindTextInFilesOptionsNew.useIgnoreFiles.local} must also be `true`. + * Use global ignore files. When set to `true`, {@link FindTextInFilesOptions2.useIgnoreFiles.local} must also be `true`. * May default to `search.useGlobalIgnoreFiles` setting if not set. */ global?: boolean; @@ -123,11 +123,11 @@ declare module 'vscode' { /** * The results of the text search, in batches. To get completion information, wait on the `complete` property. */ - results: AsyncIterable; + results: AsyncIterable; /** * The text search completion information. This resolves on completion. */ - complete: Thenable; + complete: Thenable; } /** @@ -155,10 +155,10 @@ declare module 'vscode' { * Search text in files across all {@link workspace.workspaceFolders workspace folders} in the workspace. * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. * @param options An optional set of query options. - * @param callback A callback, called for each {@link TextSearchResultNew result}. This can be a direct match, or context that surrounds a match. + * @param callback A callback, called for each {@link TextSearchResult2 result}. This can be a direct match, or context that surrounds a match. * @param token A token that can be used to signal cancellation to the underlying search engine. * @return A thenable that resolves when the search is complete. */ - export function findTextInFilesNew(query: TextSearchQueryNew, options?: FindTextInFilesOptionsNew, token?: CancellationToken): FindTextInFilesResponse; + export function findTextInFiles2(query: TextSearchQuery2, options?: FindTextInFilesOptions2, token?: CancellationToken): FindTextInFilesResponse; } } diff --git a/src/vscode-dts/vscode.proposed.textSearchCompleteNew.d.ts b/src/vscode-dts/vscode.proposed.textSearchComplete2.d.ts similarity index 90% rename from src/vscode-dts/vscode.proposed.textSearchCompleteNew.d.ts rename to src/vscode-dts/vscode.proposed.textSearchComplete2.d.ts index 60c3503210fad..f75db1af7539e 100644 --- a/src/vscode-dts/vscode.proposed.textSearchCompleteNew.d.ts +++ b/src/vscode-dts/vscode.proposed.textSearchComplete2.d.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ declare module 'vscode' { - export interface TextSearchCompleteNew { + export interface TextSearchComplete2 { /** * Additional information regarding the state of the completed search. * @@ -14,13 +14,13 @@ declare module 'vscode' { * * Commands may optionally return { triggerSearch: true } to signal to the editor that the original search should run be again. */ - message?: TextSearchCompleteMessageNew[]; + message?: TextSearchCompleteMessage2[]; } /** * A message regarding a completed search. */ - export interface TextSearchCompleteMessageNew { + export interface TextSearchCompleteMessage2 { /** * Markdown text of the message. */ diff --git a/src/vscode-dts/vscode.proposed.textSearchProviderNew.d.ts b/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts similarity index 89% rename from src/vscode-dts/vscode.proposed.textSearchProviderNew.d.ts rename to src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts index a82ca8f4ae263..23d1ba018b58d 100644 --- a/src/vscode-dts/vscode.proposed.textSearchProviderNew.d.ts +++ b/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts @@ -10,7 +10,7 @@ declare module 'vscode' { /** * The parameters of a query for text search. All optional booleans default to `false`. */ - export interface TextSearchQueryNew { + export interface TextSearchQuery2 { /** * The text pattern to search for. * @@ -142,7 +142,7 @@ declare module 'vscode' { /** * Information collected when text search is complete. */ - export interface TextSearchCompleteNew { + export interface TextSearchComplete2 { /** * Whether the search hit the limit on the maximum number of search results. * `maxResults` on {@linkcode TextSearchProviderOptions} specifies the max number of results. @@ -164,9 +164,9 @@ declare module 'vscode' { * const foo = bar; * ``` * - * If the query is `log`, then the line `console.log(bar);` should be represented using a {@link TextSearchMatchNew}. + * If the query is `log`, then the line `console.log(bar);` should be represented using a {@link TextSearchMatch2}. */ - export class TextSearchMatchNew { + export class TextSearchMatch2 { /** * @param uri The uri for the matching document. * @param ranges The ranges associated with this match. @@ -198,7 +198,7 @@ declare module 'vscode' { /** * The context lines of text that are not a part of a match, - * but that surround a match line of type {@link TextSearchMatchNew}. + * but that surround a match line of type {@link TextSearchMatch2}. * * For example, consider this excerpt: * @@ -209,10 +209,10 @@ declare module 'vscode' { * ``` * * If the query is `log`, then the lines `const bar = 1;` and `const foo = bar;` - * should be represented using two separate {@link TextSearchContextNew} for the search instance. + * should be represented using two separate {@link TextSearchContext2} for the search instance. * This example assumes that the finder requests one line of surrounding context. */ - export class TextSearchContextNew { + export class TextSearchContext2 { /** * @param uri The uri for the matching document. * @param text The line of context text. @@ -238,26 +238,26 @@ declare module 'vscode' { } /** - * A result payload for a text search, pertaining to {@link TextSearchMatchNew matches} - * and its associated {@link TextSearchContextNew context} within a single file. + * A result payload for a text search, pertaining to {@link TextSearchMatch2 matches} + * and its associated {@link TextSearchContext2 context} within a single file. */ - export type TextSearchResultNew = TextSearchMatchNew | TextSearchContextNew; + export type TextSearchResult2 = TextSearchMatch2 | TextSearchContext2; /** * A TextSearchProvider provides search results for text results inside files in the workspace. */ - export interface TextSearchProviderNew { + export interface TextSearchProvider2 { /** * WARNING: VERY EXPERIMENTAL. * * Provide results that match the given text pattern. * @param query The parameters for this query. * @param options A set of options to consider while searching. - * @param progress A progress callback that must be invoked for all {@link TextSearchResultNew results}. + * @param progress A progress callback that must be invoked for all {@link TextSearchResult2 results}. * These results can be direct matches, or context that surrounds matches. * @param token A cancellation token. */ - provideTextSearchResults(query: TextSearchQueryNew, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): ProviderResult; + provideTextSearchResults(query: TextSearchQuery2, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): ProviderResult; } export namespace workspace { @@ -270,6 +270,6 @@ declare module 'vscode' { * @param provider The provider. * @return A {@link Disposable} that unregisters this provider when being disposed. */ - export function registerTextSearchProviderNew(scheme: string, provider: TextSearchProviderNew): Disposable; + export function registerTextSearchProvider2(scheme: string, provider: TextSearchProvider2): Disposable; } } From 9de080f7cbcec77de4ef3e0d27fbf9fd335d3fba Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 28 Oct 2024 16:08:17 -0700 Subject: [PATCH 036/555] testing: actually consider visible documents with failureInVisibleDocument (#232453) Fixes #230881 --- src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 9e4be7a62fc62..aec2a17cc41c9 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -280,7 +280,8 @@ export class TestingPeekOpener extends Disposable implements ITestingPeekOpener // and this test is not in any of the editors' models. switch (cfg) { case AutoOpenPeekViewWhen.FailureVisible: { - const editorUris = new Set(editors.map(e => e.getModel()?.uri.toString())); + const visibleEditors = this.editorService.visibleTextEditorControls; + const editorUris = new Set(visibleEditors.filter(isCodeEditor).map(e => e.getModel()?.uri.toString())); if (!Iterable.some(resultItemParents(evt.result, evt.item), i => i.item.uri && editorUris.has(i.item.uri.toString()))) { return; } From da0100981f2cf01e8aca5a75a984444b839e2c4d Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 28 Oct 2024 18:02:01 -0700 Subject: [PATCH 037/555] testing: fix double peek view when using next/previous test failures (#232454) Rationalize some of the logic with the way we display things now Fixes #226727 --- .../contrib/testing/browser/testingOutputPeek.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index aec2a17cc41c9..2e08dc3a766b7 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -74,11 +74,16 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; /** Iterates through every message in every result */ -function* allMessages(results: readonly ITestResult[]) { - for (const result of results) { - for (const test of result.tests) { - for (let taskIndex = 0; taskIndex < test.tasks.length; taskIndex++) { - for (let messageIndex = 0; messageIndex < test.tasks[taskIndex].messages.length; messageIndex++) { +function* allMessages([result]: readonly ITestResult[]) { + if (!result) { + return; + } + + for (const test of result.tests) { + for (let taskIndex = 0; taskIndex < test.tasks.length; taskIndex++) { + const messages = test.tasks[taskIndex].messages; + for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) { + if (messages[messageIndex].type === TestMessageType.Error) { yield { result, test, taskIndex, messageIndex }; } } From 92df7b670ebd0799b69de298359a57287b386885 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Mon, 28 Oct 2024 18:09:20 -0700 Subject: [PATCH 038/555] fix: make `#file` variables part of the chat editing working set (#232456) * fix: make `#file` variables part of the chat editing working set * fix: determine how often the limit kicks in on files that a user wanted to send --- src/vs/workbench/contrib/chat/browser/chat.ts | 1 + .../contrib/chat/browser/chatInputPart.ts | 34 +++++++++++--- .../contrib/chat/browser/chatWidget.ts | 44 ++++++++++++++++--- .../browser/contrib/chatDynamicVariables.ts | 2 + .../browser/contrib/chatInputEditorContrib.ts | 1 + 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 088418b608353..a09abcf5fbd29 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -183,6 +183,7 @@ export interface IChatWidget { getFocus(): ChatTreeItem | undefined; setInput(query?: string): void; getInput(): string; + refreshParsedInput(): void; logInputHistory(): void; acceptInput(query?: string, options?: IChatAcceptInputOptions): Promise; acceptInputWithPrefix(prefix: string): void; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 63a5b9520bdf4..4c84444fb5f53 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -72,6 +72,7 @@ import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js'; import { CONTEXT_CHAT_HAS_FILE_ATTACHMENTS, CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INPUT_HAS_FOCUS, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from '../common/chatContextKeys.js'; import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../common/chatEditingService.js'; import { IChatRequestVariableEntry } from '../common/chatModel.js'; +import { ChatRequestDynamicVariablePart } from '../common/chatParserTypes.js'; import { IChatFollowup } from '../common/chatService.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; @@ -223,6 +224,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } return edits; } + + private _attemptedWorkingSetEntriesCount: number = 0; + /** + * The number of working set entries that the user actually wanted to attach. + * This is less than or equal to {@link ChatInputPart.chatEditWorkingSetFiles}. + */ + public get attemptedWorkingSetEntriesCount() { + return this._attemptedWorkingSetEntriesCount; + } private _combinedChatEditWorkingSetEntries: URI[] = []; public get chatEditWorkingSetFiles() { return this._combinedChatEditWorkingSetEntries; @@ -916,9 +926,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // Summary of number of files changed const innerContainer = this.chatEditingSessionWidgetContainer.querySelector('.chat-editing-session-container.show-file-icons') as HTMLElement ?? dom.append(this.chatEditingSessionWidgetContainer, $('.chat-editing-session-container.show-file-icons')); - const modifiedFiles = new ResourceSet(); + const seenEntries = new ResourceSet(); let entries: IChatCollapsibleListItem[] = chatEditingSession?.entries.get().map((entry) => { - modifiedFiles.add(entry.modifiedURI); + seenEntries.add(entry.modifiedURI); return { reference: entry.modifiedURI, state: entry.state.get(), @@ -926,22 +936,33 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }; }) ?? []; for (const attachment of this.attachmentModel.attachments) { - if (attachment.isFile && URI.isUri(attachment.value) && !modifiedFiles.has(attachment.value)) { + if (attachment.isFile && URI.isUri(attachment.value) && !seenEntries.has(attachment.value)) { entries.unshift({ reference: attachment.value, state: WorkingSetEntryState.Attached, kind: 'reference', }); - modifiedFiles.add(attachment.value); + seenEntries.add(attachment.value); } } for (const [file, state] of chatEditingSession.workingSet.entries()) { - if (!modifiedFiles.has(file)) { + if (!seenEntries.has(file)) { entries.unshift({ reference: file, state: state, kind: 'reference', }); + seenEntries.add(file); + } + } + // Factor file variables that are part of the user query into the working set + for (const part of chatWidget?.parsedInput.parts ?? []) { + if (part instanceof ChatRequestDynamicVariablePart && part.isFile && URI.isUri(part.data) && !seenEntries.has(part.data)) { + entries.unshift({ + reference: part.data, + state: WorkingSetEntryState.Attached, + kind: 'reference', + }); } } entries.sort((a, b) => { @@ -958,6 +979,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const overviewText = overviewRegion.querySelector('span') ?? dom.append(overviewRegion, $('span')); overviewText.textContent = localize('chatEditingSession.workingSet', 'Working Set'); + // Record the number of entries that the user wanted to add to the working set + this._attemptedWorkingSetEntriesCount = entries.length; + if (entries.length === 1) { overviewText.textContent += ' ' + localize('chatEditingSession.oneFile', '(1 file)'); } else if (entries.length >= remainingFileEntriesBudget) { diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 825e80a7cc0ce..485558cc190f5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -29,6 +29,7 @@ import { ServiceCollection } from '../../../../platform/instantiation/common/ser import { WorkbenchObjectTree } from '../../../../platform/list/browser/listService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground } from '../../../../platform/theme/common/colorRegistry.js'; import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; @@ -235,6 +236,7 @@ export class ChatWidget extends Disposable implements IChatWidget { @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, @IChatEditingService private readonly chatEditingService: IChatEditingService, @IStorageService private readonly storageService: IStorageService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); @@ -272,6 +274,15 @@ export class ChatWidget extends Disposable implements IChatWidget { chatEditingSessionDisposables.clear(); this.renderChatEditingSessionState(null); })); + chatEditingSessionDisposables.add(this.onDidChangeParsedInput(() => { + this.renderChatEditingSessionState(session); + })); + chatEditingSessionDisposables.add(this.inputEditor.onDidChangeModelContent(() => { + if (this.getInput() === '') { + this.refreshParsedInput(); + this.renderChatEditingSessionState(session); + } + })); this.renderChatEditingSessionState(session); })); @@ -460,6 +471,11 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.inputPart.hasFocus(); } + refreshParsedInput() { + this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel!.sessionId, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent }); + this._onDidChangeParsedInput.fire(); + } + getSibling(item: ChatTreeItem, type: 'next' | 'previous'): ChatTreeItem | undefined { if (!isResponseVM(item)) { return; @@ -757,6 +773,7 @@ export class ChatWidget extends Disposable implements IChatWidget { c.setInputState(contribState); } }); + this.refreshParsedInput(); })); this._register(this.inputPart.onDidFocus(() => this._onDidFocus.fire())); this._register(this.inputPart.onDidAcceptFollowup(e => { @@ -869,6 +886,7 @@ export class ChatWidget extends Disposable implements IChatWidget { c.setInputState(viewState.inputState?.[c.id]); } }); + this.refreshParsedInput(); this.viewModelDisposables.add(model.onDidChange((e) => { if (e.kind === 'setAgent') { this._onDidChangeAgent.fire({ agent: e.agent, slashCommand: e.command }); @@ -921,6 +939,7 @@ export class ChatWidget extends Disposable implements IChatWidget { setInput(value = ''): void { this.inputPart.setValue(value, false); + this.refreshParsedInput(); } getInput(): string { @@ -990,6 +1009,14 @@ export class ChatWidget extends Disposable implements IChatWidget { } } + const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); + for (const file of uniqueWorkingSetEntries) { + // Make sure that any files that we sent are part of the working set + // but do not permanently add file variables from previous requests to the working set + // since the user may subsequently edit the chat history + currentEditingSession?.addFileToWorkingSet(file); + } + // Collect file variables from previous requests before sending the request const previousRequests = this.viewModel.model.getRequests(); for (const request of previousRequests) { @@ -1005,12 +1032,19 @@ export class ChatWidget extends Disposable implements IChatWidget { } } workingSet = [...uniqueWorkingSetEntries.values()]; - const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); - for (const file of workingSet) { - // Make sure that any files that we sent are part of the working set - currentEditingSession?.addFileToWorkingSet(file); - } attachedContext = editingSessionAttachedContext; + + type ChatEditingWorkingSetClassification = { + owner: 'joyceerhl'; + comment: 'Information about the working set size in a chat editing request'; + originalSize: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of files that the user tried to attach in their editing request.' }; + actualSize: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of files that were actually sent in their editing request.' }; + }; + type ChatEditingWorkingSetEvent = { + originalSize: number; + actualSize: number; + }; + this.telemetryService.publicLog2('chatEditing/workingSetSize', { originalSize: this.inputPart.attemptedWorkingSetEntriesCount, actualSize: uniqueWorkingSetEntries.size }); } const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts index 29977605ad323..3c3c64d522232 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts @@ -57,6 +57,7 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC range: rangeToDelete, text: '', }]); + this.widget.refreshParsedInput(); } return null; } else if (Range.compareRangesUsingStarts(ref.range, c.range) > 0) { @@ -96,6 +97,7 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC addReference(ref: IDynamicVariable): void { this._variables.push(ref); this.updateDecorations(); + this.widget.refreshParsedInput(); } private updateDecorations(): void { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 0f084633c369d..313e90a2567bf 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -321,6 +321,7 @@ class ChatTokenDeleter extends Disposable { range: rangeToDelete, text: '', }]); + this.widget.refreshParsedInput(); } }); } From 05a38868734ab7d0916a88c113c27327f555fb5f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 29 Oct 2024 11:27:46 +0100 Subject: [PATCH 039/555] fix #229803 (#232469) * fix #229803 * show cache size, fix tests --- .../common/extensionManagement.ts | 12 ++ .../common/extensionsScannerService.ts | 3 + .../node/extensionManagementService.ts | 23 ++- .../extensions/browser/extensionEditor.ts | 131 ++++++++++++++---- .../common/extensionManagementService.ts | 3 +- .../common/webExtensionManagementService.ts | 3 +- 6 files changed, 147 insertions(+), 28 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index ea7b30d9422d1..2ae4f0a8fc175 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -11,6 +11,7 @@ import { Platform } from '../../../base/common/platform.js'; import { URI } from '../../../base/common/uri.js'; import { localize2 } from '../../../nls.js'; import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js'; +import { IFileService } from '../../files/common/files.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$'; @@ -257,6 +258,7 @@ export type Metadata = Partial; export interface ILocalExtension extends IExtension { @@ -271,6 +273,7 @@ export interface ILocalExtension extends IExtension { updated: boolean; pinned: boolean; source: InstallSource; + size: number; } export const enum SortBy { @@ -624,5 +627,14 @@ export interface IExtensionTipsService { getOtherExecutableBasedTips(): Promise; } +export async function computeSize(location: URI, fileService: IFileService): Promise { + const stat = await fileService.resolve(location); + if (stat.children) { + const sizes = await Promise.all(stat.children.map(c => computeSize(c.resource, fileService))); + return sizes.reduce((r, s) => r + s, 0); + } + return stat.size ?? 0; +} + export const ExtensionsLocalizedLabel = localize2('extensions', "Extensions"); export const PreferencesLocalizedLabel = localize2('preferences', 'Preferences'); diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index 8f238a8402973..0b2e581229429 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -652,6 +652,9 @@ class ExtensionsScanner extends Disposable { manifest.publisher = UNDEFINED_PUBLISHER; } metadata = metadata ?? manifest.__metadata; + if (metadata && !metadata?.size && manifest.__metadata?.size) { + metadata.size = manifest.__metadata?.size; + } delete manifest.__metadata; const id = getGalleryExtensionId(manifest.publisher, manifest.name); const identifier = metadata?.id ? { id, uuid: metadata.id } : { id }; diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 680e0dd15be8d..4d68a00134e36 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -33,6 +33,7 @@ import { IProductVersion, EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT, ExtensionSignatureVerificationCode, + computeSize, } from '../common/extensionManagement.js'; import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from '../common/extensionManagementUtil.js'; import { IExtensionsProfileScannerService, IScannedProfileExtension } from '../common/extensionsProfileScannerService.js'; @@ -563,6 +564,7 @@ export class ExtensionsScanner extends Disposable { async cleanUp(): Promise { await this.removeTemporarilyDeletedFolders(); await this.removeUninstalledExtensions(); + await this.initializeMetadata(); } async scanExtensions(type: ExtensionType | null, profileLocation: URI, productVersion: IProductVersion): Promise { @@ -649,6 +651,13 @@ export class ExtensionsScanner extends Disposable { throw fromExtractError(e); } + try { + metadata.size = await computeSize(tempLocation, this.fileService); + } catch (error) { + // Log & ignore + this.logService.warn(`Error while getting the size of the extracted extension : ${tempLocation.fsPath}`, getErrorMessage(error)); + } + try { await this.extensionsScannerService.updateMetadata(tempLocation, metadata); } catch (error) { @@ -875,10 +884,22 @@ export class ExtensionsScanner extends Disposable { updated: !!extension.metadata?.updated, pinned: !!extension.metadata?.pinned, isWorkspaceScoped: false, - source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix') + source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix'), + size: extension.metadata?.size ?? 0, }; } + private async initializeMetadata(): Promise { + const extensions = await this.extensionsScannerService.scanUserExtensions({ includeInvalid: true }); + await Promise.all(extensions.map(async extension => { + // set size if not set before + if (!extension.metadata?.size && extension.metadata?.source !== 'resource') { + const size = await computeSize(extension.location, this.fileService); + await this.extensionsScannerService.updateMetadata(extension.location, { size }); + } + })); + } + private async removeUninstalledExtensions(): Promise { const uninstalled = await this.getUninstalledExtensions(); if (Object.keys(uninstalled).length === 0) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index b3cc78f56ccde..4fa7c35377e4d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, Dimension, addDisposableListener, append, setParentFlowTo } from '../../../../base/browser/dom.js'; +import { $, Dimension, addDisposableListener, append, hide, setParentFlowTo, show } from '../../../../base/browser/dom.js'; import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js'; @@ -18,7 +18,6 @@ import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, MutableDisposable, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas, matchesScheme } from '../../../../base/common/network.js'; import { language } from '../../../../base/common/platform.js'; -import * as semver from '../../../../base/common/semver/semver.js'; import { isUndefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; @@ -31,7 +30,7 @@ import { localize } from '../../../../nls.js'; import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKey, IContextKeyService, IScopedContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { IExtensionGalleryService, IGalleryExtension } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { computeSize, IExtensionGalleryService, IGalleryExtension, ILocalExtension } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; import { ExtensionType, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; @@ -88,6 +87,26 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; import { VIEW_ID as EXPLORER_VIEW_ID } from '../../files/common/files.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js'; +import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; + +function toDateString(date: Date) { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}, ${date.toLocaleTimeString(language, { hourCycle: 'h23' })}`; +} + +function toMemoryString(bytes: number) { + if (bytes < 1024) { + return `${bytes} B`; + } + if (bytes < 1024 * 1024) { + return `${(bytes / 1024).toFixed(1)} KB`; + } + if (bytes < 1024 * 1024 * 1024) { + return `${(bytes / 1024 / 1024).toFixed(1)} MB`; + } + return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`; +} class NavBar extends Disposable { @@ -197,15 +216,16 @@ class VersionWidget extends ExtensionWithDifferentGalleryVersionWidget { hoverService: IHoverService ) { super(); - this.element = append(container, $('code.version')); + this.element = append(container, $('code.version', undefined, 'pre-release')); this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, localize('extension version', "Extension Version"))); this.render(); } render(): void { - if (!this.extension || !semver.valid(this.extension.version)) { - return; + if (this.extension?.preRelease) { + show(this.element); + } else { + hide(this.element); } - this.element.textContent = `v${this.gallery?.version ?? this.extension.version}${this.extension.isPreReleaseVersion ? ' (pre-release)' : ''}`; } } @@ -255,6 +275,9 @@ export class ExtensionEditor extends EditorPane { @IViewsService private readonly viewsService: IViewsService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IHoverService private readonly hoverService: IHoverService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IFileService private readonly fileService: IFileService, ) { super(ExtensionEditor.ID, group, telemetryService, themeService, storageService); this.extensionReadme = null; @@ -925,7 +948,12 @@ export class ExtensionEditor extends EditorPane { this.renderCategories(content, extension); this.renderExtensionResources(content, extension); - this.renderMoreInfo(content, extension); + if (extension.gallery) { + this.renderMarketplaceInfo(content, extension); + } + if (extension.local) { + this.renderInstallInfo(content, extension.local); + } append(container, scrollableContent.getDomNode()); scrollableContent.scanDomNode(); @@ -980,37 +1008,90 @@ export class ExtensionEditor extends EditorPane { } } - private renderMoreInfo(container: HTMLElement, extension: IExtension): void { + private renderInstallInfo(container: HTMLElement, extension: ILocalExtension): void { + const installInfoContainer = append(container, $('.more-info-container.additional-details-element')); + append(installInfoContainer, $('.additional-details-title', undefined, localize('Install Info', "Installation"))); + const installInfo = append(installInfoContainer, $('.more-info')); + append(installInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('id', "Identifier")), + $('code', undefined, extension.identifier.id) + )); + append(installInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('Version', "Version")), + $('code', undefined, extension.manifest.version) + ) + ); + if (extension.installedTimestamp) { + append(installInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('last updated', "Last Updated")), + $('div', undefined, toDateString(new Date(extension.installedTimestamp))) + ) + ); + } + if (extension.size) { + append(installInfo, + $('.more-info-entry', undefined, + $('div', { title: localize('size when installed', "Size when installed") }, localize('size', "Size")), + $('div', undefined, toMemoryString(extension.size)) + ) + ); + } + this.computeCacheSize(extension).then(size => { + if (size) { + append(installInfo, + $('.more-info-entry', undefined, + $('div', { title: localize('disk space used', "Cache size") }, localize('cache size', "Cache")), + $('div', undefined, toMemoryString(size))) + ); + } + }); + } + + private async computeCacheSize(extension: ILocalExtension): Promise { + let extensionCacheLocation = this.uriIdentityService.extUri.joinPath(this.userDataProfilesService.defaultProfile.globalStorageHome, extension.identifier.id.toLowerCase()); + if (extension.location.scheme === Schemas.vscodeRemote) { + const environment = await this.remoteAgentService.getEnvironment(); + if (!environment) { + return 0; + } + extensionCacheLocation = this.uriIdentityService.extUri.joinPath(environment.globalStorageHome, extension.identifier.id.toLowerCase()); + } + return computeSize(extensionCacheLocation, this.fileService); + } + + private renderMarketplaceInfo(container: HTMLElement, extension: IExtension): void { const gallery = extension.gallery; const moreInfoContainer = append(container, $('.more-info-container.additional-details-element')); - append(moreInfoContainer, $('.additional-details-title', undefined, localize('Marketplace Info', "More Info"))); + append(moreInfoContainer, $('.additional-details-title', undefined, localize('Marketplace Info', "Marketplace"))); const moreInfo = append(moreInfoContainer, $('.more-info')); - const toDateString = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}, ${date.toLocaleTimeString(language, { hourCycle: 'h23' })}`; if (gallery) { + if (!extension.local) { + append(moreInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('id', "Identifier")), + $('code', undefined, extension.identifier.id) + )); + append(moreInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('Version', "Version")), + $('code', undefined, gallery.version) + ) + ); + } append(moreInfo, $('.more-info-entry', undefined, $('div', undefined, localize('published', "Published")), $('div', undefined, toDateString(new Date(gallery.releaseDate))) ), $('.more-info-entry', undefined, - $('div', undefined, localize('last released', "Last released")), + $('div', undefined, localize('last released', "Last Released")), $('div', undefined, toDateString(new Date(gallery.lastUpdated))) ) ); } - if (extension.local && extension.local.installedTimestamp) { - append(moreInfo, - $('.more-info-entry', undefined, - $('div', undefined, localize('last updated', "Last updated")), - $('div', undefined, toDateString(new Date(extension.local.installedTimestamp))) - ) - ); - } - append(moreInfo, - $('.more-info-entry', undefined, - $('div', undefined, localize('id', "Identifier")), - $('code', undefined, extension.identifier.id) - )); } private openChangelog(extension: IExtension, template: IExtensionEditorTemplate, token: CancellationToken): Promise { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 90ff93379416e..a5bd6a725cfb3 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -1082,7 +1082,8 @@ class WorkspaceExtensionsManagementService extends Disposable { updated: !!extension.metadata?.updated, pinned: !!extension.metadata?.pinned, isWorkspaceScoped: true, - source: 'resource' + source: 'resource', + size: extension.metadata?.size ?? 0, }; } } diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index b3c04e94edb9b..1ada0726fdaff 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -237,7 +237,8 @@ function toLocalExtension(extension: IExtension): ILocalExtension { updated: !!metadata.updated, pinned: !!metadata?.pinned, isWorkspaceScoped: false, - source: metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'resource') + source: metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'resource'), + size: metadata.size ?? 0, }; } From bd125481bb5fca2b3a8425fd69e4049a1c0fadfd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 29 Oct 2024 11:57:24 +0100 Subject: [PATCH 040/555] chore - extract and cleanup offset edit utils (#232475) --- src/vs/editor/common/core/lineEdit.ts | 4 -- .../common/model/textModelOffsetEdit.ts | 56 +++++++++++++++++++ .../chatEditingModifiedFileEntry.ts | 51 +++-------------- 3 files changed, 63 insertions(+), 48 deletions(-) create mode 100644 src/vs/editor/common/model/textModelOffsetEdit.ts diff --git a/src/vs/editor/common/core/lineEdit.ts b/src/vs/editor/common/core/lineEdit.ts index bf1b6ad57c635..507bd7b0f330f 100644 --- a/src/vs/editor/common/core/lineEdit.ts +++ b/src/vs/editor/common/core/lineEdit.ts @@ -12,10 +12,6 @@ import { Position } from './position.js'; import { Range } from './range.js'; import { AbstractText, SingleTextEdit, TextEdit } from './textEdit.js'; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation and GitHub. All rights reserved. - *--------------------------------------------------------------------------------------------*/ - export class LineEdit { public static readonly empty = new LineEdit([]); diff --git a/src/vs/editor/common/model/textModelOffsetEdit.ts b/src/vs/editor/common/model/textModelOffsetEdit.ts new file mode 100644 index 0000000000000..4cc02d88c2ef5 --- /dev/null +++ b/src/vs/editor/common/model/textModelOffsetEdit.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditOperation } from '../core/editOperation.js'; +import { Range } from '../core/range.js'; +import { OffsetEdit, SingleOffsetEdit } from '../core/offsetEdit.js'; +import { OffsetRange } from '../core/offsetRange.js'; +import { DetailedLineRangeMapping } from '../diff/rangeMapping.js'; +import { ITextModel, IIdentifiedSingleEditOperation } from '../model.js'; +import { IModelContentChange } from '../textModelEvents.js'; + + +export abstract class OffsetEdits { + + private constructor() { + // static utils only! + } + + static asEditOperations(offsetEdit: OffsetEdit, doc: ITextModel): IIdentifiedSingleEditOperation[] { + const edits: IIdentifiedSingleEditOperation[] = []; + for (const singleEdit of offsetEdit.edits) { + const range = Range.fromPositions( + doc.getPositionAt(singleEdit.replaceRange.start), + doc.getPositionAt(singleEdit.replaceRange.start + singleEdit.replaceRange.length) + ); + edits.push(EditOperation.replace(range, singleEdit.newText)); + } + return edits; + } + + static fromContentChanges(contentChanges: readonly IModelContentChange[]) { + const editsArr = contentChanges.map(c => new SingleOffsetEdit(OffsetRange.ofStartAndLength(c.rangeOffset, c.rangeLength), c.text)); + editsArr.reverse(); + const edits = new OffsetEdit(editsArr); + return edits; + } + + static fromLineRangeMapping(original: ITextModel, modified: ITextModel, changes: readonly DetailedLineRangeMapping[]): OffsetEdit { + const edits: SingleOffsetEdit[] = []; + for (const c of changes) { + for (const i of c.innerChanges ?? []) { + const newText = modified.getValueInRange(i.modifiedRange); + + const startOrig = original.getOffsetAt(i.originalRange.getStartPosition()); + const endExOrig = original.getOffsetAt(i.originalRange.getEndPosition()); + const origRange = new OffsetRange(startOrig, endExOrig); + + edits.push(new SingleOffsetEdit(origRange, newText)); + } + } + + return new OffsetEdit(edits); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index e2d1841f33c39..e7f1b19a6ae5b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -11,19 +11,18 @@ import { themeColorFromId } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { IBulkEditService } from '../../../../../editor/browser/services/bulkEditService.js'; import { EditOperation, ISingleEditOperation } from '../../../../../editor/common/core/editOperation.js'; -import { OffsetEdit, SingleOffsetEdit } from '../../../../../editor/common/core/offsetEdit.js'; -import { OffsetRange } from '../../../../../editor/common/core/offsetRange.js'; -import { Range } from '../../../../../editor/common/core/range.js'; +import { OffsetEdit } from '../../../../../editor/common/core/offsetEdit.js'; import { IDocumentDiff, nullDocumentDiff } from '../../../../../editor/common/diff/documentDiffProvider.js'; import { TextEdit } from '../../../../../editor/common/languages.js'; import { ILanguageService } from '../../../../../editor/common/languages/language.js'; -import { IIdentifiedSingleEditOperation, IModelDeltaDecoration, ITextModel, OverviewRulerLane } from '../../../../../editor/common/model.js'; +import { IModelDeltaDecoration, ITextModel, OverviewRulerLane } from '../../../../../editor/common/model.js'; import { SingleModelEditStackElement } from '../../../../../editor/common/model/editStack.js'; import { ModelDecorationOptions, createTextBufferFactoryFromSnapshot } from '../../../../../editor/common/model/textModel.js'; +import { OffsetEdits } from '../../../../../editor/common/model/textModelOffsetEdit.js'; import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; -import { IModelContentChange, IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; +import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; import { localize } from '../../../../../nls.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { editorSelectionBackground } from '../../../../../platform/theme/common/colorRegistry.js'; @@ -209,7 +208,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie } private _mirrorEdits(event: IModelContentChangedEvent) { - const edit = fromContentChange(event.changes); + const edit = OffsetEdits.fromContentChanges(event.changes); if (this._isEditFromUs) { const e_sum = this._edit; @@ -243,7 +242,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie // user edits overlaps/conflicts with AI edits this._edit = e_ai.compose(e_user); } else { - const edits = toEditOperations(e_user_r, this.docSnapshot); + const edits = OffsetEdits.asEditOperations(e_user_r, this.docSnapshot); this.docSnapshot.applyEdits(edits); this._edit = e_ai.tryRebase(e_user_r); } @@ -336,7 +335,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie if (this.doc.getVersionId() === docVersionNow && this.docSnapshot.getVersionId() === snapshotVersionNow) { const diff2 = diff ?? nullDocumentDiff; this._diffInfo.set(diff2, undefined); - this._edit = fromDiff(this.docSnapshot, this.doc, diff2); + this._edit = OffsetEdits.fromLineRangeMapping(this.docSnapshot, this.doc, diff2.changes); } } @@ -413,39 +412,3 @@ export interface ISnapshotEntry { readonly state: WorkingSetEntryState; telemetryInfo: IModifiedEntryTelemetryInfo; } - -function toEditOperations(offsetEdit: OffsetEdit, doc: ITextModel): IIdentifiedSingleEditOperation[] { - const edits: IIdentifiedSingleEditOperation[] = []; - for (const singleEdit of offsetEdit.edits) { - const range = Range.fromPositions( - doc.getPositionAt(singleEdit.replaceRange.start), - doc.getPositionAt(singleEdit.replaceRange.start + singleEdit.replaceRange.length) - ); - edits.push(EditOperation.replace(range, singleEdit.newText)); - } - return edits; -} - -function fromContentChange(contentChanges: readonly IModelContentChange[]) { - const editsArr = contentChanges.map(c => new SingleOffsetEdit(OffsetRange.ofStartAndLength(c.rangeOffset, c.rangeLength), c.text)); - editsArr.reverse(); - const edits = new OffsetEdit(editsArr); - return edits; -} - -function fromDiff(original: ITextModel, modified: ITextModel, diff: IDocumentDiff): OffsetEdit { - const edits: SingleOffsetEdit[] = []; - for (const c of diff.changes) { - for (const i of c.innerChanges ?? []) { - const newText = modified.getValueInRange(i.modifiedRange); - - const startOrig = original.getOffsetAt(i.originalRange.getStartPosition()); - const endExOrig = original.getOffsetAt(i.originalRange.getEndPosition()); - const origRange = new OffsetRange(startOrig, endExOrig); - - edits.push(new SingleOffsetEdit(origRange, newText)); - } - } - - return new OffsetEdit(edits); -} From 4417a7b12f15b9639941fa9adaa78d8e270a16e7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 29 Oct 2024 12:09:32 +0100 Subject: [PATCH 041/555] chore - remove double register (#232476) --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 485558cc190f5..0e1ac7b4a4259 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -614,7 +614,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } private createList(listContainer: HTMLElement, options: IChatListItemRendererOptions): void { - const scopedInstantiationService = this._register(this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService])))); + const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService]))); const delegate = scopedInstantiationService.createInstance(ChatListDelegate, this.viewOptions.defaultElementHeight ?? 200); const rendererDelegate: IChatRendererDelegate = { getListLength: () => this.tree.getNode(null).visibleChildrenCount, From 4d312be7e64ce0c6c8379c71be6eb617974d59e7 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 29 Oct 2024 12:11:31 +0100 Subject: [PATCH 042/555] Implements inline edit rejection command support (#232472) * Implements inline edit rejection command support * updates monaco.d.ts --- src/vs/editor/common/languages.ts | 4 +++- .../browser/controller/commands.ts | 2 +- .../controller/inlineCompletionsController.ts | 6 +++--- .../browser/model/inlineCompletionsModel.ts | 17 +++++++++------- .../browser/model/inlineEditsAdapter.ts | 20 +++++++++++++------ src/vs/monaco.d.ts | 3 ++- .../browser/inlineChatController.ts | 2 +- 7 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 68bb4cd7fb3db..12564bcc1e9d0 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -801,10 +801,12 @@ export interface InlineCompletionsProvider { const controller = InlineCompletionsController.get(editor); transaction(tx => { - controller?.model.get()?.stop(tx); + controller?.model.get()?.stop('explicitCancel', tx); }); } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 5188dcac047e2..2a8aff67a9d19 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -228,7 +228,7 @@ export class InlineCompletionsController extends Disposable { transaction(tx => { /** @description InlineCompletionsController.onDidBlurEditorWidget */ - this.model.get()?.stop(tx); + this.model.get()?.stop('automatic', tx); }); })); @@ -332,9 +332,9 @@ export class InlineCompletionsController extends Disposable { return this._ghostTextWidgets.get()[0]?.get().ownsViewZone(viewZoneId) ?? false; } - public hide() { + public reject() { transaction(tx => { - this.model.get()?.stop(tx); + this.model.get()?.stop('explicitCancel', tx); }); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index e6986cb4bc0cf..56a8535fbcca6 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -173,8 +173,15 @@ export class InlineCompletionsModel extends Disposable { await this._fetchInlineCompletionsPromise.get(); } - public stop(tx?: ITransaction): void { + public stop(stopReason: 'explicitCancel' | 'automatic' = 'automatic', tx?: ITransaction): void { subtransaction(tx, tx => { + if (stopReason === 'explicitCancel') { + const completion = this.state.get()?.inlineCompletion?.inlineCompletion; + if (completion && completion.source.provider.handleRejection) { + completion.source.provider.handleRejection(completion.source.inlineCompletions, completion.sourceInlineCompletion); + } + } + this._isActive.set(false, tx); this._source.clear(tx); }); @@ -405,13 +412,9 @@ export class InlineCompletionsModel extends Disposable { } } - public async next(): Promise { - await this._deltaSelectedInlineCompletionIndex(1); - } + public async next(): Promise { await this._deltaSelectedInlineCompletionIndex(1); } - public async previous(): Promise { - await this._deltaSelectedInlineCompletionIndex(-1); - } + public async previous(): Promise { await this._deltaSelectedInlineCompletionIndex(-1); } public async accept(editor: ICodeEditor): Promise { if (editor.getModel() !== this.textModel) { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts index 5024437497eb4..4153ebb91f934 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts @@ -6,10 +6,11 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { autorunWithStore, observableSignalFromEvent } from '../../../../../base/common/observable.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor } from '../../../../browser/editorBrowser.js'; import { Position } from '../../../../common/core/position.js'; -import { IInlineEdit, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, InlineEditProvider, InlineEditTriggerKind } from '../../../../common/languages.js'; +import { IInlineEdit, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, InlineEditProvider, InlineEditTriggerKind } from '../../../../common/languages.js'; import { ITextModel } from '../../../../common/model.js'; import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js'; @@ -33,6 +34,7 @@ export class InlineEditsAdapterContribution extends Disposable { export class InlineEditsAdapter extends Disposable { constructor( @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @ICommandService private readonly _commandService: ICommandService, ) { super(); @@ -41,14 +43,14 @@ export class InlineEditsAdapter extends Disposable { this._register(autorunWithStore((reader, store) => { didChangeSignal.read(reader); - type InlineCompletionsAndEdits = InlineCompletions & { + type InlineCompletionsAndEdits = InlineCompletions & { edits: { result: IInlineEdit; provider: InlineEditProvider; }[]; }; - store.add(this._languageFeaturesService.inlineCompletionsProvider.register('*', new class implements InlineCompletionsProvider { + store.add(this._languageFeaturesService.inlineCompletionsProvider.register('*', { async provideInlineCompletions(model: ITextModel, position: Position, context: InlineCompletionContext, token: CancellationToken): Promise { if (!context.includeInlineEdits) { return undefined; } @@ -70,20 +72,26 @@ export class InlineEditsAdapter extends Disposable { insertText: e.result.text, command: e.result.accepted, isInlineEdit: true, + edit: e.result, }; }), commands: definedEdits.flatMap(e => e.result.commands ?? []), }; - } + }, + handleRejection: (completions: InlineCompletions, item: InlineCompletionsAndEdits['items'][number]): void => { + if (item.edit.rejected) { + this._commandService.executeCommand(item.edit.rejected.id, ...(item.edit.rejected.arguments ?? [])); + } + }, freeInlineCompletions(c: InlineCompletionsAndEdits) { for (const e of c.edits) { e.provider.freeInlineEdit(e.result); } - } + }, toString(): string { return 'InlineEditsAdapter'; } - })); + } satisfies InlineCompletionsProvider)); })); } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index f26d9206020c0..caeac06342a9f 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7257,9 +7257,10 @@ declare namespace monaco.languages { */ handleItemDidShow?(completions: T, item: T['items'][number], updatedInsertText: string): void; /** - * Will be called when an item is partially accepted. + * Will be called when an item is partially accepted. TODO: also handle full acceptance here! */ handlePartialAccept?(completions: T, item: T['items'][number], acceptedCharacters: number, info: PartialAcceptInfo): void; + handleRejection?(completions: T, item: T['items'][number]): void; /** * Will be called when a completions list is no longer in use and can be garbage-collected. */ diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 5cae592917316..0854a1354b297 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -403,7 +403,7 @@ export class InlineChatController implements IEditorContribution { assertType(this._strategy); // hide/cancel inline completions when invoking IE - InlineCompletionsController.get(this._editor)?.hide(); + InlineCompletionsController.get(this._editor)?.reject(); this._sessionStore.clear(); From e494184de98559da0d8837058e1246fefd07be37 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 29 Oct 2024 12:35:51 +0100 Subject: [PATCH 043/555] Fixes https://github.com/microsoft/vscode-copilot/issues/9872 (#232478) --- .../browser/model/inlineCompletionsModel.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 56a8535fbcca6..5c00326791b6c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -295,7 +295,8 @@ export class InlineCompletionsModel extends Disposable { const cursorPos = this._primaryPosition.read(reader); const cursorAtInlineEdit = LineRange.fromRangeInclusive(edit.range).addMargin(1, 1).contains(cursorPos.lineNumber); - if (item.inlineEditCompletion.request.context.triggerKind === InlineCompletionTriggerKind.Automatic && this._shouldHideInlineEdit.read(reader) && !cursorAtInlineEdit) { return undefined; } + const shouldShow = this._alwaysShowInlineEdit.read(reader); + if (!shouldShow && item.inlineEditCompletion.request.context.triggerKind === InlineCompletionTriggerKind.Automatic && this._shouldHideInlineEdit.read(reader) && !cursorAtInlineEdit) { return undefined; } const cursorDist = LineRange.fromRange(edit.range).distanceToLine(this._primaryPosition.read(reader).lineNumber); const disableCollapsing = true; @@ -343,6 +344,15 @@ export class InlineCompletionsModel extends Disposable { } }); + private readonly _alwaysShowInlineEdit = observableValue(this, false); + + protected readonly _resetAlwaysShowInlineEdit = this._register(autorun(reader => { + this._primaryPosition.read(reader); + this._textModelVersionId.read(reader); + + this._alwaysShowInlineEdit.set(false, undefined); + })); + public readonly status = derived(this, reader => { if (this._source.loading.read(reader)) { return 'loading'; } const s = this.state.read(reader); @@ -470,6 +480,8 @@ export class InlineCompletionsModel extends Disposable { .then(undefined, onUnexpectedExternalError); completion.source.removeRef(); } + + this._alwaysShowInlineEdit.set(true, undefined); } public async acceptNextWord(editor: ICodeEditor): Promise { From b7c8fa4cce68bf068c48f0687581b46102dd4ef3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 29 Oct 2024 06:06:29 -0700 Subject: [PATCH 044/555] Fix hover identity, dispose timer on widget dispose --- .../services/hoverService/hoverService.ts | 2 +- .../services/hoverService/hoverWidget.ts | 32 +++++++------------ src/vs/platform/hover/browser/hover.ts | 6 +++- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 249aed774b75e..8ead8b09cdba8 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -83,7 +83,7 @@ export class HoverService extends Disposable implements IHoverService { // Only clear the current options if it's the current hover, the current options help // reduce flickering when the same hover is shown multiple times - if (this._currentHoverOptions === options) { + if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { this._currentHoverOptions = undefined; } hoverDisposables.dispose(); diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 17ac7723a45ef..62ea0e865c1e7 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import './hover.css'; -import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { Event, Emitter } from '../../../../base/common/event.js'; import * as dom from '../../../../base/browser/dom.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; @@ -23,6 +23,7 @@ import { isMacintosh } from '../../../../base/common/platform.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { status } from '../../../../base/browser/ui/aria/aria.js'; import type { IHoverOptions, IHoverTarget, IHoverWidget } from '../../../../base/browser/ui/hover/hover.js'; +import { TimeoutTimer } from '../../../../base/common/async.js'; const $ = dom.$; type TargetRect = { @@ -634,7 +635,7 @@ export class HoverWidget extends Widget implements IHoverWidget { class CompositeMouseTracker extends Widget { private _isMouseIn: boolean = true; - private _mouseTimeout: number | undefined; + private readonly _mouseTimer: MutableDisposable = this._register(new MutableDisposable()); private readonly _onMouseOut = this._register(new Emitter()); get onMouseOut(): Event { return this._onMouseOut.event; } @@ -652,32 +653,23 @@ class CompositeMouseTracker extends Widget { private _eventDebounceDelay: number = 200 ) { super(); - this._elements.forEach(n => this.onmouseover(n, () => this._onTargetMouseOver(n))); - this._elements.forEach(n => this.onmouseleave(n, () => this._onTargetMouseLeave(n))); + + for (const element of this._elements) { + this.onmouseover(element, () => this._onTargetMouseOver()); + this.onmouseleave(element, () => this._onTargetMouseLeave()); + } } - private _onTargetMouseOver(target: HTMLElement): void { + private _onTargetMouseOver(): void { this._isMouseIn = true; - this._clearEvaluateMouseStateTimeout(target); + this._mouseTimer.clear(); } - private _onTargetMouseLeave(target: HTMLElement): void { + private _onTargetMouseLeave(): void { this._isMouseIn = false; - this._evaluateMouseState(target); - } - - private _evaluateMouseState(target: HTMLElement): void { - this._clearEvaluateMouseStateTimeout(target); // Evaluate whether the mouse is still outside asynchronously such that other mouse targets // have the opportunity to first their mouse in event. - this._mouseTimeout = dom.getWindow(target).setTimeout(() => this._fireIfMouseOutside(), this._eventDebounceDelay); - } - - private _clearEvaluateMouseStateTimeout(target: HTMLElement): void { - if (this._mouseTimeout) { - dom.getWindow(target).clearTimeout(this._mouseTimeout); - this._mouseTimeout = undefined; - } + this._mouseTimer.value = new TimeoutTimer(() => this._fireIfMouseOutside(), this._eventDebounceDelay); } private _fireIfMouseOutside(): void { diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 779a4598fbcb6..64fe7c21defe6 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -63,7 +63,11 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate })); } - const id = isHTMLElement(options.content) ? undefined : options.content.toString(); + const id = isHTMLElement(options.content) + ? undefined + : typeof options.content === 'string' + ? options.content.toString() + : options.content.value; return this.hoverService.showHover({ ...options, From 1b9af3b18a4c0ce43edd0e0203ca1212926a21ec Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 29 Oct 2024 06:59:03 -0700 Subject: [PATCH 045/555] Move timeline hovers to the side Fixes #232487 --- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 8 ++++---- .../browser/parts/editor/multiEditorTabsControl.ts | 2 +- .../chatContentParts/chatAttachmentsContentPart.ts | 2 +- .../workbench/contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/timeline/browser/timelinePane.ts | 13 ++++++++++--- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index f9804179a896b..981933151ccbb 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -24,7 +24,7 @@ export interface IIconLabelCreationOptions { readonly supportDescriptionHighlights?: boolean; readonly supportIcons?: boolean; readonly hoverDelegate?: IHoverDelegate; - readonly hoverTargetOverrride?: HTMLElement; + readonly hoverTargetOverride?: HTMLElement; } export interface IIconLabelValueOptions { @@ -211,11 +211,11 @@ export class IconLabel extends Disposable { } let hoverTarget = htmlElement; - if (this.creationOptions?.hoverTargetOverrride) { - if (!dom.isAncestor(htmlElement, this.creationOptions.hoverTargetOverrride)) { + if (this.creationOptions?.hoverTargetOverride) { + if (!dom.isAncestor(htmlElement, this.creationOptions.hoverTargetOverride)) { throw new Error('hoverTargetOverrride must be an ancestor of the htmlElement'); } - hoverTarget = this.creationOptions.hoverTargetOverrride; + hoverTarget = this.creationOptions.hoverTargetOverride; } if (this.hoverDelegate.showNativeHover) { diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 397d6204cc1a6..fdf85346726ae 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -817,7 +817,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { tabContainer.appendChild(tabBorderTopContainer); // Tab Editor Label - const editorLabel = this.tabResourceLabels.create(tabContainer, { hoverTargetOverrride: tabContainer }); + const editorLabel = this.tabResourceLabels.create(tabContainer, { hoverTargetOverride: tabContainer }); // Tab Actions const tabActionsContainer = document.createElement('div'); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts index 7165e2f8b8e7f..605889e6b352e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -55,7 +55,7 @@ export class ChatAttachmentsContentPart extends Disposable { } const widget = dom.append(container, dom.$('.chat-attached-context-attachment.show-file-icons')); - const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverrride: widget }); + const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverride: widget }); const correspondingContentReference = this.contentReferences.find((ref) => typeof ref.reference === 'object' && 'variableName' in ref.reference && ref.reference.variableName === attachment.name); const isAttachmentOmitted = correspondingContentReference?.options?.status?.kind === ChatResponseReferencePartStatusKind.Omitted; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 4c84444fb5f53..ca4adeb6f308b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -735,7 +735,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } const widget = dom.append(container, $('.chat-attached-context-attachment.show-file-icons')); - const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverrride: widget }); + const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverride: widget }); let ariaLabel: string | undefined; diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 60947efddb90c..5f9457bee37a8 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -56,8 +56,8 @@ import { IExtensionService } from '../../../services/extensions/common/extension import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { AriaRole } from '../../../../base/browser/ui/aria/aria.js'; import { ILocalizedString } from '../../../../platform/action/common/action.js'; -import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; -import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IHoverService, WorkbenchHoverDelegate } from '../../../../platform/hover/browser/hover.js'; +import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; const ItemHeight = 22; @@ -1159,7 +1159,14 @@ class TimelineTreeRenderer implements ITreeRenderer Date: Tue, 29 Oct 2024 07:21:55 -0700 Subject: [PATCH 046/555] Use a single store to track disposables in setupManagedHover --- .../services/hoverService/hoverService.ts | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 8ead8b09cdba8..6e6c0567d80d3 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -231,25 +231,25 @@ export class HoverService extends Disposable implements IHoverService { }, delay); }; + const store = new DisposableStore(); let isMouseDown = false; - const mouseDownEmitter = addDisposableListener(targetElement, EventType.MOUSE_DOWN, () => { + store.add(addDisposableListener(targetElement, EventType.MOUSE_DOWN, () => { isMouseDown = true; hideHover(true, true); - }, true); - const mouseUpEmitter = addDisposableListener(targetElement, EventType.MOUSE_UP, () => { + }, true)); + store.add(addDisposableListener(targetElement, EventType.MOUSE_UP, () => { isMouseDown = false; - }, true); - const mouseLeaveEmitter = addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e: MouseEvent) => { + }, true)); + store.add(addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e: MouseEvent) => { isMouseDown = false; hideHover(false, (e).fromElement === targetElement); - }, true); - - const onMouseOver = (e: MouseEvent) => { + }, true)); + store.add(addDisposableListener(targetElement, EventType.MOUSE_OVER, (e: MouseEvent) => { if (hoverPreparation) { return; } - const toDispose: DisposableStore = new DisposableStore(); + const mouseOverStore: DisposableStore = new DisposableStore(); const target: IHoverDelegateTarget = { targetElements: [targetElement], @@ -263,18 +263,17 @@ export class HoverService extends Disposable implements IHoverService { hideHover(true, true); } }; - toDispose.add(addDisposableListener(targetElement, EventType.MOUSE_MOVE, onMouseMove, true)); + mouseOverStore.add(addDisposableListener(targetElement, EventType.MOUSE_MOVE, onMouseMove, true)); } - hoverPreparation = toDispose; + hoverPreparation = mouseOverStore; if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target as HTMLElement, targetElement) !== targetElement) { return; // Do not show hover when the mouse is over another hover target } - toDispose.add(triggerShowHover(hoverDelegate.delay, false, target)); - }; - const mouseOverDomEmitter = addDisposableListener(targetElement, EventType.MOUSE_OVER, onMouseOver, true); + mouseOverStore.add(triggerShowHover(hoverDelegate.delay, false, target)); + }, true)); const onFocus = () => { if (isMouseDown || hoverPreparation) { @@ -292,9 +291,8 @@ export class HoverService extends Disposable implements IHoverService { }; // Do not show hover when focusing an input or textarea - let focusDomEmitter: undefined | IDisposable; if (!isEditableElement(targetElement)) { - focusDomEmitter = addDisposableListener(targetElement, EventType.FOCUS, onFocus, true); + store.add(addDisposableListener(targetElement, EventType.FOCUS, onFocus, true)); } const hover: IManagedHover = { @@ -311,11 +309,7 @@ export class HoverService extends Disposable implements IHoverService { }, dispose: () => { this._managedHovers.delete(targetElement); - mouseOverDomEmitter.dispose(); - mouseLeaveEmitter.dispose(); - mouseDownEmitter.dispose(); - mouseUpEmitter.dispose(); - focusDomEmitter?.dispose(); + store.dispose(); hideHover(true, true); } }; From f52a26d294b478128ce3fb634ebe5ae24065325a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 29 Oct 2024 15:25:25 +0100 Subject: [PATCH 047/555] [Edit Context]: Adding method to refresh focus state for native edit context (#232479) adding method to refresh focus state --- .../editContext/native/nativeEditContext.ts | 11 +++++++++-- .../editContext/native/nativeEditContextUtils.ts | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 333632168b123..994134928ac86 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -178,9 +178,16 @@ export class NativeEditContext extends AbstractEditContext { public isFocused(): boolean { return this._focusTracker.isFocused; } - public focus(): void { this._focusTracker.focus(); } + public focus(): void { + this._focusTracker.focus(); - public refreshFocusState(): void { } + // If the editor is off DOM, focus cannot be really set, so let's double check that we have managed to set the focus + this.refreshFocusState(); + } + + public refreshFocusState(): void { + this._focusTracker.refreshFocusState(); + } // TODO: added as a workaround fix for https://github.com/microsoft/vscode/issues/229825 // When this issue will be fixed the following should be removed. diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts index e55e1521fac13..b3166de0437a3 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener } from '../../../../../base/browser/dom.js'; +import { addDisposableListener, getActiveWindow } from '../../../../../base/browser/dom.js'; import { IDisposable, Disposable } from '../../../../../base/common/lifecycle.js'; export interface ITypeData { @@ -40,6 +40,11 @@ export class FocusTracker extends Disposable { this._domNode.focus(); } + public refreshFocusState(): void { + const focused = this._domNode === getActiveWindow().document.activeElement; + this._handleFocusedChanged(focused); + } + get isFocused(): boolean { return this._isFocused; } From 1281dbb7c13530ada301347f7ca0eeea7edc340c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 29 Oct 2024 07:28:00 -0700 Subject: [PATCH 048/555] Include new shell executables in traverseTree Fixes #232424 --- .../platform/terminal/node/windowsShellHelper.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/node/windowsShellHelper.ts b/src/vs/platform/terminal/node/windowsShellHelper.ts index 2d63433bbdc32..c073c7c01b685 100644 --- a/src/vs/platform/terminal/node/windowsShellHelper.ts +++ b/src/vs/platform/terminal/node/windowsShellHelper.ts @@ -23,13 +23,20 @@ const SHELL_EXECUTABLES = [ 'powershell.exe', 'pwsh.exe', 'bash.exe', + 'git-cmd.exe', 'wsl.exe', 'ubuntu.exe', 'ubuntu1804.exe', 'kali.exe', 'debian.exe', 'opensuse-42.exe', - 'sles-12.exe' + 'sles-12.exe', + 'julialauncher.exe', + 'nu.exe', +]; + +const SHELL_EXECUTABLE_REGEXES = [ + /^python(\d(\.\d{0,2})?)?\.exe$/, ]; let windowsProcessTree: typeof WindowsProcessTreeType; @@ -90,6 +97,11 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe if (SHELL_EXECUTABLES.indexOf(tree.name) === -1) { return tree.name; } + for (const regex of SHELL_EXECUTABLE_REGEXES) { + if (tree.name.match(regex)) { + return tree.name; + } + } if (!tree.children || tree.children.length === 0) { return tree.name; } From 67d840477c95fe3a58d8e0a9397a8e1c8b15497c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 29 Oct 2024 15:42:26 +0100 Subject: [PATCH 049/555] Fixes #9880 (#232492) --- .../inlineCompletions/browser/model/inlineCompletionsModel.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 5c00326791b6c..3b12a5919a640 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -24,7 +24,6 @@ import { ScrollType } from '../../../../common/editorCommon.js'; import { Command, InlineCompletion, InlineCompletionContext, InlineCompletionTriggerKind, PartialAcceptTriggerKind } from '../../../../common/languages.js'; import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js'; import { EndOfLinePreference, ITextModel } from '../../../../common/model.js'; -import { TextModelText } from '../../../../common/model/textModelText.js'; import { IFeatureDebounceInformation } from '../../../../common/services/languageFeatureDebounce.js'; import { IModelContentChangedEvent } from '../../../../common/textModelEvents.js'; import { SnippetController2 } from '../../../snippet/browser/snippetController2.js'; @@ -290,8 +289,6 @@ export class InlineCompletionsModel extends Disposable { let edit = item.inlineEditCompletion.toSingleTextEdit(reader); edit = singleTextRemoveCommonPrefix(edit, model); - if (edit.isEffectiveDeletion(new TextModelText(model))) { return undefined; } - const cursorPos = this._primaryPosition.read(reader); const cursorAtInlineEdit = LineRange.fromRangeInclusive(edit.range).addMargin(1, 1).contains(cursorPos.lineNumber); From 94e1b25d6503106104ac84a240f09e1b50c6d2fc Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 29 Oct 2024 16:02:07 +0100 Subject: [PATCH 050/555] Fixes https://github.com/microsoft/vscode-copilot/issues/9856 (#232494) --- .../browser/view/inlineEdits/inlineDiffView.ts | 4 +--- .../browser/view/inlineEdits/inlineEditsView.ts | 13 +++++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineDiffView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineDiffView.ts index d285d0ad143cc..0061d1c4eb1ff 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineDiffView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineDiffView.ts @@ -131,7 +131,5 @@ function allowsTrueInlineDiffRendering(mapping: DetailedLineRangeMapping): boole return false; } return mapping.innerChanges.every(c => - (rangeIsSingleLine(c.modifiedRange) && rangeIsSingleLine(c.originalRange)) - || c.originalRange.equalsRange(new Range(1, 1, 1, 1)) - ); + (rangeIsSingleLine(c.modifiedRange) && rangeIsSingleLine(c.originalRange))); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index 6deed7d0410b3..5c7483553d96a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -405,10 +405,15 @@ export class InlineEditsView extends Disposable { this._previewTextModel.setValue(uiState.newText); const range = uiState.edit.originalLineRange; - this._previewEditor.setHiddenAreas([ - new Range(1, 1, range.startLineNumber - 1, 1), - new Range(range.startLineNumber + uiState.newTextLineCount, 1, this._previewTextModel.getLineCount() + 1, 1), - ], undefined, true); + const hiddenAreas: Range[] = []; + if (range.startLineNumber > 1) { + hiddenAreas.push(new Range(1, 1, range.startLineNumber - 1, 1)); + } + if (range.startLineNumber + uiState.newTextLineCount < this._previewTextModel.getLineCount() + 1) { + hiddenAreas.push(new Range(range.startLineNumber + uiState.newTextLineCount, 1, this._previewTextModel.getLineCount() + 1, 1)); + } + + this._previewEditor.setHiddenAreas(hiddenAreas, undefined, true); }).recomputeInitiallyAndOnChange(this._store); From 6d074207225be7a299080c2202469ecbe3ff5df0 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 29 Oct 2024 16:11:10 +0100 Subject: [PATCH 051/555] extract `IconPath` type for uri, uris, and ThemeIcon combo (#232471) re https://github.com/microsoft/vscode/issues/124363 --- src/vscode-dts/vscode.d.ts | 126 ++++++++----------------------------- 1 file changed, 25 insertions(+), 101 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 7a6b66a44f6fc..75c7d5d13b642 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -946,6 +946,21 @@ declare module 'vscode' { constructor(id: string, color?: ThemeColor); } + /** + * Represents an icon in the UI. This is either an uri, separate uris for the light- and dark-themes, + * or a {@link ThemeIcon theme icon}. + */ + export type IconPath = Uri | { + /** + * The icon path for the light theme. + */ + light: Uri; + /** + * The icon path for the dark theme. + */ + dark: Uri; + } | ThemeIcon; + /** * Represents theme specific rendering styles for a {@link TextEditorDecorationType text editor decoration}. */ @@ -1868,16 +1883,7 @@ declare module 'vscode' { /** * The icon path or {@link ThemeIcon} for the QuickPickItem. */ - iconPath?: Uri | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - } | ThemeIcon; + iconPath?: IconPath; /** * A human-readable string which is rendered less prominent in the same line. Supports rendering of @@ -3876,16 +3882,7 @@ declare module 'vscode' { /** * The icon path or {@link ThemeIcon} for the edit. */ - iconPath?: Uri | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - } | ThemeIcon; + iconPath?: IconPath; } /** @@ -11902,16 +11899,7 @@ declare module 'vscode' { * When `falsy`, {@link ThemeIcon.Folder Folder Theme Icon} is assigned, if item is collapsible otherwise {@link ThemeIcon.File File Theme Icon}. * When a file or folder {@link ThemeIcon} is specified, icon is derived from the current file icon theme for the specified theme icon using {@link TreeItem.resourceUri resourceUri} (if provided). */ - iconPath?: string | Uri | { - /** - * The icon path for the light theme. - */ - light: string | Uri; - /** - * The icon path for the dark theme. - */ - dark: string | Uri; - } | ThemeIcon; + iconPath?: IconPath; /** * A human-readable string which is rendered less prominent. @@ -12112,16 +12100,7 @@ declare module 'vscode' { /** * The icon path or {@link ThemeIcon} for the terminal. */ - iconPath?: Uri | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - } | ThemeIcon; + iconPath?: IconPath; /** * The icon {@link ThemeColor} for the terminal. @@ -12160,16 +12139,7 @@ declare module 'vscode' { /** * The icon path or {@link ThemeIcon} for the terminal. */ - iconPath?: Uri | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - } | ThemeIcon; + iconPath?: IconPath; /** * The icon {@link ThemeColor} for the terminal. @@ -12910,17 +12880,7 @@ declare module 'vscode' { /** * Icon for the button. */ - readonly iconPath: Uri | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - } | ThemeIcon; - + readonly iconPath: IconPath; /** * An optional tooltip. */ @@ -18998,16 +18958,7 @@ declare module 'vscode' { /** * An icon for the participant shown in UI. */ - iconPath?: Uri | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - } | ThemeIcon; + iconPath?: IconPath; /** * The handler for requests to this participant. @@ -19174,16 +19125,7 @@ declare module 'vscode' { * @param value A uri or location * @param iconPath Icon for the reference shown in UI */ - reference(value: Uri | Location, iconPath?: Uri | ThemeIcon | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - }): void; + reference(value: Uri | Location, iconPath?: IconPath): void; /** * Pushes a part to this stream. @@ -19297,32 +19239,14 @@ declare module 'vscode' { /** * The icon for the reference. */ - iconPath?: Uri | ThemeIcon | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - }; + iconPath?: IconPath; /** * Create a new ChatResponseReferencePart. * @param value A uri or location * @param iconPath Icon for the reference shown in UI */ - constructor(value: Uri | Location, iconPath?: Uri | ThemeIcon | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - }); + constructor(value: Uri | Location, iconPath?: IconPath); } /** From 4d844ca2062e9a3f6e0cb83dec7412d2d3aff82b Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 29 Oct 2024 16:22:39 +0100 Subject: [PATCH 052/555] Fixes inline edit flickering (#232498) --- src/vs/editor/common/core/textEdit.ts | 5 ++ .../view/inlineEdits/inlineEditsView.ts | 51 +++++++++++++------ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/common/core/textEdit.ts b/src/vs/editor/common/core/textEdit.ts index 18cea2af1b67e..9307784837fd9 100644 --- a/src/vs/editor/common/core/textEdit.ts +++ b/src/vs/editor/common/core/textEdit.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { equals } from '../../../base/common/arrays.js'; import { assert, assertFn, checkAdjacentItems } from '../../../base/common/assert.js'; import { BugIndicatingError } from '../../../base/common/errors.js'; import { commonPrefixLength, commonSuffixLength, splitLines } from '../../../base/common/strings.js'; @@ -189,6 +190,10 @@ export class TextEdit { } return new SingleTextEdit(Range.fromPositions(startPos, endPos), newText); } + + equals(other: TextEdit): boolean { + return equals(this.edits, other.edits, (a, b) => a.equals(b)); + } } export class SingleTextEdit { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index 5c7483553d96a..86faf28abd5ac 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -8,7 +8,7 @@ import { numberComparator } from '../../../../../../base/common/arrays.js'; import { findFirstMin } from '../../../../../../base/common/arraysFind.js'; import { createHotClass } from '../../../../../../base/common/hotReloadHelpers.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { autorun, constObservable, derived, derivedDisposable, derivedOpts, derivedWithCancellationToken, IObservable, observableFromEvent, ObservablePromise } from '../../../../../../base/common/observable.js'; +import { autorun, constObservable, derived, derivedDisposable, derivedOpts, IObservable, observableFromEvent, ObservablePromise } from '../../../../../../base/common/observable.js'; import { getIndentationLength, splitLines } from '../../../../../../base/common/strings.js'; import { MenuId, MenuItemAction } from '../../../../../../platform/actions/common/actions.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; @@ -39,11 +39,13 @@ import { diffInserted, diffRemoved } from '../../../../../../platform/theme/comm import { CustomizedMenuWorkbenchToolBar } from '../../hintsWidget/inlineCompletionsHintsWidget.js'; import { Command } from '../../../../../common/languages.js'; import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; -import { structuralEquals } from '../../../../../../base/common/equals.js'; +import { equalsIfDefined, itemEquals, structuralEquals } from '../../../../../../base/common/equals.js'; import { IAction } from '../../../../../../base/common/actions.js'; import { editorLineHighlightBorder } from '../../../../../common/core/editorColorRegistry.js'; import { ActionViewItem } from '../../../../../../base/browser/ui/actionbar/actionViewItems.js'; import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; +import { LRUCachedFunction } from '../../../../../../base/common/cache.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; export class InlineEditsViewAndDiffProducer extends Disposable { public static readonly hot = createHotClass(InlineEditsViewAndDiffProducer); @@ -55,7 +57,23 @@ export class InlineEditsViewAndDiffProducer extends Disposable { private readonly _modifiedModel = derivedDisposable(() => this._modelService.createModel( '', null, this._modelUriGenerator.getUniqueUri())).keepObserved(this._store); - private readonly _inlineEditPromise = derivedWithCancellationToken | undefined>(this, (reader, token) => { + private readonly _differ = new LRUCachedFunction({ getCacheKey: JSON.stringify }, (arg: { original: string; modified: string }) => { + this._originalModel.get().setValue(arg.original); + this._modifiedModel.get().setValue(arg.modified); + + const diffAlgo = this._diffProviderFactoryService.createDiffProvider({ diffAlgorithm: 'advanced' }); + + return ObservablePromise.fromFn(async () => { + const result = await diffAlgo.computeDiff(this._originalModel.get(), this._modifiedModel.get(), { + computeMoves: false, + ignoreTrimWhitespace: false, + maxComputationTimeMs: 1000, + }, CancellationToken.None); + return result; + }); + }); + + private readonly _inlineEditPromise = derived | undefined>(this, (reader) => { const inlineEdit = this._edit.read(reader); if (!inlineEdit) { return undefined; } @@ -64,18 +82,13 @@ export class InlineEditsViewAndDiffProducer extends Disposable { const text = new TextModelText(this._editor.getModel()!); const edit = inlineEdit.edit.extendToFullLine(text); - this._originalModel.get().setValue(this._editor.getModel()!.getValueInRange(edit.range)); - this._modifiedModel.get().setValue(edit.text); + const diffResult = this._differ.get({ original: this._editor.getModel()!.getValueInRange(edit.range), modified: edit.text }); - const diffAlgo = this._diffProviderFactoryService.createDiffProvider({ diffAlgorithm: 'advanced' }); - return ObservablePromise.fromFn(async () => { - const result = await diffAlgo.computeDiff(this._originalModel.get(), this._modifiedModel.get(), { - computeMoves: false, - ignoreTrimWhitespace: false, - maxComputationTimeMs: 1000, - }, token); - - if (token.isCancellationRequested || result.identical) { return undefined; } + return diffResult.promiseResult.map(p => { + if (!p || !p.data) { + return undefined; + } + const result = p.data; const rangeStartPos = edit.range.getStartPosition(); const innerChanges = result.changes.flatMap(c => c.innerChanges!); @@ -95,7 +108,7 @@ export class InlineEditsViewAndDiffProducer extends Disposable { }); }); - private readonly _inlineEdit = this._inlineEditPromise.map((p, reader) => p?.promiseResult?.read(reader)?.data); + private readonly _inlineEdit = derivedOpts({ owner: this, equalsFn: equalsIfDefined(itemEquals()) }, reader => this._inlineEditPromise.read(reader)?.read(reader)); constructor( private readonly _editor: ICodeEditor, @@ -125,6 +138,14 @@ export class InlineEditWithChanges { public readonly commands: readonly Command[], ) { } + + equals(other: InlineEditWithChanges) { + return this.originalText.getValue() === other.originalText.getValue() && + this.edit.equals(other.edit) && + this.isCollapsed === other.isCollapsed && + this.showInlineIfPossible === other.showInlineIfPossible && + this.commands === other.commands; + } } export const originalBackgroundColor = registerColor( From 852e43ad4492977990edebcad344ed8db313cce0 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 29 Oct 2024 17:03:22 +0100 Subject: [PATCH 053/555] Always sending line feed on enter so that indentation works correctly in CRLF-based files (#232501) always type line feed --- .../browser/controller/editContext/native/nativeEditContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 994134928ac86..9d6556e5373c0 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -105,7 +105,7 @@ export class NativeEditContext extends AbstractEditContext { })); this._register(addDisposableListener(this.domNode.domNode, 'beforeinput', async (e) => { if (e.inputType === 'insertParagraph' || e.inputType === 'insertLineBreak') { - this._onType(viewController, { text: this._context.viewModel.model.getEOL(), replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }); + this._onType(viewController, { text: '\n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }); } })); From 32ff8728c42f978f276886ea10c5b933682fa262 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:39:55 -0700 Subject: [PATCH 054/555] Update src/vs/workbench/contrib/timeline/browser/timelinePane.ts Co-authored-by: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> --- src/vs/workbench/contrib/timeline/browser/timelinePane.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 5f9457bee37a8..9c91f4f0b83b1 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -1160,9 +1160,6 @@ class TimelineTreeRenderer implements ITreeRenderer Date: Tue, 29 Oct 2024 11:50:41 -0500 Subject: [PATCH 055/555] fix quick chat accessibility help bug (#232506) fix #229059 --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 9259096b48a0a..974bd181d4cf1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -46,7 +46,9 @@ export function getAccessibilityHelpText(type: 'panelChat' | 'inlineChat' | 'qui content.push(localize('chat.overview', 'The chat view is comprised of an input box and a request/response list. The input box is used to make requests and the list is used to display responses.')); content.push(localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the submit button to run a new request.')); content.push(localize('chat.inspectResponse', 'In the input box, inspect the last response in the accessible view{0}.', '')); - content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it.')); + if (type === 'panelChat') { + content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it.')); + } content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.')); content.push(localize('workbench.action.chat.focus', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke the Focus Chat command{0}.', getChatFocusKeybindingLabel(keybindingService, type, false))); content.push(localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command{0}.', getChatFocusKeybindingLabel(keybindingService, type, true))); From 71cec0c0f79b5f6be6b8f2b57f74a4d37bbb5aa7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 29 Oct 2024 10:44:29 -0700 Subject: [PATCH 056/555] Fix missing chat responses after window reload (#232509) --- src/vs/workbench/contrib/chat/common/chatModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index cc7d9371cf369..021292c295406 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -991,8 +991,8 @@ export class ChatModel extends Disposable implements IChatModel { const result = 'responseErrorDetails' in raw ? // eslint-disable-next-line local/code-no-dangerous-type-assertions { errorDetails: raw.responseErrorDetails } as IChatAgentResult : raw.result; + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.voteDownReason, result, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.voteDownReason, result, raw.followups); request.response.applyReference(revive(raw.usedContext)); } From 73eea841005f275e88e0dde90016cca8bec26339 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 29 Oct 2024 18:44:46 +0100 Subject: [PATCH 057/555] add source and links (#232504) * add source and links * use opener service --- .../extensions/browser/extensionEditor.ts | 50 +++++++++++++++---- .../browser/media/extensionEditor.css | 6 +++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 4fa7c35377e4d..37bd3016708a3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -17,7 +17,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, MutableDisposable, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas, matchesScheme } from '../../../../base/common/network.js'; -import { language } from '../../../../base/common/platform.js'; +import { isNative, language } from '../../../../base/common/platform.js'; import { isUndefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; @@ -1031,35 +1031,67 @@ export class ExtensionEditor extends EditorPane { ) ); } + if (extension.source !== 'gallery') { + const element = $('div', undefined, extension.source === 'vsix' ? localize('vsix', "VSIX") : localize('other', "Local")); + append(installInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('source', "Source")), + element + ) + ); + if (isNative && extension.source === 'resource' && extension.location.scheme === Schemas.file) { + element.classList.add('link'); + element.title = extension.location.fsPath; + this.transientDisposables.add(onClick(element, () => this.openerService.open(extension.location, { openExternal: true }))); + } + } if (extension.size) { + const element = $('div', undefined, toMemoryString(extension.size)); append(installInfo, $('.more-info-entry', undefined, $('div', { title: localize('size when installed', "Size when installed") }, localize('size', "Size")), - $('div', undefined, toMemoryString(extension.size)) + element ) ); + if (isNative && extension.location.scheme === Schemas.file) { + element.classList.add('link'); + element.title = extension.location.fsPath; + this.transientDisposables.add(onClick(element, () => this.openerService.open(extension.location, { openExternal: true }))); + } } - this.computeCacheSize(extension).then(size => { - if (size) { + this.getCacheLocation(extension).then(cacheLocation => { + if (!cacheLocation) { + return; + } + computeSize(cacheLocation, this.fileService).then(cacheSize => { + if (!cacheSize) { + return; + } + const element = $('div', undefined, toMemoryString(cacheSize)); append(installInfo, $('.more-info-entry', undefined, $('div', { title: localize('disk space used', "Cache size") }, localize('cache size', "Cache")), - $('div', undefined, toMemoryString(size))) + element) ); - } + if (isNative && extension.location.scheme === Schemas.file) { + element.classList.add('link'); + element.title = cacheLocation.fsPath; + this.transientDisposables.add(onClick(element, () => this.openerService.open(cacheLocation.with({ scheme: Schemas.file }), { openExternal: true }))); + } + }); }); } - private async computeCacheSize(extension: ILocalExtension): Promise { + private async getCacheLocation(extension: ILocalExtension): Promise { let extensionCacheLocation = this.uriIdentityService.extUri.joinPath(this.userDataProfilesService.defaultProfile.globalStorageHome, extension.identifier.id.toLowerCase()); if (extension.location.scheme === Schemas.vscodeRemote) { const environment = await this.remoteAgentService.getEnvironment(); if (!environment) { - return 0; + return undefined; } extensionCacheLocation = this.uriIdentityService.extUri.joinPath(environment.globalStorageHome, extension.identifier.id.toLowerCase()); } - return computeSize(extensionCacheLocation, this.fileService); + return extensionCacheLocation; } private renderMarketplaceInfo(container: HTMLElement, extension: IExtension): void { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 6a742b8bddbe8..3201223829698 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -308,11 +308,17 @@ margin-bottom: 0px; } +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry > .link { + cursor: pointer; +} + +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry > .link, .extension-editor > .header > .details > .pre-release-text a, .extension-editor > .header > .details > .actions-status-container > .status a { color: var(--vscode-textLink-foreground) } +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry .link:hover, .extension-editor > .header > .details > .pre-release-text a:hover, .extension-editor > .header > .details > .actions-status-container > .status a:hover { text-decoration: underline; From 9ae3d699dc78dd273eb7bd815499615c73a8e35b Mon Sep 17 00:00:00 2001 From: David Martos Date: Tue, 29 Oct 2024 18:48:41 +0100 Subject: [PATCH 058/555] Fix PATH prepending when using Fish (#232291) * Support Linux on EnvMixin to handle fish_user_paths being prepended to the PATH after the shell integration * apply fix based on shell * update comments * link to issue * remove isLinux check * Update comment Co-authored-by: Megan Rogge --------- Co-authored-by: Megan Rogge Co-authored-by: Megan Rogge --- .../terminal/node/terminalEnvironment.ts | 23 ++++++++++++------- .../common/scripts/shellIntegration.fish | 4 +++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index bcc1dc553a18b..fbaf3031506ae 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -167,7 +167,7 @@ export function getShellIntegrationInjection( newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash); } else if (areZshBashFishLoginArgs(originalArgs)) { envMixin['VSCODE_SHELL_LOGIN'] = '1'; - addEnvMixinPathPrefix(options, envMixin); + addEnvMixinPathPrefix(options, envMixin, shell); newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash); } if (!newArgs) { @@ -189,7 +189,7 @@ export function getShellIntegrationInjection( newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash); } else if (areZshBashFishLoginArgs(originalArgs)) { envMixin['VSCODE_SHELL_LOGIN'] = '1'; - addEnvMixinPathPrefix(options, envMixin); + addEnvMixinPathPrefix(options, envMixin, shell); newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash); } if (!newArgs) { @@ -205,13 +205,17 @@ export function getShellIntegrationInjection( newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Fish); } else if (areZshBashFishLoginArgs(originalArgs)) { newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.FishLogin); - addEnvMixinPathPrefix(options, envMixin); } else if (originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.Fish) || originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.FishLogin)) { newArgs = originalArgs; } if (!newArgs) { return undefined; } + + // On fish, '$fish_user_paths' is always prepended to the PATH, for both login and non-login shells, so we need + // to apply the path prefix fix always, not only for login shells (see #232291) + addEnvMixinPathPrefix(options, envMixin, shell); + newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot); return { newArgs, envMixin }; @@ -238,7 +242,7 @@ export function getShellIntegrationInjection( newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Zsh); } else if (areZshBashFishLoginArgs(originalArgs)) { newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.ZshLogin); - addEnvMixinPathPrefix(options, envMixin); + addEnvMixinPathPrefix(options, envMixin, shell); } else if (originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.Zsh) || originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.ZshLogin)) { newArgs = originalArgs; } @@ -284,16 +288,19 @@ export function getShellIntegrationInjection( } /** - * On macOS the profile calls path_helper which adds a bunch of standard bin directories to the - * beginning of the PATH. This causes significant problems for the environment variable + * There are a few situations where some directories are added to the beginning of the PATH. + * 1. On macOS when the profile calls path_helper. + * 2. For fish terminals, which always prepend "$fish_user_paths" to the PATH. + * + * This causes significant problems for the environment variable * collection API as the custom paths added to the end will now be somewhere in the middle of * the PATH. To combat this, VSCODE_PATH_PREFIX is used to re-apply any prefix after the profile * has run. This will cause duplication in the PATH but should fix the issue. * * See #99878 for more information. */ -function addEnvMixinPathPrefix(options: ITerminalProcessOptions, envMixin: IProcessEnvironment): void { - if (isMacintosh && options.environmentVariableCollections) { +function addEnvMixinPathPrefix(options: ITerminalProcessOptions, envMixin: IProcessEnvironment, shell: string): void { + if ((isMacintosh || shell === 'fish') && options.environmentVariableCollections) { // Deserialize and merge const deserialized = deserializeEnvironmentVariableCollections(options.environmentVariableCollections); const merged = new MergedEnvironmentVariableCollection(deserialized); diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish index bc4549e337e1b..b780c508ebf3e 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish @@ -23,7 +23,9 @@ or exit set --global VSCODE_SHELL_INTEGRATION 1 # Apply any explicit path prefix (see #99878) -if status --is-login; and set -q VSCODE_PATH_PREFIX +# On fish, '$fish_user_paths' is always prepended to the PATH, for both login and non-login shells, so we need +# to apply the path prefix fix always, not only for login shells (see #232291) +if set -q VSCODE_PATH_PREFIX set -gx PATH "$VSCODE_PATH_PREFIX$PATH" end set -e VSCODE_PATH_PREFIX From c42d363e64e0f1de08cf13376fecad51c88e53d9 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 29 Oct 2024 11:24:55 -0700 Subject: [PATCH 059/555] Add tests for #232509 (#232512) The other tests happen to go inside the block that was setting `request.response`, so add a test that restores a response that has markdown content but no references --- ...rvice_can_deserialize_with_response.0.snap | 84 +++++++++++++++++++ .../chat/test/common/chatService.test.ts | 51 +++++++++++ 2 files changed, 135 insertions(+) create mode 100644 src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap new file mode 100644 index 0000000000000..08839ed2bf7a4 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap @@ -0,0 +1,84 @@ +{ + requesterUsername: "test", + requesterAvatarIconUri: undefined, + responderUsername: "", + responderAvatarIconUri: undefined, + initialLocation: "panel", + requests: [ + { + message: { + text: "@ChatProviderWithUsedContext test request", + parts: [ + { + range: { + start: 0, + endExclusive: 28 + }, + editorRange: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 29 + }, + agent: { + name: "ChatProviderWithUsedContext", + id: "ChatProviderWithUsedContext", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, + extensionPublisherId: "", + publisherDisplayName: "", + extensionDisplayName: "", + locations: [ "panel" ], + metadata: { }, + slashCommands: [ ], + disambiguation: [ ] + }, + kind: "agent" + }, + { + range: { + start: 28, + endExclusive: 41 + }, + editorRange: { + startLineNumber: 1, + startColumn: 29, + endLineNumber: 1, + endColumn: 42 + }, + text: " test request", + kind: "text" + } + ] + }, + variableData: { variables: [ ] }, + response: [ ], + result: { errorDetails: { message: "No activated agent with id \"ChatProviderWithUsedContext\"" } }, + followups: undefined, + isCanceled: false, + vote: undefined, + voteDownReason: undefined, + agent: { + name: "ChatProviderWithUsedContext", + id: "ChatProviderWithUsedContext", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, + extensionPublisherId: "", + publisherDisplayName: "", + extensionDisplayName: "", + locations: [ "panel" ], + metadata: { }, + slashCommands: [ ], + disambiguation: [ ] + }, + slashCommand: undefined, + usedContext: undefined, + contentReferences: [ ], + codeCitations: [ ] + } + ] +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index d496011366256..393c3813b83aa 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -33,6 +33,7 @@ import { NullWorkbenchAssignmentService } from '../../../../services/assignment/ import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { TestContextService, TestExtensionService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; +import { MarkdownString } from '../../../../../base/common/htmlContent.js'; const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext'; const chatAgentWithUsedContext: IChatAgent = { @@ -67,6 +68,27 @@ const chatAgentWithUsedContext: IChatAgent = { }, }; +const chatAgentWithMarkdownId = 'ChatProviderWithMarkdown'; +const chatAgentWithMarkdown: IChatAgent = { + id: chatAgentWithMarkdownId, + name: chatAgentWithMarkdownId, + extensionId: nullExtensionDescription.identifier, + publisherDisplayName: '', + extensionPublisherId: '', + extensionDisplayName: '', + locations: [ChatAgentLocation.Panel], + metadata: {}, + slashCommands: [], + disambiguation: [], + async invoke(request, progress, history, token) { + progress({ kind: 'markdownContent', content: new MarkdownString('test') }); + return { metadata: { metadataKey: 'value' } }; + }, + async provideFollowups(sessionId, token) { + return []; + }, +}; + function getAgentData(id: string) { return { name: id, @@ -116,6 +138,7 @@ suite('ChatService', () => { }; testDisposables.add(chatAgentService.registerAgent('testAgent', { ...getAgentData('testAgent'), isDefault: true })); testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContextId, getAgentData(chatAgentWithUsedContextId))); + testDisposables.add(chatAgentService.registerAgent(chatAgentWithMarkdownId, getAgentData(chatAgentWithMarkdownId))); testDisposables.add(chatAgentService.registerAgentImplementation('testAgent', agent)); chatAgentService.updateAgent('testAgent', { requester: { name: 'test' } }); }); @@ -253,4 +276,32 @@ suite('ChatService', () => { await assertSnapshot(chatModel2.toExport()); }); + + test('can deserialize with response', async () => { + let serializedChatData: ISerializableChatData; + testDisposables.add(chatAgentService.registerAgentImplementation(chatAgentWithMarkdownId, chatAgentWithMarkdown)); + + { + const testService = testDisposables.add(instantiationService.createInstance(ChatService)); + + const chatModel1 = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + assert.strictEqual(chatModel1.getRequests().length, 0); + + const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`); + assert(response); + + await response.responseCompletePromise; + + serializedChatData = JSON.parse(JSON.stringify(chatModel1)); + } + + // try deserializing the state into a new service + + const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); + + const chatModel2 = testService2.loadSessionFromContent(serializedChatData); + assert(chatModel2); + + await assertSnapshot(chatModel2.toExport()); + }); }); From bf0917f9f2d9d1294ec0bf21991bfea2898eea46 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 29 Oct 2024 11:25:31 -0700 Subject: [PATCH 060/555] fix: don't assert chat view model (#232513) --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 0e1ac7b4a4259..6c3f268a0f451 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -472,7 +472,10 @@ export class ChatWidget extends Disposable implements IChatWidget { } refreshParsedInput() { - this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel!.sessionId, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent }); + if (!this.viewModel) { + return; + } + this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel.sessionId, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent }); this._onDidChangeParsedInput.fire(); } From 22b0035d32305c7854f5a820a4eea84de47ef007 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:51:22 -0700 Subject: [PATCH 061/555] chore: add guard:cf flags to CLI (#232452) --- build/azure-pipelines/win32/cli-build-win32.yml | 6 ++++-- cli/.cargo/config.toml | 6 ++++++ cli/CONTRIBUTING.md | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 cli/.cargo/config.toml diff --git a/build/azure-pipelines/win32/cli-build-win32.yml b/build/azure-pipelines/win32/cli-build-win32.yml index 19409272ff07b..d61f0e722f5ff 100644 --- a/build/azure-pipelines/win32/cli-build-win32.yml +++ b/build/azure-pipelines/win32/cli-build-win32.yml @@ -53,7 +53,8 @@ steps: VSCODE_CLI_ENV: OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static/lib OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static/include - RUSTFLAGS: "-C target-feature=+crt-static" + RUSTFLAGS: "-Ctarget-feature=+crt-static -Clink-args=/guard:cf -Clink-args=/CETCOMPAT" + CFLAGS: "/guard:cf /Qspectre" - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - template: ../cli/cli-compile.yml@self @@ -65,7 +66,8 @@ steps: VSCODE_CLI_ENV: OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static/lib OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static/include - RUSTFLAGS: "-C target-feature=+crt-static" + RUSTFLAGS: "-C target-feature=+crt-static -Clink-args=/guard:cf -Clink-args=/CETCOMPAT:NO" + CFLAGS: "/guard:cf /Qspectre" - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: diff --git a/cli/.cargo/config.toml b/cli/.cargo/config.toml new file mode 100644 index 0000000000000..ad9374d4a9087 --- /dev/null +++ b/cli/.cargo/config.toml @@ -0,0 +1,6 @@ +[target.'cfg(all(target_os = "windows", any(target_arch = "i686", target_arch = "x86_64", target_arch = "x86")))'] +rustflags = ["-Ctarget-feature=+crt-static", "-Clink-args=/guard:cf", "-Clink-args=/CETCOMPAT"] + +# CETCOMPAT is not supported on ARM binaries +[target.'cfg(all(target_os = "windows", not(any(target_arch = "i686", target_arch = "x86_64", target_arch = "x86"))))'] +rustflags = ["-Ctarget-feature=+crt-static", "-Clink-args=/guard:cf"] diff --git a/cli/CONTRIBUTING.md b/cli/CONTRIBUTING.md index d119f1ac98a87..4809fccd08093 100644 --- a/cli/CONTRIBUTING.md +++ b/cli/CONTRIBUTING.md @@ -8,7 +8,7 @@ For the moment, we require OpenSSL on Windows, where it is not usually installed by default. To install it: -1. Install (clone) vcpkg [using their instructions](https://github.com/Microsoft/vcpkg#quick-start-windows) +1. Follow steps 1 and 2 of [Set up vcpkg](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started-msbuild?pivots=shell-powershell#1---set-up-vcpkg) to obtain the executable. 1. Add the location of the `vcpkg` directory to your system or user PATH. 1. Run`vcpkg install openssl:x64-windows-static-md` (after restarting your terminal for PATH changes to apply) 1. You should be able to then `cargo build` successfully From f992298908e72d13cba220afe00e705f9cafcc77 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 29 Oct 2024 11:52:29 -0700 Subject: [PATCH 062/555] debug: fix dynamic configurations in empty workspace (#232520) We would skip trying to find configurations instead of passing an undefined workspace folder (which is entirely legal) Fixes #228949 --- .../contrib/debug/browser/debugConfigurationManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 84fd17595e032..26d6f90be38bc 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -226,8 +226,8 @@ export class ConfigurationManager implements IConfigurationManager { const picks: Promise[] = []; const provider = this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations); this.getLaunches().forEach(launch => { - if (launch.workspace && provider) { - picks.push(provider.provideDebugConfigurations!(launch.workspace.uri, token.token).then(configurations => configurations.map(config => ({ + if (provider) { + picks.push(provider.provideDebugConfigurations!(launch.workspace?.uri, token.token).then(configurations => configurations.map(config => ({ label: config.name, description: launch.name, config, From 50a2adc65b8bf6ba353c6927c2f1f19ce6701c4d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 29 Oct 2024 11:55:16 -0700 Subject: [PATCH 063/555] Cleanup chat context keys (#232515) * Start renaming context keys * vote * detected agent * More * More * chatEditApplied * More * More * More * Cleanup * Cleanup --- .../browser/editorAccessibilityHelp.ts | 4 +- .../browser/actions/chatAccessibilityHelp.ts | 22 +++--- .../chat/browser/actions/chatActions.ts | 36 ++++----- .../chat/browser/actions/chatClearActions.ts | 26 +++---- .../browser/actions/chatCodeblockActions.ts | 38 +++++----- .../browser/actions/chatContextActions.ts | 24 +++--- .../chat/browser/actions/chatCopyActions.ts | 6 +- .../browser/actions/chatExecuteActions.ts | 40 +++++----- .../browser/actions/chatFileTreeActions.ts | 10 +-- .../chat/browser/actions/chatImportExport.ts | 6 +- .../chat/browser/actions/chatMoveActions.ts | 8 +- .../browser/actions/chatQuickInputActions.ts | 4 +- .../chat/browser/actions/chatTitleActions.ts | 32 ++++---- .../browser/chatEditing/chatEditingActions.ts | 24 +++--- .../browser/chatEditing/chatEditingService.ts | 6 +- .../contrib/chat/browser/chatEditorSaving.ts | 8 +- .../contrib/chat/browser/chatInputPart.ts | 12 +-- .../contrib/chat/browser/chatListRenderer.ts | 20 ++--- .../browser/chatMovedView.contribution.ts | 10 +-- .../browser/chatParticipantContributions.ts | 10 +-- .../browser/chatResponseAccessibleView.ts | 4 +- .../contrib/chat/browser/chatWidget.ts | 16 ++-- .../contrib/chat/browser/codeBlockPart.ts | 4 +- .../contrib/chat/common/chatAgents.ts | 8 +- .../contrib/chat/common/chatContextKeys.ts | 74 ++++++++++--------- .../chatInstallEntitlement.contribution.ts | 4 +- .../contrib/chat/common/languageModels.ts | 4 +- .../actions/voiceChatActions.ts | 48 ++++++------ .../contrib/debug/browser/debugCommands.ts | 4 +- .../debug/browser/debugEditorActions.ts | 8 +- .../browser/inlineChat.contribution.ts | 6 +- .../browser/inlineChatAccessibilityHelp.ts | 4 +- .../inlineChat/browser/inlineChatActions.ts | 12 +-- .../browser/inlineChatController.ts | 6 +- .../inlineChat/browser/inlineChatWidget.ts | 20 ++--- .../controller/chat/cellChatActions.ts | 4 +- 36 files changed, 287 insertions(+), 285 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index 4363a2f6f1f08..9f0b4190a226b 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -13,7 +13,7 @@ import { IContextKeyService } from '../../../../platform/contextkey/common/conte import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { AccessibilityHelpAction } from './accessibleViewActions.js'; -import { CONTEXT_CHAT_ENABLED } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { CommentAccessibilityHelpNLS } from '../../comments/browser/commentsAccessibility.js'; import { CommentContextKeys } from '../../comments/common/commentContextKeys.js'; import { NEW_UNTITLED_FILE_COMMAND_ID } from '../../files/browser/fileConstants.js'; @@ -115,7 +115,7 @@ export function getCommentCommandInfo(keybindingService: IKeybindingService, con } export function getChatCommandInfo(keybindingService: IKeybindingService, contextKeyService: IContextKeyService): string | undefined { - if (CONTEXT_CHAT_ENABLED.getValue(contextKeyService)) { + if (ChatContextKeys.enabled.getValue(contextKeyService)) { return [AccessibilityHelpNLS.quickChat, AccessibilityHelpNLS.startInlineChat].join('\n'); } return; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 974bd181d4cf1..d15798eb0be2f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -3,26 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../../../../nls.js'; import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; -import { IChatWidgetService } from '../chat.js'; -import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from '../../../../../platform/accessibility/browser/accessibleView.js'; -import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; -import { AccessibleDiffViewerNext } from '../../../../../editor/browser/widget/diffEditor/commands.js'; -import { INLINE_CHAT_ID } from '../../../inlineChat/common/inlineChat.js'; import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; -import { CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE, CONTEXT_REQUEST, CONTEXT_CHAT_LOCATION, CONTEXT_IN_QUICK_CHAT } from '../../common/chatContextKeys.js'; +import { AccessibleDiffViewerNext } from '../../../../../editor/browser/widget/diffEditor/commands.js'; +import { localize } from '../../../../../nls.js'; +import { AccessibleContentProvider, AccessibleViewProviderId, AccessibleViewType } from '../../../../../platform/accessibility/browser/accessibleView.js'; import { IAccessibleViewImplentation } from '../../../../../platform/accessibility/browser/accessibleViewRegistry.js'; -import { ChatAgentLocation } from '../../common/chatAgents.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; +import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; +import { INLINE_CHAT_ID } from '../../../inlineChat/common/inlineChat.js'; +import { ChatAgentLocation } from '../../common/chatAgents.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { IChatWidgetService } from '../chat.js'; export class PanelChatAccessibilityHelp implements IAccessibleViewImplentation { readonly priority = 107; readonly name = 'panelChat'; readonly type = AccessibleViewType.Help; - readonly when = ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), CONTEXT_IN_QUICK_CHAT.negate(), ContextKeyExpr.or(CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE, CONTEXT_REQUEST)); + readonly when = ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.inQuickChat.negate(), ContextKeyExpr.or(ChatContextKeys.inChatSession, ChatContextKeys.isResponse, ChatContextKeys.isRequest)); getProvider(accessor: ServicesAccessor) { const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor(); return getChatAccessibilityHelpProvider(accessor, codeEditor ?? undefined, 'panelChat'); @@ -33,7 +33,7 @@ export class QuickChatAccessibilityHelp implements IAccessibleViewImplentation { readonly priority = 107; readonly name = 'quickChat'; readonly type = AccessibleViewType.Help; - readonly when = ContextKeyExpr.and(CONTEXT_IN_QUICK_CHAT, ContextKeyExpr.or(CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE, CONTEXT_REQUEST)); + readonly when = ContextKeyExpr.and(ChatContextKeys.inQuickChat, ContextKeyExpr.or(ChatContextKeys.inChatSession, ChatContextKeys.isResponse, ChatContextKeys.isRequest)); getProvider(accessor: ServicesAccessor) { const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor(); return getChatAccessibilityHelpProvider(accessor, codeEditor ?? undefined, 'quickChat'); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 3b308d9607878..313cab40904f4 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -33,7 +33,7 @@ import { ACTIVE_GROUP, IEditorService } from '../../../../services/editor/common import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INSTALL_ENTITLED, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_QUICK_CHAT } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatDetail, IChatService } from '../../common/chatService.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; @@ -96,7 +96,7 @@ class OpenChatGlobalAction extends Action2 { title: OpenChatGlobalAction.TITLE, icon: defaultChat.icon, f1: true, - precondition: CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, + precondition: ChatContextKeys.panelParticipantRegistered, category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -160,7 +160,7 @@ class ChatHistoryAction extends Action2 { category: CHAT_CATEGORY, icon: Codicon.history, f1: true, - precondition: CONTEXT_CHAT_ENABLED + precondition: ChatContextKeys.enabled }); } @@ -265,7 +265,7 @@ class OpenChatEditorAction extends Action2 { title: localize2('interactiveSession.open', "Open Editor"), f1: true, category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_ENABLED + precondition: ChatContextKeys.enabled }); } @@ -286,7 +286,7 @@ class ChatAddAction extends Action2 { category: CHAT_CATEGORY, menu: { id: MenuId.ChatInput, - when: CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), + when: ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), group: 'navigation', order: 1 } @@ -331,7 +331,7 @@ export function registerChatActions() { super({ id: 'workbench.action.chat.clearInputHistory', title: localize2('interactiveSession.clearHistory.label', "Clear Input History"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, f1: true, }); @@ -347,7 +347,7 @@ export function registerChatActions() { super({ id: 'workbench.action.chat.clearHistory', title: localize2('chat.clear.label', "Clear All Workspace Chats"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, f1: true, }); @@ -381,23 +381,23 @@ export function registerChatActions() { super({ id: 'chat.action.focus', title: localize2('actions.interactiveSession.focus', 'Focus Chat List'), - precondition: ContextKeyExpr.and(CONTEXT_IN_CHAT_INPUT), + precondition: ContextKeyExpr.and(ChatContextKeys.inChatInput), category: CHAT_CATEGORY, keybinding: [ // On mac, require that the cursor is at the top of the input, to avoid stealing cmd+up to move the cursor to the top { - when: ContextKeyExpr.and(CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_IN_QUICK_CHAT.negate()), + when: ContextKeyExpr.and(ChatContextKeys.inputCursorAtTop, ChatContextKeys.inQuickChat.negate()), primary: KeyMod.CtrlCmd | KeyCode.UpArrow, weight: KeybindingWeight.EditorContrib, }, // On win/linux, ctrl+up can always focus the chat list { - when: ContextKeyExpr.and(ContextKeyExpr.or(IsWindowsContext, IsLinuxContext), CONTEXT_IN_QUICK_CHAT.negate()), + when: ContextKeyExpr.and(ContextKeyExpr.or(IsWindowsContext, IsLinuxContext), ChatContextKeys.inQuickChat.negate()), primary: KeyMod.CtrlCmd | KeyCode.UpArrow, weight: KeybindingWeight.EditorContrib, }, { - when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_QUICK_CHAT), + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inQuickChat), primary: KeyMod.CtrlCmd | KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib, } @@ -424,10 +424,10 @@ export function registerChatActions() { { primary: KeyMod.CtrlCmd | KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate(), CONTEXT_IN_QUICK_CHAT.negate()), + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate(), ChatContextKeys.inQuickChat.negate()), }, { - when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate(), CONTEXT_IN_QUICK_CHAT), + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate(), ChatContextKeys.inQuickChat), primary: KeyMod.CtrlCmd | KeyCode.UpArrow, weight: KeybindingWeight.WorkbenchContrib, } @@ -462,7 +462,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandCenter, { icon: defaultChat.icon, when: ContextKeyExpr.and( ContextKeyExpr.has('config.chat.commandCenter.enabled'), - ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_INSTALL_ENTITLED) + ContextKeyExpr.or(ChatContextKeys.panelParticipantRegistered, ChatContextKeys.installEntitled) ), order: 10001, }); @@ -475,7 +475,7 @@ registerAction2(class ToggleChatControl extends ToggleTitleBarConfigAction { localize('toggle.chatControlsDescription', "Toggle visibility of the Chat Controls in title bar"), 3, false, ContextKeyExpr.and( ContextKeyExpr.has('config.window.commandCenter'), - ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_INSTALL_ENTITLED) + ContextKeyExpr.or(ChatContextKeys.panelParticipantRegistered, ChatContextKeys.installEntitled) ) ); } @@ -606,7 +606,7 @@ class InstallChatWithoutPromptAction extends BaseInstallChatAction { id: MenuId.ChatCommandCenter, group: 'a_atfirst', order: 1, - when: CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED.negate() + when: ChatContextKeys.panelParticipantRegistered.negate() } }); } @@ -630,12 +630,12 @@ class LearnMoreChatAction extends Action2 { id: MenuId.ChatCommandCenter, group: 'a_atfirst', order: 2, - when: CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED.negate() + when: ChatContextKeys.panelParticipantRegistered.negate() }, { id: MenuId.ChatCommandCenter, group: 'z_atlast', order: 1, - when: CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED + when: ChatContextKeys.panelParticipantRegistered }] }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 3682d4075cd34..8e7296df23ae4 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -16,7 +16,7 @@ import { ActiveEditorContext } from '../../../../common/contextkeys.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { isChatViewTitleActionContext } from '../../common/chatActions.js'; import { ChatAgentLocation } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_EDITING_CAN_REDO, CONTEXT_CHAT_EDITING_CAN_UNDO, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_SESSION } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { CHAT_VIEW_ID, EDITS_VIEW_ID, IChatWidgetService } from '../chat.js'; import { ChatEditorInput } from '../chatEditorInput.js'; @@ -36,7 +36,7 @@ export function registerNewChatActions() { title: localize2('chat.newChat.label', "New Chat"), icon: Codicon.plus, f1: false, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, menu: [{ id: MenuId.EditorTitle, group: 'navigation', @@ -58,7 +58,7 @@ export function registerNewChatActions() { title: localize2('chat.newChat.label', "New Chat"), category: CHAT_CATEGORY, icon: Codicon.plus, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession)), + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession)), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -66,7 +66,7 @@ export function registerNewChatActions() { mac: { primary: KeyMod.WinCtrl | KeyCode.KeyL }, - when: CONTEXT_IN_CHAT_SESSION + when: ChatContextKeys.inChatSession }, menu: [{ id: MenuId.ChatContext, @@ -108,7 +108,7 @@ export function registerNewChatActions() { title: localize2('chat.newEdits.label', "New Edit Session"), category: CHAT_CATEGORY, icon: Codicon.plus, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), f1: true, menu: [{ id: MenuId.ChatContext, @@ -206,11 +206,11 @@ export function registerNewChatActions() { id: 'workbench.action.chat.done', title: localize2('chat.done.label', "Done"), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), f1: false, menu: [{ id: MenuId.ChatEditingWidgetToolbar, - when: ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey.negate(), hasAppliedChatEditsContextKey, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession)), + when: ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey.negate(), hasAppliedChatEditsContextKey, ChatContextKeys.editingParticipantRegistered, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), group: 'navigation', order: 0 }] @@ -252,7 +252,7 @@ export function registerNewChatActions() { title: localize2('chat.undoEdit.label', "Undo Last Edit"), category: CHAT_CATEGORY, icon: Codicon.discard, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_EDITING_CAN_UNDO, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanUndo, ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), f1: true, menu: [{ id: MenuId.ViewTitle, @@ -284,7 +284,7 @@ export function registerNewChatActions() { title: localize2('chat.redoEdit.label', "Redo Last Edit"), category: CHAT_CATEGORY, icon: Codicon.redo, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_EDITING_CAN_REDO, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanRedo, ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), f1: true, menu: [{ id: MenuId.ViewTitle, @@ -316,16 +316,16 @@ export function registerNewChatActions() { title: localize2('chat.openEdits.label', "Open {0}", 'Copilot Edits'), category: CHAT_CATEGORY, icon: Codicon.goToEditingSession, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), f1: true, menu: [{ id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', CHAT_VIEW_ID), CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', CHAT_VIEW_ID), ChatContextKeys.editingParticipantRegistered), group: 'navigation', order: 1 }, { id: MenuId.ChatCommandCenter, - when: CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, + when: ChatContextKeys.editingParticipantRegistered, group: 'a_chatEdit', order: 1 }], @@ -335,7 +335,7 @@ export function registerNewChatActions() { linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI }, - when: ContextKeyExpr.and(ContextKeyExpr.notEquals('view', EDITS_VIEW_ID), CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED) + when: ContextKeyExpr.and(ContextKeyExpr.notEquals('view', EDITS_VIEW_ID), ChatContextKeys.editingParticipantRegistered) } }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 3425dad368f04..9f7b22785be4f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -21,7 +21,7 @@ import { IUntitledTextResourceEditorInput } from '../../../../common/editor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { accessibleViewInCodeBlock } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from '../../../terminal/browser/terminal.js'; -import { CONTEXT_CHAT_EDIT_APPLIED, CONTEXT_CHAT_ENABLED, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; import { ChatCopyKind, IChatService } from '../../common/chatService.js'; import { IChatResponseViewModel, isResponseVM } from '../../common/chatViewModel.js'; @@ -186,7 +186,7 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.applyInEditor', title: localize2('interactive.applyInEditor.label', "Apply in Editor"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, icon: Codicon.gitPullRequestGoToChanges, @@ -195,13 +195,13 @@ export function registerChatCodeBlockActions() { id: MenuId.ChatCodeBlock, group: 'navigation', when: ContextKeyExpr.and( - CONTEXT_IN_CHAT_SESSION, + ChatContextKeys.inChatSession, ...shellLangIds.map(e => ContextKeyExpr.notEquals(EditorContextKeys.languageId.key, e)) ), order: 10 }, keybinding: { - when: ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), accessibleViewInCodeBlock), + when: ContextKeyExpr.or(ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), accessibleViewInCodeBlock), primary: KeyMod.CtrlCmd | KeyCode.Enter, mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, weight: KeybindingWeight.ExternalExtension + 1 @@ -222,7 +222,7 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.applyAll', title: localize2('chat.applyAll.label', "Apply All Edits"), - precondition: CONTEXT_CHAT_ENABLED, // improve this condition + precondition: ChatContextKeys.enabled, // improve this condition f1: true, category: CHAT_CATEGORY, icon: Codicon.edit @@ -254,18 +254,18 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.insertCodeBlock', title: localize2('interactive.insertCodeBlock.label', "Insert At Cursor"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, icon: Codicon.insert, menu: { id: MenuId.ChatCodeBlock, group: 'navigation', - when: CONTEXT_IN_CHAT_SESSION, + when: ChatContextKeys.inChatSession, order: 20 }, keybinding: { - when: ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), accessibleViewInCodeBlock), + when: ContextKeyExpr.or(ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), accessibleViewInCodeBlock), primary: KeyMod.CtrlCmd | KeyCode.Enter, mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, weight: KeybindingWeight.ExternalExtension + 1 @@ -284,7 +284,7 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.insertIntoNewFile', title: localize2('interactive.insertIntoNewFile.label', "Insert into New File"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, icon: Codicon.newFile, @@ -331,7 +331,7 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.runInTerminal', title: localize2('interactive.runInTerminal.label', "Insert into Terminal"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, icon: Codicon.terminal, @@ -339,7 +339,7 @@ export function registerChatCodeBlockActions() { id: MenuId.ChatCodeBlock, group: 'navigation', when: ContextKeyExpr.and( - CONTEXT_IN_CHAT_SESSION, + ChatContextKeys.inChatSession, ContextKeyExpr.or(...shellLangIds.map(e => ContextKeyExpr.equals(EditorContextKeys.languageId.key, e))) ), }, @@ -348,7 +348,7 @@ export function registerChatCodeBlockActions() { group: 'navigation', isHiddenByDefault: true, when: ContextKeyExpr.and( - CONTEXT_IN_CHAT_SESSION, + ChatContextKeys.inChatSession, ...shellLangIds.map(e => ContextKeyExpr.notEquals(EditorContextKeys.languageId.key, e)) ) }], @@ -358,7 +358,7 @@ export function registerChatCodeBlockActions() { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter }, weight: KeybindingWeight.EditorContrib, - when: ContextKeyExpr.or(CONTEXT_IN_CHAT_SESSION, accessibleViewInCodeBlock), + when: ContextKeyExpr.or(ChatContextKeys.inChatSession, accessibleViewInCodeBlock), }] }); } @@ -448,9 +448,9 @@ export function registerChatCodeBlockActions() { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, }, weight: KeybindingWeight.WorkbenchContrib, - when: CONTEXT_IN_CHAT_SESSION, + when: ChatContextKeys.inChatSession, }, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, }); @@ -470,9 +470,9 @@ export function registerChatCodeBlockActions() { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, }, weight: KeybindingWeight.WorkbenchContrib, - when: CONTEXT_IN_CHAT_SESSION, + when: ChatContextKeys.inChatSession, }, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, }); @@ -537,7 +537,7 @@ export function registerChatCodeCompareBlockActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.check, - precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, CONTEXT_CHAT_EDIT_APPLIED.negate()), + precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, ChatContextKeys.editApplied.negate()), menu: { id: MenuId.ChatCompareBlock, group: 'navigation', @@ -569,7 +569,7 @@ export function registerChatCodeCompareBlockActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.trash, - precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, CONTEXT_CHAT_EDIT_APPLIED.negate()), + precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, ChatContextKeys.editApplied.negate()), menu: { id: MenuId.ChatCompareBlock, group: 'navigation', diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 40f1a198567d6..2ea46a8435236 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -39,7 +39,7 @@ import { SearchView } from '../../../search/browser/searchView.js'; import { ISymbolQuickPickItem, SymbolsQuickAccessProvider } from '../../../search/browser/symbolsQuickAccess.js'; import { SearchContext } from '../../../search/common/constants.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_INPUT } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; import { IChatRequestVariableEntry } from '../../common/chatModel.js'; import { ChatRequestAgentPart } from '../../common/chatParserTypes.js'; @@ -169,7 +169,7 @@ class AttachFileAction extends Action2 { title: localize2('workbench.action.chat.attachFile.label', "Add File to Chat"), category: CHAT_CATEGORY, f1: false, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor')), + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor')), menu: { id: MenuId.ChatCommandCenter, group: 'a_chat', @@ -200,7 +200,7 @@ class AttachSelectionAction extends Action2 { title: localize2('workbench.action.chat.attachSelection.label', "Add Selection to Chat"), category: CHAT_CATEGORY, f1: false, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor')), + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor')), menu: { id: MenuId.ChatCommandCenter, group: 'a_chat', @@ -231,10 +231,10 @@ export class AttachContextAction extends Action2 { // used to enable/disable the keybinding and defined menu containment protected static _cdt = ContextKeyExpr.or( - ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel)), - ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Editor)), - ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Notebook)), - ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Terminal)), + ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel)), + ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Editor)), + ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Notebook)), + ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Terminal)), ); constructor(desc: Readonly = { @@ -242,21 +242,21 @@ export class AttachContextAction extends Action2 { title: localize2('workbench.action.chat.attachContext.label', "Attach Context"), icon: Codicon.attach, category: CHAT_CATEGORY, - precondition: ContextKeyExpr.or(AttachContextAction._cdt, ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession))), + precondition: ContextKeyExpr.or(AttachContextAction._cdt, ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession))), keybinding: { - when: CONTEXT_IN_CHAT_INPUT, + when: ChatContextKeys.inChatInput, primary: KeyMod.CtrlCmd | KeyCode.Slash, weight: KeybindingWeight.EditorContrib }, menu: [ { - when: ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession)), ContextKeyExpr.and(ContextKeyExpr.or(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession)), AttachContextAction._cdt)), + when: ContextKeyExpr.or(ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), ContextKeyExpr.and(ContextKeyExpr.or(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), AttachContextAction._cdt)), id: MenuId.ChatInput, group: 'navigation', order: 2 }, { - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), AttachContextAction._cdt), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), AttachContextAction._cdt), id: MenuId.ChatExecute, group: 'navigation', order: 1 @@ -660,7 +660,7 @@ registerAction2(class AttachFilesAction extends AttachContextAction { title: localize2('workbench.action.chat.editing.attachFiles.label', "Add Files to Working Set"), f1: false, category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession) + precondition: ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession) }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts index 989d2d72a41c0..1c09419c4c615 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts @@ -9,7 +9,7 @@ import { Action2, MenuId, registerAction2 } from '../../../../../platform/action import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js'; import { CHAT_CATEGORY, stringifyItem } from './chatActions.js'; import { IChatWidgetService } from '../chat.js'; -import { CONTEXT_RESPONSE_FILTERED } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; export function registerChatCopyActions() { @@ -22,7 +22,7 @@ export function registerChatCopyActions() { category: CHAT_CATEGORY, menu: { id: MenuId.ChatContext, - when: CONTEXT_RESPONSE_FILTERED.toNegated(), + when: ChatContextKeys.responseIsFiltered.toNegated(), group: 'copy', } }); @@ -54,7 +54,7 @@ export function registerChatCopyActions() { category: CHAT_CATEGORY, menu: { id: MenuId.ChatContext, - when: CONTEXT_RESPONSE_FILTERED.toNegated(), + when: ChatContextKeys.responseIsFiltered.toNegated(), group: 'copy', } }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index ec8aff95a584f..5ba1fd4a26fa6 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -14,7 +14,7 @@ import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.j import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_INPUT_HAS_AGENT, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_LANGUAGE_MODELS_ARE_USER_SELECTABLE } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { applyingChatEditsContextKey, IChatEditingService } from '../../common/chatEditingService.js'; import { chatAgentLeader, extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatService } from '../../common/chatService.js'; @@ -42,9 +42,9 @@ export class SubmitAction extends Action2 { f1: false, category: CHAT_CATEGORY, icon: Codicon.send, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), ContextKeyExpr.or(CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession), ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()))), + precondition: ContextKeyExpr.and(ChatContextKeys.inputHasText, ChatContextKeys.requestInProgress.negate(), ContextKeyExpr.or(ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession), ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()))), keybinding: { - when: CONTEXT_IN_CHAT_INPUT, + when: ChatContextKeys.inChatInput, primary: KeyCode.Enter, weight: KeybindingWeight.EditorContrib }, @@ -57,7 +57,7 @@ export class SubmitAction extends Action2 { { id: MenuId.ChatExecute, order: 4, - when: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), ContextKeyExpr.or(CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession), ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()))), + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ContextKeyExpr.or(ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession), ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()))), group: 'navigation', }, ] @@ -83,11 +83,11 @@ class SubmitWithoutDispatchingAction extends Action2 { f1: false, category: CHAT_CATEGORY, precondition: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT, - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), - ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel))), + ChatContextKeys.inputHasText, + ChatContextKeys.requestInProgress.negate(), + ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel))), keybinding: { - when: CONTEXT_IN_CHAT_INPUT, + when: ChatContextKeys.inChatInput, primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Enter, weight: KeybindingWeight.EditorContrib }, @@ -119,11 +119,11 @@ MenuRegistry.appendMenuItem(MenuId.ChatExecute, { order: 3, group: 'navigation', when: ContextKeyExpr.and( - CONTEXT_LANGUAGE_MODELS_ARE_USER_SELECTABLE, + ChatContextKeys.languageModelsAreUserSelectable, ContextKeyExpr.or( - ContextKeyExpr.equals(CONTEXT_CHAT_LOCATION.key, ChatAgentLocation.Panel), - ContextKeyExpr.equals(CONTEXT_CHAT_LOCATION.key, ChatAgentLocation.EditingSession), - ContextKeyExpr.equals(CONTEXT_CHAT_LOCATION.key, ChatAgentLocation.Editor) + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Panel), + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.EditingSession), + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Editor) ) ), }); @@ -135,14 +135,14 @@ export class ChatSubmitSecondaryAgentAction extends Action2 { super({ id: ChatSubmitSecondaryAgentAction.ID, title: localize2({ key: 'actions.chat.submitSecondaryAgent', comment: ['Send input from the chat input box to the secondary agent'] }, "Submit to Secondary Agent"), - precondition: ContextKeyExpr.and(CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_INPUT_HAS_AGENT.negate(), CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), + precondition: ContextKeyExpr.and(ChatContextKeys.inputHasText, ChatContextKeys.inputHasAgent.negate(), ChatContextKeys.requestInProgress.negate()), menu: { id: MenuId.ChatExecuteSecondary, group: 'group_1', order: 3 }, keybinding: { - when: CONTEXT_IN_CHAT_INPUT, + when: ChatContextKeys.inChatInput, primary: KeyMod.CtrlCmd | KeyCode.Enter, weight: KeybindingWeight.EditorContrib }, @@ -177,19 +177,19 @@ class SendToChatEditingAction extends Action2 { super({ id: 'workbench.action.chat.sendToChatEditing', title: localize2('chat.sendToChatEditing.label', "Send to Copilot Edits"), - precondition: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), CONTEXT_CHAT_INPUT_HAS_AGENT.negate(), CONTEXT_CHAT_INPUT_HAS_TEXT), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ChatContextKeys.inputHasAgent.negate(), ChatContextKeys.inputHasText), category: CHAT_CATEGORY, f1: false, menu: { id: MenuId.ChatExecuteSecondary, group: 'group_1', order: 4, - when: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession)) + when: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered, ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession)) }, keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter, - when: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession)) + when: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered, ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession)) } }); } @@ -252,7 +252,7 @@ class SendToNewChatAction extends Action2 { super({ id: 'workbench.action.chat.sendToNewChat', title: localize2('chat.newChat.label', "Send to New Chat"), - precondition: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), CONTEXT_CHAT_INPUT_HAS_TEXT), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ChatContextKeys.inputHasText), category: CHAT_CATEGORY, f1: false, menu: { @@ -262,7 +262,7 @@ class SendToNewChatAction extends Action2 { keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter, - when: CONTEXT_IN_CHAT_INPUT, + when: ChatContextKeys.inChatInput, } }); } @@ -292,7 +292,7 @@ export class CancelAction extends Action2 { icon: Codicon.stopCircle, menu: { id: MenuId.ChatExecute, - when: ContextKeyExpr.or(CONTEXT_CHAT_REQUEST_IN_PROGRESS, ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey)), + when: ContextKeyExpr.or(ChatContextKeys.requestInProgress, ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey)), order: 4, group: 'navigation', }, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts index 6a04473055fce..41ddea9c443d7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts @@ -10,7 +10,7 @@ import { Action2, registerAction2 } from '../../../../../platform/actions/common import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { CHAT_CATEGORY } from './chatActions.js'; import { IChatWidgetService } from '../chat.js'; -import { CONTEXT_IN_CHAT_SESSION, CONTEXT_CHAT_ENABLED } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatResponseViewModel, isResponseVM } from '../../common/chatViewModel.js'; export function registerChatFileTreeActions() { @@ -22,9 +22,9 @@ export function registerChatFileTreeActions() { keybinding: { primary: KeyMod.CtrlCmd | KeyCode.F9, weight: KeybindingWeight.WorkbenchContrib, - when: CONTEXT_IN_CHAT_SESSION, + when: ChatContextKeys.inChatSession, }, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, }); @@ -43,9 +43,9 @@ export function registerChatFileTreeActions() { keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F9, weight: KeybindingWeight.WorkbenchContrib, - when: CONTEXT_IN_CHAT_SESSION, + when: ChatContextKeys.inChatSession, }, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts index 709d2605bfede..9cb268db6b868 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts @@ -14,7 +14,7 @@ import { CHAT_CATEGORY } from './chatActions.js'; import { IChatWidgetService } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; -import { CONTEXT_CHAT_ENABLED } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { isExportableSessionData } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; @@ -29,7 +29,7 @@ export function registerChatExportActions() { id: 'workbench.action.chat.export', category: CHAT_CATEGORY, title: localize2('chat.export.label', "Export Chat..."), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, }); } @@ -70,7 +70,7 @@ export function registerChatExportActions() { id: 'workbench.action.chat.import', title: localize2('chat.import.label', "Import Chat..."), category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 8f68515d6fca3..e2a295bb6e38e 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -13,7 +13,7 @@ import { CHAT_VIEW_ID, IChatWidgetService } from '../chat.js'; import { ChatEditor, IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { ChatViewPane } from '../chatViewPane.js'; -import { CONTEXT_CHAT_ENABLED } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { ACTIVE_GROUP, AUX_WINDOW_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; @@ -31,7 +31,7 @@ export function registerMoveActions() { id: `workbench.action.chat.openInEditor`, title: localize2('chat.openInEditor.label', "Open Chat in Editor"), category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, menu: { id: MenuId.ViewTitle, @@ -53,7 +53,7 @@ export function registerMoveActions() { id: `workbench.action.chat.openInNewWindow`, title: localize2('chat.openInNewWindow.label', "Open Chat in New Window"), category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, menu: { id: MenuId.ViewTitle, @@ -75,7 +75,7 @@ export function registerMoveActions() { id: `workbench.action.chat.openInSidebar`, title: localize2('interactiveSession.openInSidebar.label', "Open Chat in Side Bar"), category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, menu: [{ id: MenuId.EditorTitle, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 43b7bf8b6fae7..05c9bf646c49d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -13,7 +13,7 @@ import { ServicesAccessor } from '../../../../../platform/instantiation/common/i import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { CHAT_CATEGORY } from './chatActions.js'; import { IQuickChatOpenOptions, IQuickChatService } from '../chat.js'; -import { CONTEXT_CHAT_ENABLED } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { InlineChatController } from '../../../inlineChat/browser/inlineChatController.js'; export const ASK_QUICK_QUESTION_ACTION_ID = 'workbench.action.quickchat.toggle'; @@ -103,7 +103,7 @@ class QuickChatGlobalAction extends Action2 { super({ id: ASK_QUICK_QUESTION_ACTION_ID, title: localize2('quickChat', 'Quick Chat'), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, icon: Codicon.commentDiscussion, f1: false, category: CHAT_CATEGORY, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index f0775d757d784..c2ba129720c09 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -27,7 +27,7 @@ import { INotebookEditor } from '../../../notebook/browser/notebookBrowser.js'; import { CellEditType, CellKind, NOTEBOOK_EDITOR_ID } from '../../../notebook/common/notebookCommon.js'; import { NOTEBOOK_IS_ACTIVE_EDITOR } from '../../../notebook/common/notebookContextKeys.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_ITEM_ID, CONTEXT_LAST_ITEM_ID, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IParsedChatRequest } from '../../common/chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatProgress, IChatService } from '../../common/chatService.js'; @@ -47,17 +47,17 @@ export function registerChatTitleActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.thumbsup, - toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('up'), + toggled: ChatContextKeys.responseVote.isEqualTo('up'), menu: [{ id: MenuId.ChatMessageFooter, group: 'navigation', order: 1, - when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR.negate()) + when: ContextKeyExpr.and(ChatContextKeys.isResponse, ChatContextKeys.responseHasError.negate()) }, { id: MENU_INLINE_CHAT_WIDGET_SECONDARY, group: 'navigation', order: 1, - when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR.negate()) + when: ContextKeyExpr.and(ChatContextKeys.isResponse, ChatContextKeys.responseHasError.negate()) }] }); } @@ -94,17 +94,17 @@ export function registerChatTitleActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.thumbsdown, - toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('down'), + toggled: ChatContextKeys.responseVote.isEqualTo('down'), menu: [{ id: MenuId.ChatMessageFooter, group: 'navigation', order: 2, - when: ContextKeyExpr.and(CONTEXT_RESPONSE) + when: ContextKeyExpr.and(ChatContextKeys.isResponse) }, { id: MENU_INLINE_CHAT_WIDGET_SECONDARY, group: 'navigation', order: 2, - when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR.negate()) + when: ContextKeyExpr.and(ChatContextKeys.isResponse, ChatContextKeys.responseHasError.negate()) }] }); } @@ -151,12 +151,12 @@ export function registerChatTitleActions() { id: MenuId.ChatMessageFooter, group: 'navigation', order: 3, - when: ContextKeyExpr.and(CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_RESPONSE) + when: ContextKeyExpr.and(ChatContextKeys.responseSupportsIssueReporting, ChatContextKeys.isResponse) }, { id: MENU_INLINE_CHAT_WIDGET_SECONDARY, group: 'navigation', order: 3, - when: ContextKeyExpr.and(CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_RESPONSE) + when: ContextKeyExpr.and(ChatContextKeys.responseSupportsIssueReporting, ChatContextKeys.isResponse) }] }); } @@ -194,8 +194,8 @@ export function registerChatTitleActions() { id: MenuId.ChatMessageFooter, group: 'navigation', when: ContextKeyExpr.and( - CONTEXT_RESPONSE, - ContextKeyExpr.in(CONTEXT_ITEM_ID.key, CONTEXT_LAST_ITEM_ID.key)) + ChatContextKeys.isResponse, + ContextKeyExpr.in(ChatContextKeys.itemId.key, ChatContextKeys.lastItemId.key)) }, { id: MenuId.ChatEditingWidgetToolbar, @@ -277,7 +277,7 @@ export function registerChatTitleActions() { id: MenuId.ChatMessageFooter, group: 'navigation', isHiddenByDefault: true, - when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED.negate()) + when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, ChatContextKeys.isResponse, ChatContextKeys.responseIsFiltered.negate()) } }); } @@ -346,20 +346,20 @@ export function registerChatTitleActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.x, - precondition: CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession), + precondition: ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession), keybinding: { primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace, }, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession), CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), + when: ContextKeyExpr.and(ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), weight: KeybindingWeight.WorkbenchContrib, }, menu: { id: MenuId.ChatMessageTitle, group: 'navigation', order: 2, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession), CONTEXT_REQUEST) + when: ContextKeyExpr.and(ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession), ChatContextKeys.isRequest) } }); } @@ -400,7 +400,7 @@ export function registerChatTitleActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.goToEditingSession, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession)), + precondition: ContextKeyExpr.and(ChatContextKeys.editingParticipantRegistered, ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession)), menu: { id: MenuId.ChatMessageFooter, group: 'navigation', diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index d6101195773c9..f49f8d1200bdf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -21,7 +21,7 @@ import { IListService } from '../../../../../platform/list/browser/listService.j import { GroupsOrder, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { ChatAgentLocation } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_ITEM_ID, CONTEXT_LAST_ITEM_ID, CONTEXT_REQUEST } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingResourceContextKey, chatEditingWidgetFileStateContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IChatService } from '../../common/chatService.js'; import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; @@ -180,10 +180,10 @@ export class ChatEditingAcceptAllAction extends Action2 { title: localize('accept', 'Accept'), icon: Codicon.check, tooltip: localize('acceptAllEdits', 'Accept All Edits'), - precondition: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Enter, - when: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_IN_CHAT_INPUT), + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatInput), weight: KeybindingWeight.WorkbenchContrib, }, menu: [ @@ -197,7 +197,7 @@ export class ChatEditingAcceptAllAction extends Action2 { id: MenuId.ChatEditingWidgetToolbar, group: 'navigation', order: 0, - when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey, ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession))))) + when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey, ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession))))) } ] }); @@ -222,7 +222,7 @@ export class ChatEditingDiscardAllAction extends Action2 { title: localize('discard', 'Discard'), icon: Codicon.discard, tooltip: localize('discardAllEdits', 'Discard All Edits'), - precondition: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), menu: [ { when: ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), @@ -234,11 +234,11 @@ export class ChatEditingDiscardAllAction extends Action2 { id: MenuId.ChatEditingWidgetToolbar, group: 'navigation', order: 1, - when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), hasUndecidedChatEditingResourceContextKey))) + when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), hasUndecidedChatEditingResourceContextKey))) } ], keybinding: { - when: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_IN_CHAT_INPUT, CONTEXT_CHAT_INPUT_HAS_TEXT.negate()), + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatInput, ChatContextKeys.inputHasText.negate()), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Backspace, }, @@ -273,7 +273,7 @@ export class ChatEditingShowChangesAction extends Action2 { id: MenuId.ChatEditingWidgetToolbar, group: 'navigation', order: 4, - when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession)))) + when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)))) } ], }); @@ -297,7 +297,7 @@ registerAction2(class AddFilesToWorkingSetAction extends Action2 { title: localize2('workbench.action.chat.addSelectedFilesToWorkingSet.label', "Add Selected Files to Working Set"), icon: Codicon.attach, category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), + precondition: ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), f1: true }); } @@ -347,7 +347,7 @@ registerAction2(class RemoveAction extends Action2 { mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace, }, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), weight: KeybindingWeight.WorkbenchContrib, }, menu: [ @@ -355,7 +355,7 @@ registerAction2(class RemoveAction extends Action2 { id: MenuId.ChatMessageTitle, group: 'navigation', order: 2, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_REQUEST) + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.isRequest) } ] }); @@ -453,7 +453,7 @@ registerAction2(class OpenWorkingSetHistoryAction extends Action2 { id: MenuId.ChatEditingCodeBlockContext, group: 'navigation', order: 0, - when: ContextKeyExpr.notIn(CONTEXT_ITEM_ID.key, CONTEXT_LAST_ITEM_ID.key), + when: ContextKeyExpr.notIn(ChatContextKeys.itemId.key, ChatContextKeys.lastItemId.key), },] }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts index aacde88330c45..db6f5bebafd65 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts @@ -33,7 +33,7 @@ import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEdito import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js'; import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from '../../../multiDiffEditor/browser/multiDiffSourceResolverService.js'; import { ICodeMapperResponse, ICodeMapperService } from '../../common/chatCodeMapperService.js'; -import { CONTEXT_CHAT_EDITING_CAN_REDO, CONTEXT_CHAT_EDITING_CAN_UNDO } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { applyingChatEditsContextKey, applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingMaxFileAssignmentName, chatEditingResourceContextKey, ChatEditingSessionState, decidedChatEditingResourceContextKey, defaultChatEditingMaxFileLimit, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, IChatEditingSessionStream, inChatEditingSessionContextKey, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel, IChatTextEditGroup } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; @@ -129,10 +129,10 @@ export class ChatEditingService extends Disposable implements IChatEditingServic this._register(bindContextKey(applyingChatEditsContextKey, contextKeyService, (reader) => { return this._currentAutoApplyOperationObs.read(reader) !== null; })); - this._register(bindContextKey(CONTEXT_CHAT_EDITING_CAN_UNDO, contextKeyService, (r) => { + this._register(bindContextKey(ChatContextKeys.chatEditingCanUndo, contextKeyService, (r) => { return this._currentSessionObs.read(r)?.canUndo.read(r) || false; })); - this._register(bindContextKey(CONTEXT_CHAT_EDITING_CAN_REDO, contextKeyService, (r) => { + this._register(bindContextKey(ChatContextKeys.chatEditingCanRedo, contextKeyService, (r) => { return this._currentSessionObs.read(r)?.canRedo.read(r) || false; })); this._register(this._chatService.onDidDisposeSession((e) => { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorSaving.ts b/src/vs/workbench/contrib/chat/browser/chatEditorSaving.ts index 790ff8e78c771..138f0f0626ba4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorSaving.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorSaving.ts @@ -25,7 +25,7 @@ import { IEditorService } from '../../../services/editor/common/editorService.js import { IFilesConfigurationService } from '../../../services/filesConfiguration/common/filesConfigurationService.js'; import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js'; -import { CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../common/chatEditingService.js'; export class ChatEditorSaving extends Disposable implements IWorkbenchContribution { @@ -199,7 +199,7 @@ export class ChatEditingSaveAllAction extends Action2 { id: ChatEditingSaveAllAction.ID, title: ChatEditingSaveAllAction.LABEL, tooltip: ChatEditingSaveAllAction.LABEL, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), icon: Codicon.saveAll, menu: [ { @@ -218,13 +218,13 @@ export class ChatEditingSaveAllAction extends Action2 { applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasUndecidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey.negate()), ContextKeyExpr.notEquals('config.files.autoSave', 'off'), ContextKeyExpr.equals(`config.${ChatEditorSaving._config}`, false), - CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession) + ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession) ) } ], keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyS, - when: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_IN_CHAT_INPUT), + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatInput), weight: KeybindingWeight.WorkbenchContrib, }, }); diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 4c84444fb5f53..ce24f6e53e6dd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -69,7 +69,7 @@ import { AccessibilityVerbositySettingId } from '../../accessibility/browser/acc import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEditorSelectionStyling } from '../../codeEditor/browser/simpleEditorOptions.js'; import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js'; -import { CONTEXT_CHAT_HAS_FILE_ATTACHMENTS, CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INPUT_HAS_FOCUS, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../common/chatEditingService.js'; import { IChatRequestVariableEntry } from '../common/chatModel.js'; import { ChatRequestDynamicVariablePart } from '../common/chatParserTypes.js'; @@ -272,9 +272,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }; this.inputEditorMaxHeight = this.options.renderStyle === 'compact' ? INPUT_EDITOR_MAX_HEIGHT / 3 : INPUT_EDITOR_MAX_HEIGHT; - this.inputEditorHasText = CONTEXT_CHAT_INPUT_HAS_TEXT.bindTo(contextKeyService); - this.chatCursorAtTop = CONTEXT_CHAT_INPUT_CURSOR_AT_TOP.bindTo(contextKeyService); - this.inputEditorHasFocus = CONTEXT_CHAT_INPUT_HAS_FOCUS.bindTo(contextKeyService); + this.inputEditorHasText = ChatContextKeys.inputHasText.bindTo(contextKeyService); + this.chatCursorAtTop = ChatContextKeys.inputCursorAtTop.bindTo(contextKeyService); + this.inputEditorHasFocus = ChatContextKeys.inputHasFocus.bindTo(contextKeyService); this.history = this.loadHistory(); this._register(this.historyService.onDidClearHistory(() => this.history = new HistoryNavigator2([{ text: '' }], 50, historyKeyFn))); @@ -287,7 +287,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._chatEditsListPool = this._register(this.instantiationService.createInstance(CollapsibleListPool, this._onDidChangeVisibility.event, MenuId.ChatEditingWidgetModifiedFilesToolbar)); - this._hasFileAttachmentContextKey = CONTEXT_CHAT_HAS_FILE_ATTACHMENTS.bindTo(contextKeyService); + this._hasFileAttachmentContextKey = ChatContextKeys.hasFileAttachments.bindTo(contextKeyService); } private setCurrentLanguageModelToDefault() { @@ -548,7 +548,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.renderChatEditingSessionState(null, widget); const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(inputContainer)); - CONTEXT_IN_CHAT_INPUT.bindTo(inputScopedContextKeyService).set(true); + ChatContextKeys.inChatInput.bindTo(inputScopedContextKeyService).set(true); const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, inputScopedContextKeyService]))); const { historyNavigationBackwardsEnablement, historyNavigationForwardsEnablement } = this._register(registerAndCreateHistoryNavigationContext(inputScopedContextKeyService, this)); diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index f4d6ab305e7aa..fee9e532595e1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -43,7 +43,7 @@ import { IThemeService } from '../../../../platform/theme/common/themeService.js import { IWorkbenchIssueService } from '../../issue/common/issue.js'; import { annotateSpecialMarkdownContent } from '../common/annotations.js'; import { ChatAgentLocation, IChatAgentMetadata } from '../common/chatAgents.js'; -import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_ITEM_ID, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND, CONTEXT_RESPONSE_ERROR, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatRequestVariableEntry, IChatTextEditGroup } from '../common/chatModel.js'; import { chatSubcommandLeader } from '../common/chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatConfirmation, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatTask, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData } from '../common/chatService.js'; @@ -361,15 +361,15 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer showChatView(accessor.get(IViewsService)) }); @@ -206,7 +206,7 @@ export class MoveChatViewContribution extends Disposable implements IWorkbenchCo order: 1, canToggleVisibility: false, canMoveView: false, - when: ContextKeyExpr.and(CONTEXT_CHAT_SHOULD_SHOW_MOVED_VIEW_WELCOME, ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID)), + when: ContextKeyExpr.and(ChatContextKeys.shouldShowMovedViewWelcome, ContextKeyExpr.or(ChatContextKeys.panelParticipantRegistered, ChatContextKeys.extensionInvalid)), ctorDescriptor: new SyncDescriptor(MovedChatViewPane, [{ id: viewId }]), }; @@ -244,7 +244,7 @@ export class MoveChatViewContribution extends Disposable implements IWorkbenchCo return viewsRegistry.registerViewWelcomeContent(viewId, { content: [welcomeViewMainMessage, okButton, restoreButton, welcomeViewFooterMessage].join('\n\n'), renderSecondaryButtons: true, - when: ContextKeyExpr.and(CONTEXT_CHAT_SHOULD_SHOW_MOVED_VIEW_WELCOME, ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID)) + when: ContextKeyExpr.and(ChatContextKeys.shouldShowMovedViewWelcome, ContextKeyExpr.or(ChatContextKeys.panelParticipantRegistered, ChatContextKeys.extensionInvalid)) }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts index 99b4fdd4924a5..001ed69424a64 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts @@ -25,7 +25,7 @@ import * as extensionsRegistry from '../../../services/extensions/common/extensi import { showExtensionsWithIdsCommandId } from '../../extensions/browser/extensionsActions.js'; import { IExtension, IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; import { ChatAgentLocation, IChatAgentData, IChatAgentService } from '../common/chatAgents.js'; -import { CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IRawChatParticipantContribution } from '../common/chatParticipantContribTypes.js'; import { CHAT_VIEW_ID } from './chat.js'; import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from './chatViewPane.js'; @@ -317,7 +317,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { order: 100 }, ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.Panel }]), - when: ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID) + when: ContextKeyExpr.or(ChatContextKeys.panelParticipantRegistered, ChatContextKeys.extensionInvalid) }]; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer); @@ -350,7 +350,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.EditingSession }]), - when: CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED + when: ChatContextKeys.editingParticipantRegistered }]; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, viewContainer); @@ -379,7 +379,7 @@ export class ChatCompatibilityNotifier extends Disposable implements IWorkbenchC // It may be better to have some generic UI for this, for any extension that is incompatible, // but this is only enabled for Copilot Chat now and it needs to be obvious. - const isInvalid = CONTEXT_CHAT_EXTENSION_INVALID.bindTo(contextKeyService); + const isInvalid = ChatContextKeys.extensionInvalid.bindTo(contextKeyService); this._register(Event.runAndSubscribe( extensionsWorkbenchService.onDidChangeExtensionsNotification, () => { @@ -408,7 +408,7 @@ export class ChatCompatibilityNotifier extends Disposable implements IWorkbenchC const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); this._register(viewsRegistry.registerViewWelcomeContent(CHAT_VIEW_ID, { content: [mainMessage, commandButton, versionMessage].join('\n\n'), - when: CONTEXT_CHAT_EXTENSION_INVALID, + when: ChatContextKeys.extensionInvalid, })); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts index d6d0ff2be07fd..75f75066e1bde 100644 --- a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts @@ -10,7 +10,7 @@ import { AccessibleViewProviderId, AccessibleViewType, IAccessibleViewContentPro import { IAccessibleViewImplentation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; -import { CONTEXT_IN_CHAT_SESSION } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { isResponseVM } from '../common/chatViewModel.js'; import { ChatTreeItem, IChatWidget, IChatWidgetService } from './chat.js'; @@ -18,7 +18,7 @@ export class ChatResponseAccessibleView implements IAccessibleViewImplentation { readonly priority = 100; readonly name = 'panelChat'; readonly type = AccessibleViewType.View; - readonly when = CONTEXT_IN_CHAT_SESSION; + readonly when = ChatContextKeys.inChatSession; getProvider(accessor: ServicesAccessor) { const widgetService = accessor.get(IChatWidgetService); const widget = widgetService.lastFocusedWidget; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 6c3f268a0f451..154d127d6be00 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -34,7 +34,7 @@ import { buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHo import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService, IChatWelcomeMessageContent, isChatWelcomeMessageContent } from '../common/chatAgents.js'; -import { CONTEXT_CHAT_INPUT_HAS_AGENT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_QUICK_CHAT, CONTEXT_LAST_ITEM_ID, CONTEXT_RESPONSE_FILTERED } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatEditingSessionState, IChatEditingService, IChatEditingSession } from '../common/chatEditingService.js'; import { IChatModel, IChatRequestVariableEntry, IChatResponseModel } from '../common/chatModel.js'; import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, formatChatQuestion } from '../common/chatParserTypes.js'; @@ -248,11 +248,11 @@ export class ChatWidget extends Disposable implements IChatWidget { this._location = { location }; } - CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); - CONTEXT_CHAT_LOCATION.bindTo(contextKeyService).set(this._location.location); - CONTEXT_IN_QUICK_CHAT.bindTo(contextKeyService).set(isQuickChat(this)); - this.agentInInput = CONTEXT_CHAT_INPUT_HAS_AGENT.bindTo(contextKeyService); - this.requestInProgress = CONTEXT_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); + ChatContextKeys.inChatSession.bindTo(contextKeyService).set(true); + ChatContextKeys.location.bindTo(contextKeyService).set(this._location.location); + ChatContextKeys.inQuickChat.bindTo(contextKeyService).set(isQuickChat(this)); + this.agentInInput = ChatContextKeys.inputHasAgent.bindTo(contextKeyService); + this.requestInProgress = ChatContextKeys.requestInProgress.bindTo(contextKeyService); this._codeBlockModelCollection = this._register(instantiationService.createInstance(CodeBlockModelCollection)); @@ -547,7 +547,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const lastItem = treeItems[treeItems.length - 1]?.element; if (lastItem) { - CONTEXT_LAST_ITEM_ID.bindTo(this.contextKeyService).set([lastItem.id]); + ChatContextKeys.lastItemId.bindTo(this.contextKeyService).set([lastItem.id]); } if (lastItem && isResponseVM(lastItem) && lastItem.isComplete) { this.renderFollowups(lastItem.replyFollowups, lastItem); @@ -714,7 +714,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const selected = e.element; const scopedContextKeyService = this.contextKeyService.createOverlay([ - [CONTEXT_RESPONSE_FILTERED.key, isResponseVM(selected) && !!selected.errorDetails?.responseIsFiltered] + [ChatContextKeys.responseIsFiltered.key, isResponseVM(selected) && !!selected.errorDetails?.responseIsFiltered] ]); this.contextMenuService.showContextMenu({ menuId: MenuId.ChatContext, diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 88db10091c076..e7173e06145a8 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -63,7 +63,7 @@ import { MenuPreventer } from '../../codeEditor/browser/menuPreventer.js'; import { SelectionClipboardContributionID } from '../../codeEditor/browser/selectionClipboard.js'; import { getSimpleEditorOptions } from '../../codeEditor/browser/simpleEditorOptions.js'; import { IMarkdownVulnerability } from '../common/annotations.js'; -import { CONTEXT_CHAT_EDIT_APPLIED } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatResponseModel, IChatTextEditGroup } from '../common/chatModel.js'; import { IChatResponseViewModel, isResponseVM } from '../common/chatViewModel.js'; import { ChatTreeItem } from './chat.js'; @@ -748,7 +748,7 @@ export class CodeCompareBlockPart extends Disposable { const isEditApplied = Boolean(data.edit.state?.applied ?? 0); - CONTEXT_CHAT_EDIT_APPLIED.bindTo(this.contextKeyService).set(isEditApplied); + ChatContextKeys.editApplied.bindTo(this.contextKeyService).set(isEditApplied); this.element.classList.toggle('no-diff', isEditApplied); diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index a153c6f61720d..dcbaf09dbbbae 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -23,7 +23,7 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { asJson, IRequestService } from '../../../../platform/request/common/request.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; -import { CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from './chatContextKeys.js'; +import { ChatContextKeys } from './chatContextKeys.js'; import { IChatProgressHistoryResponseContent, IChatRequestVariableData, ISerializableChatAgentData } from './chatModel.js'; import { IRawChatCommandContribution, RawChatParticipantLocation } from './chatParticipantContribTypes.js'; import { IChatFollowup, IChatLocationData, IChatProgress, IChatResponseErrorDetails, IChatTaskDto } from './chatService.js'; @@ -255,9 +255,9 @@ export class ChatAgentService extends Disposable implements IChatAgentService { @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); - this._hasDefaultAgent = CONTEXT_CHAT_ENABLED.bindTo(this.contextKeyService); - this._defaultAgentRegistered = CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED.bindTo(this.contextKeyService); - this._editingAgentRegistered = CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED.bindTo(this.contextKeyService); + this._hasDefaultAgent = ChatContextKeys.enabled.bindTo(this.contextKeyService); + this._defaultAgentRegistered = ChatContextKeys.panelParticipantRegistered.bindTo(this.contextKeyService); + this._editingAgentRegistered = ChatContextKeys.editingParticipantRegistered.bindTo(this.contextKeyService); this._register(contextKeyService.onDidChangeContext((e) => { if (e.affectsSome(this._agentsContextKeys)) { this._updateContextKeys(); diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 9fee8ccab3a63..8dd8c552e0fdc 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -7,39 +7,41 @@ import { localize } from '../../../../nls.js'; import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { ChatAgentLocation } from './chatAgents.js'; -export const CONTEXT_RESPONSE_VOTE = new RawContextKey('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); -export const CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND = new RawContextKey('chatSessionResponseDetectedAgentOrCommand', false, { type: 'boolean', description: localize('chatSessionResponseDetectedAgentOrCommand', "When the agent or command was automatically detected") }); -export const CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING = new RawContextKey('chatResponseSupportsIssueReporting', false, { type: 'boolean', description: localize('chatResponseSupportsIssueReporting', "True when the current chat response supports issue reporting.") }); -export const CONTEXT_RESPONSE_FILTERED = new RawContextKey('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") }); -export const CONTEXT_RESPONSE_ERROR = new RawContextKey('chatSessionResponseError', false, { type: 'boolean', description: localize('chatResponseErrored', "True when the chat response resulted in an error.") }); -export const CONTEXT_CHAT_REQUEST_IN_PROGRESS = new RawContextKey('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") }); - -export const CONTEXT_RESPONSE = new RawContextKey('chatResponse', false, { type: 'boolean', description: localize('chatResponse', "The chat item is a response.") }); -export const CONTEXT_REQUEST = new RawContextKey('chatRequest', false, { type: 'boolean', description: localize('chatRequest', "The chat item is a request") }); -export const CONTEXT_ITEM_ID = new RawContextKey('chatItemId', '', { type: 'string', description: localize('chatItemId', "The id of the chat item.") }); -export const CONTEXT_LAST_ITEM_ID = new RawContextKey('chatLastItemId', [], { type: 'string', description: localize('chatLastItemId', "The id of the last chat item.") }); - -export const CONTEXT_CHAT_EDIT_APPLIED = new RawContextKey('chatEditApplied', false, { type: 'boolean', description: localize('chatEditApplied', "True when the chat text edits have been applied.") }); - -export const CONTEXT_CHAT_INPUT_HAS_TEXT = new RawContextKey('chatInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the chat input has text.") }); -export const CONTEXT_CHAT_INPUT_HAS_FOCUS = new RawContextKey('chatInputHasFocus', false, { type: 'boolean', description: localize('interactiveInputHasFocus', "True when the chat input has focus.") }); -export const CONTEXT_IN_CHAT_INPUT = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); -export const CONTEXT_IN_CHAT_SESSION = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); - -export const CONTEXT_CHAT_ENABLED = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is activated with an implementation.") }); -export const CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED = new RawContextKey('chatPanelParticipantRegistered', false, { type: 'boolean', description: localize('chatParticipantRegistered', "True when a default chat participant is registered for the panel.") }); -export const CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED = new RawContextKey('chatEditingParticipantRegistered', false, { type: 'boolean', description: localize('chatEditingParticipantRegistered', "True when a default chat participant is registered for editing.") }); -export const CONTEXT_CHAT_EDITING_CAN_UNDO = new RawContextKey('chatEditingCanUndo', false, { type: 'boolean', description: localize('chatEditingCanUndo', "True when it is possible to undo an interaction in the editing panel.") }); -export const CONTEXT_CHAT_EDITING_CAN_REDO = new RawContextKey('chatEditingCanRedo', false, { type: 'boolean', description: localize('chatEditingCanRedo', "True when it is possible to redo an interaction in the editing panel.") }); -export const CONTEXT_CHAT_EXTENSION_INVALID = new RawContextKey('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") }); -export const CONTEXT_CHAT_INPUT_CURSOR_AT_TOP = new RawContextKey('chatCursorAtTop', false); -export const CONTEXT_CHAT_INPUT_HAS_AGENT = new RawContextKey('chatInputHasAgent', false); -export const CONTEXT_CHAT_LOCATION = new RawContextKey('chatLocation', undefined); -export const CONTEXT_IN_QUICK_CHAT = new RawContextKey('quickChatHasFocus', false, { type: 'boolean', description: localize('inQuickChat', "True when the quick chat UI has focus, false otherwise.") }); -export const CONTEXT_CHAT_HAS_FILE_ATTACHMENTS = new RawContextKey('chatHasFileAttachments', false, { type: 'boolean', description: localize('chatHasFileAttachments', "True when the chat has file attachments.") }); - -export const CONTEXT_LANGUAGE_MODELS_ARE_USER_SELECTABLE = new RawContextKey('chatModelsAreUserSelectable', false, { type: 'boolean', description: localize('chatModelsAreUserSelectable', "True when the chat model can be selected manually by the user.") }); - -export const CONTEXT_CHAT_INSTALL_ENTITLED = new RawContextKey('chatInstallEntitled', false, { type: 'boolean', description: localize('chatInstallEntitled', "True when the user is entitled for chat installation.") }); - -export const CONTEXT_CHAT_SHOULD_SHOW_MOVED_VIEW_WELCOME = new RawContextKey('chatShouldShowMovedViewWelcome', false, { type: 'boolean', description: localize('chatShouldShowMovedViewWelcome', "True when the user should be shown the moved view welcome view.") }); +export namespace ChatContextKeys { + export const responseVote = new RawContextKey('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); + export const responseDetectedAgentCommand = new RawContextKey('chatSessionResponseDetectedAgentOrCommand', false, { type: 'boolean', description: localize('chatSessionResponseDetectedAgentOrCommand', "When the agent or command was automatically detected") }); + export const responseSupportsIssueReporting = new RawContextKey('chatResponseSupportsIssueReporting', false, { type: 'boolean', description: localize('chatResponseSupportsIssueReporting', "True when the current chat response supports issue reporting.") }); + export const responseIsFiltered = new RawContextKey('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") }); + export const responseHasError = new RawContextKey('chatSessionResponseError', false, { type: 'boolean', description: localize('chatResponseErrored', "True when the chat response resulted in an error.") }); + export const requestInProgress = new RawContextKey('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") }); + + export const isResponse = new RawContextKey('chatResponse', false, { type: 'boolean', description: localize('chatResponse', "The chat item is a response.") }); + export const isRequest = new RawContextKey('chatRequest', false, { type: 'boolean', description: localize('chatRequest', "The chat item is a request") }); + export const itemId = new RawContextKey('chatItemId', '', { type: 'string', description: localize('chatItemId', "The id of the chat item.") }); + export const lastItemId = new RawContextKey('chatLastItemId', [], { type: 'string', description: localize('chatLastItemId', "The id of the last chat item.") }); + + export const editApplied = new RawContextKey('chatEditApplied', false, { type: 'boolean', description: localize('chatEditApplied', "True when the chat text edits have been applied.") }); + + export const inputHasText = new RawContextKey('chatInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the chat input has text.") }); + export const inputHasFocus = new RawContextKey('chatInputHasFocus', false, { type: 'boolean', description: localize('interactiveInputHasFocus', "True when the chat input has focus.") }); + export const inChatInput = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); + export const inChatSession = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); + + export const enabled = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is activated with an implementation.") }); + export const panelParticipantRegistered = new RawContextKey('chatPanelParticipantRegistered', false, { type: 'boolean', description: localize('chatParticipantRegistered', "True when a default chat participant is registered for the panel.") }); + export const editingParticipantRegistered = new RawContextKey('chatEditingParticipantRegistered', false, { type: 'boolean', description: localize('chatEditingParticipantRegistered', "True when a default chat participant is registered for editing.") }); + export const chatEditingCanUndo = new RawContextKey('chatEditingCanUndo', false, { type: 'boolean', description: localize('chatEditingCanUndo', "True when it is possible to undo an interaction in the editing panel.") }); + export const chatEditingCanRedo = new RawContextKey('chatEditingCanRedo', false, { type: 'boolean', description: localize('chatEditingCanRedo', "True when it is possible to redo an interaction in the editing panel.") }); + export const extensionInvalid = new RawContextKey('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") }); + export const inputCursorAtTop = new RawContextKey('chatCursorAtTop', false); + export const inputHasAgent = new RawContextKey('chatInputHasAgent', false); + export const location = new RawContextKey('chatLocation', undefined); + export const inQuickChat = new RawContextKey('quickChatHasFocus', false, { type: 'boolean', description: localize('inQuickChat', "True when the quick chat UI has focus, false otherwise.") }); + export const hasFileAttachments = new RawContextKey('chatHasFileAttachments', false, { type: 'boolean', description: localize('chatHasFileAttachments', "True when the chat has file attachments.") }); + + export const languageModelsAreUserSelectable = new RawContextKey('chatModelsAreUserSelectable', false, { type: 'boolean', description: localize('chatModelsAreUserSelectable', "True when the chat model can be selected manually by the user.") }); + + export const installEntitled = new RawContextKey('chatInstallEntitled', false, { type: 'boolean', description: localize('chatInstallEntitled', "True when the user is entitled for chat installation.") }); + + export const shouldShowMovedViewWelcome = new RawContextKey('chatShouldShowMovedViewWelcome', false, { type: 'boolean', description: localize('chatShouldShowMovedViewWelcome', "True when the user should be shown the moved view welcome view.") }); +} diff --git a/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts b/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts index 0c103eead9d18..fb5352a0113ae 100644 --- a/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts +++ b/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts @@ -14,7 +14,7 @@ import { ExtensionIdentifier } from '../../../../platform/extensions/common/exte import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IRequestService, asText } from '../../../../platform/request/common/request.js'; import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; -import { CONTEXT_CHAT_INSTALL_ENTITLED } from './chatContextKeys.js'; +import { ChatContextKeys } from './chatContextKeys.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IRequestContext } from '../../../../base/parts/request/common/request.js'; @@ -34,7 +34,7 @@ class ChatInstallEntitlementContribution extends Disposable implements IWorkbenc private static readonly CHAT_EXTENSION_INSTALLED_KEY = 'chat.extensionInstalled'; - private readonly chatInstallEntitledContextKey = CONTEXT_CHAT_INSTALL_ENTITLED.bindTo(this.contextService); + private readonly chatInstallEntitledContextKey = ChatContextKeys.installEntitled.bindTo(this.contextService); private resolvedEntitlement: boolean | undefined = undefined; diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index 7cee12f0e27c6..b3310ea5332db 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -16,7 +16,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { ILogService } from '../../../../platform/log/common/log.js'; import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js'; -import { CONTEXT_LANGUAGE_MODELS_ARE_USER_SELECTABLE } from './chatContextKeys.js'; +import { ChatContextKeys } from './chatContextKeys.js'; export const enum ChatMessageRole { System, @@ -190,7 +190,7 @@ export class LanguageModelsService implements ILanguageModelsService { @ILogService private readonly _logService: ILogService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { - this._hasUserSelectableModels = CONTEXT_LANGUAGE_MODELS_ARE_USER_SELECTABLE.bindTo(this._contextKeyService); + this._hasUserSelectableModels = ChatContextKeys.languageModelsAreUserSelectable.bindTo(this._contextKeyService); this._store.add(languageModelExtensionPoint.setHandler((extensions) => { diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 12ed7d13047b1..198a49bb8696b 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -37,7 +37,7 @@ import { CHAT_CATEGORY } from '../../browser/actions/chatActions.js'; import { IChatExecuteActionContext } from '../../browser/actions/chatExecuteActions.js'; import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView } from '../../browser/chat.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_CHAT_ENABLED, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_CHAT_LOCATION } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { KEYWORD_ACTIVIATION_SETTING_ID } from '../../common/chatService.js'; import { ChatResponseViewModel, IChatResponseViewModel, isResponseVM } from '../../common/chatViewModel.js'; import { IVoiceChatService, VoiceChatInProgress as GlobalVoiceChatInProgress } from '../../common/voiceChatService.js'; @@ -61,9 +61,9 @@ type VoiceChatSessionContext = 'view' | 'inline' | 'quick' | 'editor'; const VoiceChatSessionContexts: VoiceChatSessionContext[] = ['view', 'inline', 'quick', 'editor']; // Global Context Keys (set on global context key service) -const CanVoiceChat = ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, HasSpeechProvider); -const FocusInChatInput = ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT); -const AnyChatRequestInProgress = CONTEXT_CHAT_REQUEST_IN_PROGRESS; +const CanVoiceChat = ContextKeyExpr.and(ChatContextKeys.enabled, HasSpeechProvider); +const FocusInChatInput = ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, ChatContextKeys.inChatInput); +const AnyChatRequestInProgress = ChatContextKeys.requestInProgress; // Scoped Context Keys (set on per-chat-context scoped context key service) const ScopedVoiceChatGettingReady = new RawContextKey('scopedVoiceChatGettingReady', false, { type: 'boolean', description: localize('scopedVoiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat. This key is only defined scoped, per chat context.") }); @@ -435,7 +435,7 @@ export class VoiceChatInChatViewAction extends VoiceChatWithHoldModeAction { category: CHAT_CATEGORY, precondition: ContextKeyExpr.and( CanVoiceChat, - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate() // disable when a chat request is in progress + ChatContextKeys.requestInProgress.negate() // disable when a chat request is in progress ), f1: true }, 'view'); @@ -454,7 +454,7 @@ export class HoldToVoiceChatInChatViewAction extends Action2 { weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and( CanVoiceChat, - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), // disable when a chat request is in progress + ChatContextKeys.requestInProgress.negate(), // disable when a chat request is in progress FocusInChatInput?.negate(), // when already in chat input, disable this action and prefer to start voice chat directly EditorContextKeys.focus.negate(), // do not steal the inline-chat keybinding NOTEBOOK_EDITOR_FOCUSED.negate() // do not steal the notebook keybinding @@ -508,7 +508,7 @@ export class InlineVoiceChatAction extends VoiceChatWithHoldModeAction { precondition: ContextKeyExpr.and( CanVoiceChat, ActiveEditorContext, - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate() // disable when a chat request is in progress + ChatContextKeys.requestInProgress.negate() // disable when a chat request is in progress ), f1: true }, 'inline'); @@ -526,7 +526,7 @@ export class QuickVoiceChatAction extends VoiceChatWithHoldModeAction { category: CHAT_CATEGORY, precondition: ContextKeyExpr.and( CanVoiceChat, - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate() // disable when a chat request is in progress + ChatContextKeys.requestInProgress.negate() // disable when a chat request is in progress ), f1: true }, 'quick'); @@ -568,13 +568,13 @@ export class StartVoiceChatAction extends Action2 { menu: [ { id: MenuId.ChatInput, - when: ContextKeyExpr.and(ContextKeyExpr.or(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession)), menuCondition), + when: ContextKeyExpr.and(ContextKeyExpr.or(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), menuCondition), group: 'navigation', order: 3 }, { id: MenuId.ChatExecute, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession).negate(), menuCondition), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession).negate(), menuCondition), group: 'navigation', order: 2 } @@ -616,13 +616,13 @@ export class StopListeningAction extends Action2 { menu: [ { id: MenuId.ChatInput, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), AnyScopedVoiceChatInProgress), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), AnyScopedVoiceChatInProgress), group: 'navigation', order: 3 }, { id: MenuId.ChatExecute, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), AnyScopedVoiceChatInProgress), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), AnyScopedVoiceChatInProgress), group: 'navigation', order: 2 } @@ -850,9 +850,9 @@ export class ReadChatResponseAloud extends Action2 { id: MenuId.ChatMessageFooter, when: ContextKeyExpr.and( CanVoiceChat, - CONTEXT_RESPONSE, // only for responses + ChatContextKeys.isResponse, // only for responses ScopedChatSynthesisInProgress.negate(), // but not when already in progress - CONTEXT_RESPONSE_FILTERED.negate(), // and not when response is filtered + ChatContextKeys.responseIsFiltered.negate(), // and not when response is filtered ), group: 'navigation', order: -10 // first @@ -860,9 +860,9 @@ export class ReadChatResponseAloud extends Action2 { id: MENU_INLINE_CHAT_WIDGET_SECONDARY, when: ContextKeyExpr.and( CanVoiceChat, - CONTEXT_RESPONSE, // only for responses + ChatContextKeys.isResponse, // only for responses ScopedChatSynthesisInProgress.negate(), // but not when already in progress - CONTEXT_RESPONSE_FILTERED.negate() // and not when response is filtered + ChatContextKeys.responseIsFiltered.negate() // and not when response is filtered ), group: 'navigation', order: -10 // first @@ -936,13 +936,13 @@ export class StopReadAloud extends Action2 { menu: [ { id: MenuId.ChatInput, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), ScopedChatSynthesisInProgress), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ScopedChatSynthesisInProgress), group: 'navigation', order: 3 }, { id: MenuId.ChatExecute, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), ScopedChatSynthesisInProgress), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), ScopedChatSynthesisInProgress), group: 'navigation', order: 2 } @@ -974,8 +974,8 @@ export class StopReadChatItemAloud extends Action2 { id: MenuId.ChatMessageFooter, when: ContextKeyExpr.and( ScopedChatSynthesisInProgress, // only when in progress - CONTEXT_RESPONSE, // only for responses - CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered + ChatContextKeys.isResponse, // only for responses + ChatContextKeys.responseIsFiltered.negate() // but not when response is filtered ), group: 'navigation', order: -10 // first @@ -984,8 +984,8 @@ export class StopReadChatItemAloud extends Action2 { id: MENU_INLINE_CHAT_WIDGET_SECONDARY, when: ContextKeyExpr.and( ScopedChatSynthesisInProgress, // only when in progress - CONTEXT_RESPONSE, // only for responses - CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered + ChatContextKeys.isResponse, // only for responses + ChatContextKeys.responseIsFiltered.negate() // but not when response is filtered ), group: 'navigation', order: -10 // first @@ -1283,13 +1283,13 @@ export class InstallSpeechProviderForVoiceChatAction extends BaseInstallSpeechPr menu: [ { id: MenuId.ChatInput, - when: ContextKeyExpr.and(HasSpeechProvider.negate(), ContextKeyExpr.or(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession))), + when: ContextKeyExpr.and(HasSpeechProvider.negate(), ContextKeyExpr.or(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession))), group: 'navigation', order: 3 }, { id: MenuId.ChatExecute, - when: ContextKeyExpr.and(HasSpeechProvider.negate(), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession).negate()), + when: ContextKeyExpr.and(HasSpeechProvider.negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession).negate()), group: 'navigation', order: 2 } diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 096a909b74a51..c6d45bda96265 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -36,7 +36,7 @@ import { showLoadedScriptMenu } from '../common/loadedScriptsPicker.js'; import { showDebugSessionMenu } from './debugSessionPicker.js'; import { TEXT_FILE_EDITOR_ID } from '../../files/common/files.js'; import { ILocalizedString } from '../../../../platform/action/common/action.js'; -import { CONTEXT_IN_CHAT_SESSION } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; export const ADD_CONFIGURATION_ID = 'debug.addConfiguration'; @@ -1014,7 +1014,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorContext, { CONTEXT_IN_DEBUG_MODE, PanelFocusContext.toNegated(), EditorContextKeys.editorTextFocus, - CONTEXT_IN_CHAT_SESSION.toNegated()), + ChatContextKeys.inChatSession.toNegated()), group: 'debug', order: 1 }); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 4072cf0b64a10..b513b4056f226 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -23,7 +23,7 @@ import { ServicesAccessor } from '../../../../platform/instantiation/common/inst import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { PanelFocusContext } from '../../../common/contextkeys.js'; -import { CONTEXT_IN_CHAT_SESSION } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { openBreakpointSource } from './breakpointsView.js'; import { DisassemblyView } from './disassemblyView.js'; import { Repl } from './repl.js'; @@ -309,7 +309,7 @@ export class RunToCursorAction extends EditorAction { CONTEXT_DEBUGGERS_AVAILABLE, PanelFocusContext.toNegated(), ContextKeyExpr.or(EditorContextKeys.editorTextFocus, CONTEXT_DISASSEMBLY_VIEW_FOCUS), - CONTEXT_IN_CHAT_SESSION.negate() + ChatContextKeys.inChatSession.negate() ), contextMenuOpts: { group: 'debug', @@ -354,7 +354,7 @@ export class SelectionToReplAction extends EditorAction { precondition: ContextKeyExpr.and( CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus, - CONTEXT_IN_CHAT_SESSION.negate()), + ChatContextKeys.inChatSession.negate()), contextMenuOpts: { group: 'debug', order: 0 @@ -397,7 +397,7 @@ export class SelectionToWatchExpressionsAction extends EditorAction { precondition: ContextKeyExpr.and( CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus, - CONTEXT_IN_CHAT_SESSION.negate()), + ChatContextKeys.inChatSession.negate()), contextMenuOpts: { group: 'debug', order: 1 diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index bf80d6d241b40..35c4e7b1f8109 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -21,7 +21,7 @@ import { InlineChatEnabler, InlineChatSessionServiceImpl } from './inlineChatSes import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; import { CancelAction, SubmitAction } from '../../chat/browser/actions/chatExecuteActions.js'; import { localize } from '../../../../nls.js'; -import { CONTEXT_CHAT_INPUT_HAS_TEXT } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { InlineChatAccessibilityHelp } from './inlineChatAccessibilityHelp.js'; import { InlineChatExansionContextKey, InlineChatExpandLineAction } from './inlineChatCurrentLine.js'; @@ -47,7 +47,7 @@ const editActionMenuItem: IMenuItem = { title: localize('send.edit', "Edit Code"), }, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT, + ChatContextKeys.inputHasText, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), CTX_INLINE_CHAT_EDITING ), @@ -61,7 +61,7 @@ const generateActionMenuItem: IMenuItem = { title: localize('send.generate', "Generate"), }, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT, + ChatContextKeys.inputHasText, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), CTX_INLINE_CHAT_EDITING.toNegated() ), diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts index e44fe66d93f98..338697c4b7bd9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts @@ -9,14 +9,14 @@ import { AccessibleViewType } from '../../../../platform/accessibility/browser/a import { IAccessibleViewImplentation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { getChatAccessibilityHelpProvider } from '../../chat/browser/actions/chatAccessibilityHelp.js'; -import { CONTEXT_CHAT_INPUT_HAS_FOCUS } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from '../common/inlineChat.js'; export class InlineChatAccessibilityHelp implements IAccessibleViewImplentation { readonly priority = 106; readonly name = 'inlineChat'; readonly type = AccessibleViewType.Help; - readonly when = ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CONTEXT_CHAT_INPUT_HAS_FOCUS); + readonly when = ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, ChatContextKeys.inputHasFocus); getProvider(accessor: ServicesAccessor) { const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (!codeEditor) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 030b9bda36596..411bc4764d632 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -25,7 +25,7 @@ import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js' import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IChatService } from '../../chat/common/chatService.js'; -import { CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { HunkInformation } from './inlineChatSession.js'; import { IChatWidgetService } from '../../chat/browser/chat.js'; @@ -255,7 +255,7 @@ export class AcceptChanges extends AbstractInlineChatAction { group: '0_main', order: 1, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + ChatContextKeys.inputHasText.toNegated(), CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits) ), @@ -285,7 +285,7 @@ export class DiscardHunkAction extends AbstractInlineChatAction { group: '0_main', order: 2, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + ChatContextKeys.inputHasText.toNegated(), CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits), CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live) @@ -322,7 +322,7 @@ export class RerunAction extends AbstractInlineChatAction { group: '0_main', order: 5, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + ChatContextKeys.inputHasText.toNegated(), CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.None) ) @@ -465,7 +465,7 @@ export class ViewInChatAction extends AbstractInlineChatAction { group: '0_main', order: 1, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + ChatContextKeys.inputHasText.toNegated(), CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.Messages), CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate() ) @@ -473,7 +473,7 @@ export class ViewInChatAction extends AbstractInlineChatAction { keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - when: CONTEXT_IN_CHAT_INPUT + when: ChatContextKeys.inChatInput } }); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 0854a1354b297..946ac198bcb0b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -48,7 +48,7 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IInlineChatSavingService } from './inlineChatSavingService.js'; import { IInlineChatSessionService } from './inlineChatSessionService.js'; import { InlineChatZoneWidget } from './inlineChatZoneWidget.js'; -import { CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -153,8 +153,8 @@ export class InlineChatController implements IEditorContribution { this._ctxResponseType = CTX_INLINE_CHAT_RESPONSE_TYPE.bindTo(contextKeyService); this._ctxRequestInProgress = CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); - this._ctxResponse = CONTEXT_RESPONSE.bindTo(contextKeyService); - CONTEXT_RESPONSE_ERROR.bindTo(contextKeyService); + this._ctxResponse = ChatContextKeys.isResponse.bindTo(contextKeyService); + ChatContextKeys.responseHasError.bindTo(contextKeyService); this._ui = new Lazy(() => { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 3881a693a6936..5b1a2d989412b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -13,7 +13,6 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { constObservable, derived, ISettableObservable, observableValue } from '../../../../base/common/observable.js'; -import './media/inlineChat.css'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from '../../../../editor/browser/widget/diffEditor/components/accessibleDiffViewer.js'; import { EditorOption, IComputedEditorOptions } from '../../../../editor/common/config/editorOptions.js'; @@ -38,7 +37,9 @@ 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 { asCssVariable, asCssVariableName, editorBackground, inputBackground } from '../../../../platform/theme/common/colorRegistry.js'; +import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js'; import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; import { MarkUnhelpfulActionId } from '../../chat/browser/actions/chatTitleActions.js'; @@ -46,14 +47,13 @@ import { IChatWidgetViewOptions } from '../../chat/browser/chat.js'; import { ChatVoteDownButton } from '../../chat/browser/chatListRenderer.js'; import { ChatWidget, IChatViewState, IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; import { chatRequestBackground } from '../../chat/common/chatColors.js'; -import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { IChatModel } from '../../chat/common/chatModel.js'; import { ChatAgentVoteDirection, IChatService } from '../../chat/common/chatService.js'; import { isResponseVM } from '../../chat/common/chatViewModel.js'; -import { HunkInformation, Session } from './inlineChatSession.js'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, inlineChatBackground, inlineChatForeground } from '../common/inlineChat.js'; -import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js'; -import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; +import { HunkInformation, Session } from './inlineChatSession.js'; +import './media/inlineChat.css'; export interface InlineChatWidgetViewState { @@ -187,11 +187,11 @@ export class InlineChatWidget { this._chatWidget.setVisible(true); this._store.add(this._chatWidget); - const ctxResponse = CONTEXT_RESPONSE.bindTo(this.scopedContextKeyService); - const ctxResponseVote = CONTEXT_RESPONSE_VOTE.bindTo(this.scopedContextKeyService); - const ctxResponseSupportIssues = CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING.bindTo(this.scopedContextKeyService); - const ctxResponseError = CONTEXT_RESPONSE_ERROR.bindTo(this.scopedContextKeyService); - const ctxResponseErrorFiltered = CONTEXT_RESPONSE_FILTERED.bindTo(this.scopedContextKeyService); + const ctxResponse = ChatContextKeys.isResponse.bindTo(this.scopedContextKeyService); + const ctxResponseVote = ChatContextKeys.responseVote.bindTo(this.scopedContextKeyService); + const ctxResponseSupportIssues = ChatContextKeys.responseSupportsIssueReporting.bindTo(this.scopedContextKeyService); + const ctxResponseError = ChatContextKeys.responseHasError.bindTo(this.scopedContextKeyService); + const ctxResponseErrorFiltered = ChatContextKeys.responseIsFiltered.bindTo(this.scopedContextKeyService); const viewModelStore = this._store.add(new DisposableStore()); this._store.add(this._chatWidget.onDidChangeViewModel(() => { diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 00a48c648f2fa..e32df03950a2e 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -26,7 +26,7 @@ import { IS_COMPOSITE_NOTEBOOK, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_GENE import { Iterable } from '../../../../../../base/common/iterator.js'; import { ICodeEditor } from '../../../../../../editor/browser/editorBrowser.js'; import { IEditorService } from '../../../../../services/editor/common/editorService.js'; -import { CONTEXT_CHAT_INPUT_HAS_TEXT } from '../../../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../../../chat/common/chatContextKeys.js'; import { AbstractInlineChatAction } from '../../../../inlineChat/browser/inlineChatActions.js'; import { InlineChatController } from '../../../../inlineChat/browser/inlineChatController.js'; import { HunkInformation } from '../../../../inlineChat/browser/inlineChatSession.js'; @@ -693,7 +693,7 @@ export class AcceptChangesAndRun extends AbstractInlineChatAction { order: 2, when: ContextKeyExpr.and( NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), - CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + ChatContextKeys.inputHasText.toNegated(), CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits) ) From b6648af1eac7021cc646d1a9f59e93026a7c1689 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 29 Oct 2024 12:18:35 -0700 Subject: [PATCH 064/555] Align codicons in chat paragraph (#232523) Fix microsoft/vscode-copilot#9374 --- src/vs/workbench/contrib/chat/browser/media/chat.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 9907d5c5d8e59..d044da15bdfa6 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -305,6 +305,11 @@ margin: 8px 0 16px 0; } +.interactive-item-container .value .rendered-markdown .codicon { + position: relative; + top: 2px; +} + .interactive-item-container .value .rendered-markdown p { line-height: 1.5em; } From a6f38353e616fe1fbc695891700a2a8c87f24b93 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 29 Oct 2024 14:47:19 -0500 Subject: [PATCH 065/555] cache which task providers have been activated (#232505) --- .../contrib/tasks/browser/abstractTaskService.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index f3ade8acfbcc8..ae2035c4b07f5 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -237,6 +237,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _onDidChangeTaskProviders = this._register(new Emitter()); public onDidChangeTaskProviders = this._onDidChangeTaskProviders.event; + private _activatedTaskProviders: Set = new Set(); + constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @IMarkerService protected readonly _markerService: IMarkerService, @@ -614,15 +616,21 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private async _activateTaskProviders(type: string | undefined): Promise { + if (type && this._activatedTaskProviders.has(type)) { + return; + } // We need to first wait for extensions to be registered because we might read // the `TaskDefinitionRegistry` in case `type` is `undefined` await this._extensionService.whenInstalledExtensionsRegistered(); this._log('Activating task providers ' + (type ?? 'all')); - await raceTimeout( + const result = await raceTimeout( Promise.all(this._getActivationEvents(type).map(activationEvent => this._extensionService.activateByEvent(activationEvent))), 5000, () => console.warn('Timed out activating extensions for task providers') ); + if (result && type) { + this._activatedTaskProviders.add(type); + } } private _updateSetup(setup?: [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion, IWorkspace | undefined]): void { From 32a55f1279c69b1c22fe5b98ef486968a0e0a8c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:47:48 -0700 Subject: [PATCH 066/555] Fix julia executable --- src/vs/platform/terminal/node/windowsShellHelper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/node/windowsShellHelper.ts b/src/vs/platform/terminal/node/windowsShellHelper.ts index c073c7c01b685..15edb8c02cab3 100644 --- a/src/vs/platform/terminal/node/windowsShellHelper.ts +++ b/src/vs/platform/terminal/node/windowsShellHelper.ts @@ -31,7 +31,7 @@ const SHELL_EXECUTABLES = [ 'debian.exe', 'opensuse-42.exe', 'sles-12.exe', - 'julialauncher.exe', + 'julia.exe', 'nu.exe', ]; @@ -155,7 +155,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe case 'bash.exe': case 'git-cmd.exe': return WindowsShellType.GitBash; - case 'julialauncher.exe': + case 'julia.exe': return GeneralShellType.Julia; case 'nu.exe': return GeneralShellType.NuShell; From 6c3a7141c7be4a980076e14395ba2c3b5a76c971 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 29 Oct 2024 12:47:58 -0700 Subject: [PATCH 067/555] testing: reveal tests that are filtered to (#232526) Fixes #228195 --- .../testing/browser/testingExplorerView.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 4343ac69a5a90..8528080e5e938 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -772,7 +772,19 @@ class TestingExplorerViewModel extends Disposable { filterState.text.onDidChange, filterState.fuzzy.onDidChange, testService.excluded.onTestExclusionsChanged, - )(this.tree.refilter, this.tree)); + )(() => { + if (!filterState.text.value) { + return this.tree.refilter(); + } + + const items = this.filter.lastIncludedTests = new Set(); + this.tree.refilter(); + this.filter.lastIncludedTests = undefined; + + for (const test of items) { + this.tree.expandTo(test); + } + })); this._register(this.tree.onDidOpen(e => { if (e.element instanceof TestItemTreeElement && !e.element.children.size && e.element.test.item.uri) { @@ -1125,6 +1137,8 @@ const hasNodeInOrParentOfUri = (collection: IMainThreadTestCollection, ident: IU class TestsFilter implements ITreeFilter { private documentUris: URI[] = []; + public lastIncludedTests?: Set; + constructor( private readonly collection: IMainThreadTestCollection, @ITestExplorerFilterState private readonly state: ITestExplorerFilterState, @@ -1152,6 +1166,7 @@ class TestsFilter implements ITreeFilter { case FilterResult.Exclude: return TreeVisibility.Hidden; case FilterResult.Include: + this.lastIncludedTests?.add(element); return TreeVisibility.Visible; default: return TreeVisibility.Recurse; From c37e3894ea8ec53ff347ff244907b0e26b1ee6db Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 29 Oct 2024 14:12:45 -0700 Subject: [PATCH 068/555] Fix copilot edits running off the top of the view too easily (#232531) Fix microsoft/vscode-copilot#9694 --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 154d127d6be00..03abf59e04401 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1130,7 +1130,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.tree.getHTMLElement().style.height = `${listHeight}px`; // Push the welcome message down so it doesn't change position when followups appear - const followupsOffset = Math.max(100 - this.inputPart.followupsHeight, 0); + const followupsOffset = this.viewOptions.renderFollowups ? Math.max(100 - this.inputPart.followupsHeight, 0) : 0; this.welcomeMessageContainer.style.height = `${listHeight - followupsOffset}px`; this.welcomeMessageContainer.style.paddingBottom = `${followupsOffset}px`; this.renderer.layout(width); From fcae80e2dfe9ef495ec4b26203fbaa6f4b7e15f0 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:26:18 -0700 Subject: [PATCH 069/555] fix: useMsal setting doesn't have tags (#232534) --- extensions/microsoft-authentication/package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 15acb5db286d4..9eea07ec02436 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -101,7 +101,11 @@ "properties": { "microsoft.useMsal": { "type": "boolean", - "description": "%useMsal.description%" + "description": "%useMsal.description%", + "tags": [ + "onExP", + "preview" + ] } } } From db1b27ac578c31b526bfde306d255bdb7167592c Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 29 Oct 2024 15:37:49 -0700 Subject: [PATCH 070/555] re #212879. dispose child instantiation service properly (#232532) * re #212879. dispose child instantiation service properly * fix unit tests * Fix memory leaks --- .../services/notebookEditorServiceImpl.ts | 21 ++++++++++++------- .../NotebookEditorWidgetService.test.ts | 2 ++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index 412c6f77621cc..ddf27fd85f2d2 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -38,7 +38,7 @@ export class NotebookEditorWidgetService implements INotebookEditorService { readonly onDidAddNotebookEditor = this._onNotebookEditorAdd.event; readonly onDidRemoveNotebookEditor = this._onNotebookEditorsRemove.event; - private readonly _borrowableEditors = new Map>(); + private readonly _borrowableEditors = new Map>(); constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -65,6 +65,7 @@ export class NotebookEditorWidgetService implements INotebookEditorService { const value = widgets.splice(index, 1)[0]; value.token = undefined; this._disposeWidget(value.widget); + value.disposableStore.dispose(); value.widget = (undefined); // unset the widget so that others that still hold a reference don't harm us }); })); @@ -98,6 +99,7 @@ export class NotebookEditorWidgetService implements INotebookEditorService { for (const value of values) { value.token = undefined; this._disposeWidget(value.widget); + value.disposableStore.dispose(); } } } @@ -125,6 +127,11 @@ export class NotebookEditorWidgetService implements INotebookEditorService { listeners.forEach(listener => listener.dispose()); }); this.groupListener.clear(); + this._borrowableEditors.forEach(widgetMap => { + widgetMap.forEach(widgets => { + widgets.forEach(widget => widget.disposableStore.dispose()); + }); + }); } // --- group-based editor borrowing... @@ -205,9 +212,10 @@ export class NotebookEditorWidgetService implements INotebookEditorService { // NEW widget const editorGroupContextKeyService = accessor.get(IContextKeyService); const editorGroupEditorProgressService = accessor.get(IEditorProgressService); - const widget = this.createWidget(editorGroupContextKeyService, editorGroupEditorProgressService, creationOptions, codeWindow, initialDimension); + const widgetDisposeStore = new DisposableStore(); + const widget = this.createWidget(editorGroupContextKeyService, widgetDisposeStore, editorGroupEditorProgressService, creationOptions, codeWindow, initialDimension); const token = this._tokenPool++; - value = { widget, editorType: input.typeId, token }; + value = { widget, editorType: input.typeId, token, disposableStore: widgetDisposeStore }; let map = this._borrowableEditors.get(groupId); if (!map) { @@ -217,7 +225,6 @@ export class NotebookEditorWidgetService implements INotebookEditorService { const values = map.get(input.resource) ?? []; values.push(value); map.set(input.resource, values); - } else { // reuse a widget which was either free'ed before or which // is simply being reused... @@ -228,10 +235,10 @@ export class NotebookEditorWidgetService implements INotebookEditorService { } // protected for unit testing overrides - protected createWidget(editorGroupContextKeyService: IContextKeyService, editorGroupEditorProgressService: IEditorProgressService, creationOptions?: INotebookEditorCreationOptions, codeWindow?: CodeWindow, initialDimension?: Dimension) { - const notebookInstantiationService = this.instantiationService.createChild(new ServiceCollection( + protected createWidget(editorGroupContextKeyService: IContextKeyService, widgetDisposeStore: DisposableStore, editorGroupEditorProgressService: IEditorProgressService, creationOptions?: INotebookEditorCreationOptions, codeWindow?: CodeWindow, initialDimension?: Dimension) { + const notebookInstantiationService = widgetDisposeStore.add(this.instantiationService.createChild(new ServiceCollection( [IContextKeyService, editorGroupContextKeyService], - [IEditorProgressService, editorGroupEditorProgressService])); + [IEditorProgressService, editorGroupEditorProgressService]))); const ctorOptions = creationOptions ?? getDefaultNotebookCreationOptions(); const widget = notebookInstantiationService.createInstance(NotebookEditorWidget, { ...ctorOptions, diff --git a/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts index 4740b7d365125..d90a9173854f4 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts @@ -135,6 +135,8 @@ suite('NotebookEditorWidgetService', () => { assert.strictEqual(widget.value, undefined, 'widgets in group should get disposed'); assert.strictEqual(widgetDiffType.value, undefined, 'widgets in group should get disposed'); assert.notStrictEqual(widgetDiffGroup.value, undefined, 'other group should not be disposed'); + + notebookEditorService.dispose(); }); test('Widget should move between groups when editor is moved', async function () { From 33f6fab2d12572675e11dae0e5e0c31032b20cdf Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 29 Oct 2024 15:50:50 -0700 Subject: [PATCH 071/555] Escape description and detail hovers (#232539) * Escape description and detail hovers Fixes https://github.com/microsoft/vscode/issues/231065 * lint --- .../quickinput/browser/quickInputTree.ts | 33 +++---------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInputTree.ts b/src/vs/platform/quickinput/browser/quickInputTree.ts index 0cc8255971d8a..3203be9e53570 100644 --- a/src/vs/platform/quickinput/browser/quickInputTree.ts +++ b/src/vs/platform/quickinput/browser/quickInputTree.ts @@ -33,7 +33,7 @@ import { Lazy } from '../../../base/common/lazy.js'; import { IParsedLabelWithIcons, getCodiconAriaLabel, matchesFuzzyIconAware, parseLabelWithIcons } from '../../../base/common/iconLabels.js'; import { HoverPosition } from '../../../base/browser/ui/hover/hoverWidget.js'; import { compareAnything } from '../../../base/common/comparers.js'; -import { ltrim } from '../../../base/common/strings.js'; +import { escape, ltrim } from '../../../base/common/strings.js'; import { RenderIndentGuides } from '../../../base/browser/ui/tree/abstractTree.js'; import { ThrottledDelayer } from '../../../base/common/async.js'; import { isCancellationError } from '../../../base/common/errors.js'; @@ -443,7 +443,7 @@ class QuickPickItemElementRenderer extends BaseQuickInputListRenderer Date: Tue, 29 Oct 2024 16:18:00 -0700 Subject: [PATCH 072/555] Fix #232519 (#232540) --- .../browser/contrib/chatInputCompletions.ts | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 426a4b67f2dc5..a5fe14671f4d5 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -60,6 +60,11 @@ class SlashCommandCompletions extends Disposable { return null; } + if (!isEmptyUpToCompletionWord(model, range)) { + // No text allowed before the completion + return; + } + const parsedRequest = widget.parsedInput.parts; const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart); if (usedAgent) { @@ -79,7 +84,7 @@ class SlashCommandCompletions extends Disposable { label: withSlash, insertText: c.executeImmediately ? '' : `${withSlash} `, detail: c.detail, - range: new Range(1, 1, 1, 1), + range, sortText: c.sortText ?? 'a'.repeat(i + 1), kind: CompletionItemKind.Text, // The icons are disabled here anyway, command: c.executeImmediately ? { id: SubmitAction.ID, title: withSlash, arguments: [{ widget, inputValue: `${withSlash} ` }] } : undefined, @@ -90,7 +95,7 @@ class SlashCommandCompletions extends Disposable { })); this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { _debugDisplayName: 'globalSlashCommandsAt', - triggerCharacters: ['@'], + triggerCharacters: [chatAgentLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); if (!widget || !widget.viewModel) { @@ -102,6 +107,11 @@ class SlashCommandCompletions extends Disposable { return null; } + if (!isEmptyUpToCompletionWord(model, range)) { + // No text allowed before the completion + return; + } + const slashCommands = this.chatSlashCommandService.getCommands(widget.location); if (!slashCommands) { return null; @@ -114,7 +124,7 @@ class SlashCommandCompletions extends Disposable { label: withSlash, insertText: c.executeImmediately ? '' : `${withSlash} `, detail: c.detail, - range: new Range(1, 1, 1, 1), + range, filterText: `${chatAgentLeader}${c.command}`, sortText: c.sortText ?? 'z'.repeat(i + 1), kind: CompletionItemKind.Text, // The icons are disabled here anyway, @@ -205,6 +215,11 @@ class AgentCompletions extends Disposable { return null; } + if (!isEmptyUpToCompletionWord(model, range)) { + // No text allowed before the completion + return; + } + const agents = this.chatAgentService.getAgents() .filter(a => a.locations.includes(widget.location)); @@ -231,7 +246,7 @@ class AgentCompletions extends Disposable { detail, filterText: `${chatAgentLeader}${agent.name}`, insertText: `${agentLabel} `, - range: new Range(1, 1, 1, 1), + range, kind: CompletionItemKind.Text, sortText: `${chatAgentLeader}${agent.name}`, command: { id: AssignSelectedAgentAction.ID, title: AssignSelectedAgentAction.ID, arguments: [{ agent, widget } satisfies AssignSelectedAgentActionArgs] }, @@ -251,7 +266,7 @@ class AgentCompletions extends Disposable { filterText: getFilterText(agent, c.name), commitCharacters: [' '], insertText: label + ' ', - range: new Range(1, 1, 1, 1), + range, kind: CompletionItemKind.Text, // The icons are disabled here anyway sortText: `x${chatAgentLeader}${agent.name}${c.name}`, command: { id: AssignSelectedAgentAction.ID, title: AssignSelectedAgentAction.ID, arguments: [{ agent, widget } satisfies AssignSelectedAgentActionArgs] }, @@ -286,6 +301,11 @@ class AgentCompletions extends Disposable { return null; } + if (!isEmptyUpToCompletionWord(model, range)) { + // No text allowed before the completion + return; + } + const agents = this.chatAgentService.getAgents() .filter(a => a.locations.includes(widget.location)); @@ -300,7 +320,7 @@ class AgentCompletions extends Disposable { commitCharacters: [' '], insertText: `${agentLabel} ${withSlash} `, detail: `(${agentLabel}) ${c.description ?? ''}`, - range: new Range(1, 1, 1, 1), + range, kind: CompletionItemKind.Text, // The icons are disabled here anyway sortText, command: { id: AssignSelectedAgentAction.ID, title: AssignSelectedAgentAction.ID, arguments: [{ agent, widget } satisfies AssignSelectedAgentActionArgs] }, @@ -333,11 +353,21 @@ class AgentCompletions extends Disposable { return; } + const range = computeCompletionRanges(model, position, /(@|\/)\w*/g); + if (!range) { + return; + } + + if (!isEmptyUpToCompletionWord(model, range)) { + // No text allowed before the completion + return; + } + const label = localize('installLabel', "Install Chat Extensions..."); const item: CompletionItem = { label, insertText: '', - range: new Range(1, 1, 1, 1), + range, kind: CompletionItemKind.Text, // The icons are disabled here anyway command: { id: 'workbench.extensions.search', title: '', arguments: ['@tag:chat-participant'] }, filterText: chatAgentLeader + label, @@ -557,7 +587,13 @@ class BuiltinDynamicCompletions extends Disposable { Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BuiltinDynamicCompletions, LifecyclePhase.Eventually); -export function computeCompletionRanges(model: ITextModel, position: Position, reg: RegExp, onlyOnWordStart = false): { insert: Range; replace: Range; varWord: IWordAtPosition | null } | undefined { +export interface IChatCompletionRangeResult { + insert: Range; + replace: Range; + varWord: IWordAtPosition | null; +} + +export function computeCompletionRanges(model: ITextModel, position: Position, reg: RegExp, onlyOnWordStart = false): IChatCompletionRangeResult | undefined { const varWord = getWordAtText(position.column, reg, model.getLineContent(position.lineNumber), 0); if (!varWord && model.getWordUntilPosition(position).word) { // inside a "normal" word @@ -591,6 +627,11 @@ export function computeCompletionRanges(model: ITextModel, position: Position, r return { insert, replace, varWord }; } +function isEmptyUpToCompletionWord(model: ITextModel, rangeResult: IChatCompletionRangeResult): boolean { + const startToCompletionWordStart = new Range(1, 1, rangeResult.replace.startLineNumber, rangeResult.replace.startColumn); + return !!model.getValueInRange(startToCompletionWordStart).match(/^\s*$/); +} + class VariableCompletions extends Disposable { private static readonly VariableNameDef = new RegExp(`(?<=^|\\s)${chatVariableLeader}\\w*`, 'g'); // MUST be using `g`-flag From 3551cb01fa6f3a838e2d160df704cc3debfb9896 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:41:23 -0700 Subject: [PATCH 073/555] edit API names and remove `findFiles2New` and `aiTextSearchProviderNew` (#232431) * remove references to findfiles2New and aiTextSearchProviderNew --- .../src/singlefolder-tests/workspace.test.ts | 10 +- .../common/extensionsApiProposals.ts | 8 +- .../workbench/api/common/extHost.api.impl.ts | 16 +- src/vs/workbench/api/common/extHostSearch.ts | 27 +-- .../workbench/api/common/extHostWorkspace.ts | 31 +-- .../api/test/browser/extHostWorkspace.test.ts | 179 +----------------- .../search/common/searchExtConversionTypes.ts | 53 +----- .../services/search/common/searchExtTypes.ts | 2 +- .../search/common/textSearchManager.ts | 4 +- .../vscode.proposed.aiTextSearchProvider.d.ts | 38 +--- ...vscode.proposed.aiTextSearchProvider2.d.ts | 40 ---- .../vscode.proposed.findFiles2.d.ts | 124 +++++++----- .../vscode.proposed.findFiles2New.d.ts | 105 ---------- .../vscode.proposed.findTextInFiles2.d.ts | 18 -- 14 files changed, 115 insertions(+), 540 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.aiTextSearchProvider2.d.ts delete mode 100644 src/vscode-dts/vscode.proposed.findFiles2New.d.ts diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 231625697053d..b871093df39b0 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -598,19 +598,19 @@ suite('vscode API - workspace', () => { }); test('`findFiles2`', () => { - return vscode.workspace.findFiles2('**/image.png').then((res) => { + return vscode.workspace.findFiles2(['**/image.png']).then((res) => { assert.strictEqual(res.length, 2); }); }); test('findFiles2 - null exclude', async () => { - await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: true, useDefaultSearchExcludes: false }).then((res) => { + await vscode.workspace.findFiles2(['**/file.txt'], { useExcludeSettings: vscode.ExcludeSettingOptions.FilesExclude }).then((res) => { // file.exclude folder is still searched, search.exclude folder is not assert.strictEqual(res.length, 1); assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt'); }); - await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: false, useDefaultSearchExcludes: false }).then((res) => { + await vscode.workspace.findFiles2(['**/file.txt'], { useExcludeSettings: vscode.ExcludeSettingOptions.None }).then((res) => { // search.exclude and files.exclude folders are both searched assert.strictEqual(res.length, 2); assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt'); @@ -618,7 +618,7 @@ suite('vscode API - workspace', () => { }); test('findFiles2, exclude', () => { - return vscode.workspace.findFiles2('**/image.png', { exclude: '**/sub/**' }).then((res) => { + return vscode.workspace.findFiles2(['**/image.png'], { exclude: ['**/sub/**'] }).then((res) => { res.forEach(r => console.log(r.toString())); assert.strictEqual(res.length, 1); }); @@ -630,7 +630,7 @@ suite('vscode API - workspace', () => { const token = source.token; // just to get an instance first source.cancel(); - return vscode.workspace.findFiles2('*.js', {}, token).then((res) => { + return vscode.workspace.findFiles2(['*.js'], {}, token).then((res) => { assert.deepStrictEqual(res, []); }); }); diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 6ea83f743ad66..672a7cf9755a2 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -14,9 +14,7 @@ const _allApiProposals = { }, aiTextSearchProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts', - }, - aiTextSearchProvider2: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider2.d.ts', + version: 2 }, attributableCoverage: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.attributableCoverage.d.ts', @@ -205,9 +203,7 @@ const _allApiProposals = { }, findFiles2: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2.d.ts', - }, - findFiles2New: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2New.d.ts', + version: 2 }, findTextInFiles: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts', diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 82b336d4c2bf9..d45dd274008a9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -961,14 +961,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // Note, undefined/null have different meanings on "exclude" return extHostWorkspace.findFiles(include, exclude, maxResults, extension.identifier, token); }, - findFiles2: (filePattern: vscode.GlobPattern, options?: vscode.FindFiles2Options, token?: vscode.CancellationToken): Thenable => { + findFiles2: (filePattern: vscode.GlobPattern[], options?: vscode.FindFiles2Options, token?: vscode.CancellationToken): Thenable => { checkProposedApiEnabled(extension, 'findFiles2'); return extHostWorkspace.findFiles2(filePattern, options, extension.identifier, token); }, - findFiles2New: (filePattern: vscode.GlobPattern[], options?: vscode.FindFiles2OptionsNew, token?: vscode.CancellationToken): Thenable => { - checkProposedApiEnabled(extension, 'findFiles2New'); - return extHostWorkspace.findFiles2New(filePattern, options, extension.identifier, token); - }, findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => { checkProposedApiEnabled(extension, 'findTextInFiles'); let options: vscode.FindTextInFilesOptions; @@ -1136,8 +1132,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerAITextSearchProvider: (scheme: string, provider: vscode.AITextSearchProvider) => { // there are some dependencies on textSearchProvider, so we need to check for both checkProposedApiEnabled(extension, 'aiTextSearchProvider'); - checkProposedApiEnabled(extension, 'textSearchProvider'); - return extHostSearch.registerAITextSearchProviderOld(scheme, provider); + checkProposedApiEnabled(extension, 'textSearchProvider2'); + return extHostSearch.registerAITextSearchProvider(scheme, provider); }, registerFileSearchProvider2: (scheme: string, provider: vscode.FileSearchProvider2) => { checkProposedApiEnabled(extension, 'fileSearchProvider2'); @@ -1147,12 +1143,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'textSearchProvider2'); return extHostSearch.registerTextSearchProvider(scheme, provider); }, - registerAITextSearchProvider2: (scheme: string, provider: vscode.AITextSearchProvider2) => { - // there are some dependencies on textSearchProvider, so we need to check for both - checkProposedApiEnabled(extension, 'aiTextSearchProvider2'); - checkProposedApiEnabled(extension, 'textSearchProvider2'); - return extHostSearch.registerAITextSearchProvider(scheme, provider); - }, registerRemoteAuthorityResolver: (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { checkProposedApiEnabled(extension, 'resolvers'); return extensionService.registerRemoteAuthorityResolver(authorityPrefix, resolver); diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts index cac2fb809143b..fb92adb1fc9fd 100644 --- a/src/vs/workbench/api/common/extHostSearch.ts +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -16,14 +16,13 @@ import { URI, UriComponents } from '../../../base/common/uri.js'; import { TextSearchManager } from '../../services/search/common/textSearchManager.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; import { revive } from '../../../base/common/marshalling.js'; -import { OldAITextSearchProviderConverter, OldFileSearchProviderConverter, OldTextSearchProviderConverter } from '../../services/search/common/searchExtConversionTypes.js'; +import { OldFileSearchProviderConverter, OldTextSearchProviderConverter } from '../../services/search/common/searchExtConversionTypes.js'; export interface IExtHostSearch extends ExtHostSearchShape { registerTextSearchProviderOld(scheme: string, provider: vscode.TextSearchProvider): IDisposable; - registerAITextSearchProviderOld(scheme: string, provider: vscode.AITextSearchProvider): IDisposable; registerFileSearchProviderOld(scheme: string, provider: vscode.FileSearchProvider): IDisposable; registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider2): IDisposable; - registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider2): IDisposable; + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider): IDisposable; registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider2): IDisposable; doInternalFileSearchWithCustomCallback(query: IFileQuery, token: CancellationToken, handleFileMatch: (data: URI[]) => void): Promise; } @@ -38,7 +37,7 @@ export class ExtHostSearch implements IExtHostSearch { private readonly _textSearchProvider = new Map(); private readonly _textSearchUsedSchemes = new Set(); - private readonly _aiTextSearchProvider = new Map(); + private readonly _aiTextSearchProvider = new Map(); private readonly _aiTextSearchUsedSchemes = new Set(); private readonly _fileSearchProvider = new Map(); @@ -88,23 +87,7 @@ export class ExtHostSearch implements IExtHostSearch { }); } - registerAITextSearchProviderOld(scheme: string, provider: vscode.AITextSearchProvider): IDisposable { - if (this._aiTextSearchUsedSchemes.has(scheme)) { - throw new Error(`an AI text search provider for the scheme '${scheme}'is already registered`); - } - - this._aiTextSearchUsedSchemes.add(scheme); - const handle = this._handlePool++; - this._aiTextSearchProvider.set(handle, new OldAITextSearchProviderConverter(provider)); - this._proxy.$registerAITextSearchProvider(handle, this._transformScheme(scheme)); - return toDisposable(() => { - this._aiTextSearchUsedSchemes.delete(scheme); - this._aiTextSearchProvider.delete(handle); - this._proxy.$unregisterProvider(handle); - }); - } - - registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider2): IDisposable { + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider): IDisposable { if (this._aiTextSearchUsedSchemes.has(scheme)) { throw new Error(`an AI text search provider for the scheme '${scheme}'is already registered`); } @@ -215,7 +198,7 @@ export class ExtHostSearch implements IExtHostSearch { }, 'textSearchProvider'); } - protected createAITextSearchManager(query: IAITextQuery, provider: vscode.AITextSearchProvider2): TextSearchManager { + protected createAITextSearchManager(query: IAITextQuery, provider: vscode.AITextSearchProvider): TextSearchManager { return new TextSearchManager({ query, provider }, { readdir: resource => Promise.resolve([]), toCanonicalName: encoding => encoding diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 2d2305b81f711..2f40bb8a39271 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -478,34 +478,9 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac }, token); } - findFiles2(filePattern: vscode.GlobPattern | undefined, - options: vscode.FindFiles2Options = {}, - extensionId: ExtensionIdentifier, - token: vscode.CancellationToken = CancellationToken.None): Promise { - this._logService.trace(`extHostWorkspace#findFiles2: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles2`); - - - const useDefaultExcludes = options.useDefaultExcludes ?? true; - const useDefaultSearchExcludes = options.useDefaultSearchExcludes ?? true; - const excludeSetting = useDefaultExcludes ? - (useDefaultSearchExcludes ? ExcludeSettingOptions.SearchAndFilesExclude : ExcludeSettingOptions.FilesExclude) : - ExcludeSettingOptions.None; - const newOptions: vscode.FindFiles2OptionsNew = { - exclude: options.exclude ? [options.exclude] : undefined, - useIgnoreFiles: { - local: options.useIgnoreFiles, - global: options.useGlobalIgnoreFiles, - parent: options.useParentIgnoreFiles - }, - useExcludeSettings: excludeSetting, - followSymlinks: options.followSymlinks, - maxResults: options.maxResults, - }; - return this._findFilesImpl(undefined, filePattern !== undefined ? [filePattern] : [], newOptions, token); - } - findFiles2New(filePatterns: vscode.GlobPattern[], - options: vscode.FindFiles2OptionsNew = {}, + findFiles2(filePatterns: vscode.GlobPattern[], + options: vscode.FindFiles2Options = {}, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findFiles2New: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles2New`); @@ -517,7 +492,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac // `filePattern` is the proper way to handle this, since it takes less precedence than the ignore files. include: vscode.GlobPattern | undefined, filePatterns: vscode.GlobPattern[] | undefined, - options: vscode.FindFiles2OptionsNew, + options: vscode.FindFiles2Options, token: vscode.CancellationToken = CancellationToken.None): Promise { if (token && token.isCancellationRequested) { return Promise.resolve([]); diff --git a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts index 37d95aec3ec8c..1c0bec661f090 100644 --- a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts @@ -716,12 +716,12 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2('foo', { maxResults: 10, useDefaultExcludes: true }, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles2(['foo'], { maxResults: 10, useExcludeSettings: ExcludeSettingOptions.FilesExclude }, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); - function testFindFiles2Include(pattern: RelativePattern) { + function testFindFiles2Include(pattern: RelativePattern[]) { const root = '/project/foo'; const rpcProtocol = new TestRPCProtocol(); @@ -745,11 +745,11 @@ suite('ExtHostWorkspace', function () { } test('RelativePattern include (string)', () => { - return testFindFiles2Include(new RelativePattern('/other/folder', 'glob/**')); + return testFindFiles2Include([new RelativePattern('/other/folder', 'glob/**')]); }); test('RelativePattern include (URI)', () => { - return testFindFiles2Include(new RelativePattern(URI.file('/other/folder'), 'glob/**')); + return testFindFiles2Include([new RelativePattern(URI.file('/other/folder'), 'glob/**')]); }); test('no excludes', () => { @@ -770,7 +770,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2(new RelativePattern('/other/folder', 'glob/**'), {}, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles2([new RelativePattern('/other/folder', 'glob/**')], {}, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); @@ -790,7 +790,7 @@ suite('ExtHostWorkspace', function () { const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); const token = CancellationToken.Cancelled; - return ws.findFiles2(new RelativePattern('/other/folder', 'glob/**'), {}, new ExtensionIdentifier('test'), token).then(() => { + return ws.findFiles2([new RelativePattern('/other/folder', 'glob/**')], {}, new ExtensionIdentifier('test'), token).then(() => { assert(!mainThreadCalled, '!mainThreadCalled'); }); }); @@ -811,7 +811,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2('', { exclude: new RelativePattern(root, 'glob/**') }, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles2([''], { exclude: [new RelativePattern(root, 'glob/**')] }, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); @@ -832,7 +832,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2('', { useIgnoreFiles: true, useParentIgnoreFiles: true, useGlobalIgnoreFiles: true }, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles2([''], { useIgnoreFiles: { local: true, parent: true, global: true } }, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); @@ -851,168 +851,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2('', { followSymlinks: true }, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - }); - }); - - suite('findFiles2New -', function () { - test('string include', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.filePattern, 'foo'); - assert.strictEqual(options.includePattern, undefined); - assert.strictEqual(_includeFolder, null); - assert.strictEqual(options.excludePattern, undefined); - assert.strictEqual(options.disregardExcludeSettings, false); - assert.strictEqual(options.maxResults, 10); - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New(['foo'], { maxResults: 10, useExcludeSettings: ExcludeSettingOptions.FilesExclude }, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - }); - - function testFindFiles2NewInclude(pattern: RelativePattern[]) { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.filePattern, 'glob/**'); - assert.strictEqual(options.includePattern, undefined); - assert.deepStrictEqual(_includeFolder ? URI.from(_includeFolder).toJSON() : null, URI.file('/other/folder').toJSON()); - assert.strictEqual(options.excludePattern, undefined); - assert.strictEqual(options.disregardExcludeSettings, false); - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New(pattern, { maxResults: 10 }, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - } - - test('RelativePattern include (string)', () => { - return testFindFiles2NewInclude([new RelativePattern('/other/folder', 'glob/**')]); - }); - - test('RelativePattern include (URI)', () => { - return testFindFiles2NewInclude([new RelativePattern(URI.file('/other/folder'), 'glob/**')]); - }); - - test('no excludes', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.filePattern, 'glob/**'); - assert.strictEqual(options.includePattern, undefined); - assert.deepStrictEqual(URI.revive(_includeFolder!).toString(), URI.file('/other/folder').toString()); - assert.strictEqual(options.excludePattern, undefined); - assert.strictEqual(options.disregardExcludeSettings, false); - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New([new RelativePattern('/other/folder', 'glob/**')], {}, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - }); - - test('with cancelled token', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - - const token = CancellationToken.Cancelled; - return ws.findFiles2New([new RelativePattern('/other/folder', 'glob/**')], {}, new ExtensionIdentifier('test'), token).then(() => { - assert(!mainThreadCalled, '!mainThreadCalled'); - }); - }); - - test('RelativePattern exclude', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.disregardExcludeSettings, false); - assert.strictEqual(options.excludePattern?.length, 1); - assert.strictEqual(options.excludePattern[0].pattern, 'glob/**'); // Note that the base portion is ignored, see #52651 - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New([''], { exclude: [new RelativePattern(root, 'glob/**')] }, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - }); - test('useIgnoreFiles', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.disregardExcludeSettings, false); - assert.strictEqual(options.disregardIgnoreFiles, false); - assert.strictEqual(options.disregardGlobalIgnoreFiles, false); - assert.strictEqual(options.disregardParentIgnoreFiles, false); - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New([''], { useIgnoreFiles: { local: true, parent: true, global: true } }, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - }); - - test('use symlinks', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.ignoreSymlinks, false); - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New([''], { followSymlinks: true }, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles2([''], { followSymlinks: true }, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); diff --git a/src/vs/workbench/services/search/common/searchExtConversionTypes.ts b/src/vs/workbench/services/search/common/searchExtConversionTypes.ts index 96bff83165652..5e96fef6d409f 100644 --- a/src/vs/workbench/services/search/common/searchExtConversionTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtConversionTypes.ts @@ -13,7 +13,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { URI } from '../../../../base/common/uri.js'; import { IProgress } from '../../../../platform/progress/common/progress.js'; import { DEFAULT_TEXT_SEARCH_PREVIEW_OPTIONS } from './search.js'; -import { Range, FileSearchProvider2, FileSearchProviderOptions, ProviderResult, TextSearchComplete2, TextSearchContext2, TextSearchMatch2, TextSearchProvider2, TextSearchProviderOptions, TextSearchQuery2, TextSearchResult2, AITextSearchProvider2, TextSearchCompleteMessage } from './searchExtTypes.js'; +import { Range, FileSearchProvider2, FileSearchProviderOptions, ProviderResult, TextSearchComplete2, TextSearchContext2, TextSearchMatch2, TextSearchProvider2, TextSearchProviderOptions, TextSearchQuery2, TextSearchResult2, TextSearchCompleteMessage } from './searchExtTypes.js'; // old types that are retained for backward compatibility // TODO: delete this when search apis are adopted by all first-party extensions @@ -372,23 +372,6 @@ export interface TextSearchProvider { */ provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: IProgress, token: CancellationToken): ProviderResult; } - -export interface AITextSearchProvider { - /** - * The name of the AI searcher. Will be displayed as `{name} Results` in the Search View. - */ - readonly name?: string; - - /** - * Provide results that match the given text pattern. - * @param query The parameter for this query. - * @param options A set of options to consider while searching. - * @param progress A progress callback that must be invoked for all results. - * @param token A cancellation token. - */ - provideAITextSearchResults(query: string, options: AITextSearchOptions, progress: IProgress, token: CancellationToken): ProviderResult; -} - /** * Options that can be set on a findTextInFiles search. */ @@ -561,40 +544,6 @@ export class OldTextSearchProviderConverter implements TextSearchProvider2 { } } -export class OldAITextSearchProviderConverter implements AITextSearchProvider2 { - public readonly name?: string; - - constructor(private provider: AITextSearchProvider) { - this.name = this.provider.name; - } - - provideAITextSearchResults(query: string, options: TextSearchProviderOptions, progress: IProgress, token: CancellationToken): ProviderResult { - const progressShim = (oldResult: TextSearchResult) => { - if (!validateProviderResult(oldResult)) { - return; - } - progress.report(oldToNewTextSearchResult(oldResult)); - }; - - const getResult = async () => { - return coalesce(await Promise.all( - newToOldTextProviderOptions(options).map( - o => this.provider.provideAITextSearchResults(query, o, { report: (e) => progressShim(e) }, token)))) - .reduce( - (prev, cur) => ({ limitHit: prev.limitHit || cur.limitHit }), - { limitHit: false } - ); - }; - const oldResult = getResult(); - return oldResult.then((e) => { - return { - limitHit: e.limitHit, - message: coalesce(asArray(e.message)) - } satisfies TextSearchComplete2; - }); - } -} - function validateProviderResult(result: TextSearchResult): boolean { if (extensionResultIsMatch(result)) { if (Array.isArray(result.ranges)) { diff --git a/src/vs/workbench/services/search/common/searchExtTypes.ts b/src/vs/workbench/services/search/common/searchExtTypes.ts index 7dd13ebc258d3..fc785ae189bce 100644 --- a/src/vs/workbench/services/search/common/searchExtTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtTypes.ts @@ -527,7 +527,7 @@ export interface TextSearchCompleteMessage { /** * An AITextSearchProvider provides additional AI text search results in the workspace. */ -export interface AITextSearchProvider2 { +export interface AITextSearchProvider { /** * The name of the AI searcher. Will be displayed as `{name} Results` in the Search View. diff --git a/src/vs/workbench/services/search/common/textSearchManager.ts b/src/vs/workbench/services/search/common/textSearchManager.ts index 5d8e858833b25..59a10ed902410 100644 --- a/src/vs/workbench/services/search/common/textSearchManager.ts +++ b/src/vs/workbench/services/search/common/textSearchManager.ts @@ -12,14 +12,14 @@ import * as resources from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; import { FolderQuerySearchTree } from './folderQuerySearchTree.js'; import { DEFAULT_MAX_SEARCH_RESULTS, hasSiblingPromiseFn, IAITextQuery, IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, excludeToGlobPattern, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, ITextSearchStats, QueryGlobTester, QueryType, resolvePatternsForProvider, ISearchRange, DEFAULT_TEXT_SEARCH_PREVIEW_OPTIONS } from './search.js'; -import { AITextSearchProvider2, TextSearchComplete2, TextSearchMatch2, TextSearchProviderFolderOptions, TextSearchProvider2, TextSearchProviderOptions, TextSearchQuery2, TextSearchResult2 } from './searchExtTypes.js'; +import { TextSearchComplete2, TextSearchMatch2, TextSearchProviderFolderOptions, TextSearchProvider2, TextSearchProviderOptions, TextSearchQuery2, TextSearchResult2, AITextSearchProvider } from './searchExtTypes.js'; export interface IFileUtils { readdir: (resource: URI) => Promise; toCanonicalName: (encoding: string) => string; } interface IAITextQueryProviderPair { - query: IAITextQuery; provider: AITextSearchProvider2; + query: IAITextQuery; provider: AITextSearchProvider; } interface ITextQueryProviderPair { diff --git a/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts b/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts index f30b87f609784..46960a0b5b1c2 100644 --- a/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts @@ -3,39 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -declare module 'vscode' { - - /** - * Options that apply to AI text search. - */ - export interface AITextSearchOptions extends SearchOptions { - /** - * The maximum number of results to be returned. - */ - maxResults: number; - - /** - * Options to specify the size of the result text preview. - */ - previewOptions?: TextSearchPreviewOptions; - - /** - * Exclude files larger than `maxFileSize` in bytes. - */ - maxFileSize?: number; - - /** - * Number of lines of context to include before each match. - */ - beforeContext?: number; - - /** - * Number of lines of context to include after each match. - */ - afterContext?: number; - } - +// version: 2 +declare module 'vscode' { /** * An AITextSearchProvider provides additional AI text search results in the workspace. */ @@ -46,14 +16,14 @@ declare module 'vscode' { readonly name?: string; /** + * * Provide results that match the given text pattern. * @param query The parameter for this query. * @param options A set of options to consider while searching. * @param progress A progress callback that must be invoked for all results. * @param token A cancellation token. */ - provideAITextSearchResults(query: string, options: AITextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult; - + provideAITextSearchResults(query: string, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): ProviderResult; } export namespace workspace { diff --git a/src/vscode-dts/vscode.proposed.aiTextSearchProvider2.d.ts b/src/vscode-dts/vscode.proposed.aiTextSearchProvider2.d.ts deleted file mode 100644 index 05c9c6b04158f..0000000000000 --- a/src/vscode-dts/vscode.proposed.aiTextSearchProvider2.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - /** - * An AITextSearchProvider provides additional AI text search results in the workspace. - */ - export interface AITextSearchProvider2 { - /** - * The name of the AI searcher. Will be displayed as `{name} Results` in the Search View. - */ - readonly name?: string; - - /** - * WARNING: VERY EXPERIMENTAL. - * - * Provide results that match the given text pattern. - * @param query The parameter for this query. - * @param options A set of options to consider while searching. - * @param progress A progress callback that must be invoked for all results. - * @param token A cancellation token. - */ - provideAITextSearchResults(query: string, options: TextSearchProviderOptions, progress: Progress, token: CancellationToken): ProviderResult; - } - - export namespace workspace { - /** - * Register an AI text search provider. - * - * Only one provider can be registered per scheme. - * - * @param scheme The provider will be invoked for workspace folders that have this file scheme. - * @param provider The provider. - * @return A {@link Disposable} that unregisters this provider when being disposed. - */ - export function registerAITextSearchProvider2(scheme: string, provider: AITextSearchProvider2): Disposable; - } -} diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts index 145776110d4f1..579ba732ac9da 100644 --- a/src/vscode-dts/vscode.proposed.findFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -3,87 +3,123 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// version: 2 + declare module 'vscode' { export interface FindFiles2Options { - // note: this is just FindTextInFilesOptions without select properties (include, previewOptions, beforeContext, afterContext) - - /** - * A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern - * will be matched against the file paths of resulting matches relative to their workspace. - */ - exclude?: GlobPattern; - /** - * Whether to use the values for files.exclude. Defaults to true. + * An array of {@link GlobPattern GlobPattern} that defines files to exclude. + * The glob patterns will be matched against the file paths of files relative to their workspace or {@link RelativePattern.baseUri} if applicable. + * + * If more than one value is used, the values are combined with a logical AND. + * For example, consider the following code: + * + * ```ts + * const ab = findFiles(['**​/*.js'], {exclude: ['*.ts', '*.js']}); + * const a = findFiles(['**​/*.js'], {exclude: ['*.ts']}); + * const b = findFiles(['**​/*.js'], {exclude: ['*.js']}); + * ``` + * + * In this, `ab` will be the intersection of results from `a` and `b`. */ - useDefaultExcludes?: boolean; + exclude?: GlobPattern[]; /** - * Whether to use the values for search.exclude. Defaults to true. Will not be followed if `useDefaultExcludes` is set to `false`. + * Which settings to follow when searching for files. Defaults to {@link ExcludeSettingOptions.searchAndFilesExclude}. */ - useDefaultSearchExcludes?: boolean; + useExcludeSettings?: ExcludeSettingOptions; /** - * The maximum number of results to search for + * The maximum number of results to search for. Defaults to 20000 results. */ maxResults?: number; /** - * Whether external files that exclude files, like .gitignore, should be respected. - * Defaults to the value for `search.useIgnoreFiles` in settings. - * For more info, see the setting listed above. + * Which file locations have ignore (`.gitignore` or `.ignore`) files to follow. + * + * When any of these fields are `undefined`, the value will either be assumed (e.g. if only one is valid), + * or it will follow settings based on the corresponding `search.use*IgnoreFiles` setting. + * + * Will log an error if an invalid combination is set. + * + * Although `.ignore` files are uncommon, they can be leveraged if there are patterns + * that should not be known to git, but should be known to the search providers. + * They should be in the same locations where `.gitignore` files are found, and they follow the same format. */ - useIgnoreFiles?: boolean; + useIgnoreFiles?: { + /** + * Use ignore files at the current workspace root. + * May default to `search.useIgnoreFiles` setting if not set. + */ + local?: boolean; + /** + * Use ignore files at the parent directory. When set to `true`, {@link FindFiles2Options.useIgnoreFiles.local} must also be `true`. + * May default to `search.useParentIgnoreFiles` setting if not set. + */ + parent?: boolean; + /** + * Use global ignore files. When set to `true`, {@link FindFiles2Options.useIgnoreFiles.local} must also be `true`. + * May default to `search.useGlobalIgnoreFiles` setting if not set. + */ + global?: boolean; + }; /** - * Whether global files that exclude files, like .gitignore, should be respected. - * Must set `useIgnoreFiles` to `true` to use this. - * Defaults to the value for `search.useGlobalIgnoreFiles` in settings. - * For more info, see the setting listed above. + * Whether symlinks should be followed while searching. + * Defaults to the value for `search.followSymlinks` in settings. + * For more info, see the setting description for `search.followSymlinks`. */ - useGlobalIgnoreFiles?: boolean; + followSymlinks?: boolean; + } + /** + * Options for following search.exclude and files.exclude settings. + */ + export enum ExcludeSettingOptions { /** - * Whether files in parent directories that exclude files, like .gitignore, should be respected. - * Must set `useIgnoreFiles` to `true` to use this. - * Defaults to the value for `search.useParentIgnoreFiles` in settings. - * For more info, see the setting listed above. + * Don't use any exclude settings. */ - useParentIgnoreFiles?: boolean; - + None = 1, /** - * Whether symlinks should be followed while searching. - * Defaults to the value for `search.followSymlinks` in settings. - * For more info, see the setting listed above. + * Use the `files.exclude` setting */ - followSymlinks?: boolean; - + FilesExclude = 2, /** - * If set to true, the `filePattern` arg will be fuzzy-searched instead of glob-searched. - * If `filePattern` is a {@link RelativePattern relative pattern}, then the fuzzy search will act on the `pattern` of the {@link RelativePattern RelativePattern} + * Use the `files.exclude` and `search.exclude` settings */ - fuzzy?: boolean; + SearchAndFilesExclude = 3 } - /** - * Represents a session of a currently logged in user. - */ export namespace workspace { /** + * WARNING: VERY EXPERIMENTAL. + * * Find files across all {@link workspace.workspaceFolders workspace folders} in the workspace. * * @example - * findFiles('**​/*.js', {exclude: '**​/out/**', useIgnoreFiles: true, maxResults: 10}) + * findFiles(['**​/*.js'], {exclude: ['**​/out/**'], useIgnoreFiles: true, maxResults: 10}) + * + * @param filePattern An array of {@link GlobPattern GlobPattern} that defines the files to search for. + * The glob patterns will be matched against the file paths of files relative to their workspace or {@link baseUri GlobPattern.baseUri} if applicable. + * Use a {@link RelativePattern RelativePatten} to restrict the search results to a {@link WorkspaceFolder workspace folder}. + * + * If more than one value is used, the values are combined with a logical OR. + * + * For example, consider the following code: + * + * ```ts + * const ab = findFiles(['*.ts', '*.js']); + * const a = findFiles(['**​/*.ts']); + * const b = findFiles(['**​/*.js']); + * ``` * - * @param filePattern A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern - * will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern} - * to restrict the search results to a {@link WorkspaceFolder workspace folder}. + * In this, `ab` will be the union of results from `a` and `b`. * @param options A set of {@link FindFiles2Options FindFiles2Options} that defines where and how to search (e.g. exclude settings). * @param token A token that can be used to signal cancellation to the underlying search engine. * @returns A thenable that resolves to an array of resource identifiers. Will return no results if no * {@link workspace.workspaceFolders workspace folders} are opened. */ - export function findFiles2(filePattern: GlobPattern, options?: FindFiles2Options, token?: CancellationToken): Thenable; + export function findFiles2(filePattern: GlobPattern[], options?: FindFiles2Options, token?: CancellationToken): Thenable; } } diff --git a/src/vscode-dts/vscode.proposed.findFiles2New.d.ts b/src/vscode-dts/vscode.proposed.findFiles2New.d.ts deleted file mode 100644 index 5ccc4e1bc3233..0000000000000 --- a/src/vscode-dts/vscode.proposed.findFiles2New.d.ts +++ /dev/null @@ -1,105 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - export interface FindFiles2OptionsNew { - /** - * An array of {@link GlobPattern GlobPattern} that defines files to exclude. - * The glob patterns will be matched against the file paths of files relative to their workspace or {@link RelativePattern.baseUri} if applicable. - * - * If more than one value is used, the values are combined with a logical AND. - * For example, consider the following code: - * - * ```ts - * const ab = findFiles(['**​/*.js'], {exclude: ['*.ts', '*.js']}); - * const a = findFiles(['**​/*.js'], {exclude: ['*.ts']}); - * const b = findFiles(['**​/*.js'], {exclude: ['*.js']}); - * ``` - * - * In this, `ab` will be the intersection of results from `a` and `b`. - */ - exclude?: GlobPattern[]; - - /** - * Which settings to follow when searching for files. Defaults to {@link ExcludeSettingOptions.searchAndFilesExclude}. - */ - useExcludeSettings?: ExcludeSettingOptions; - - /** - * The maximum number of results to search for. Defaults to 20000 results. - */ - maxResults?: number; - - /** - * Which file locations have ignore (`.gitignore` or `.ignore`) files to follow. - * - * When any of these fields are `undefined`, the value will either be assumed (e.g. if only one is valid), - * or it will follow settings based on the corresponding `search.use*IgnoreFiles` setting. - * - * Will log an error if an invalid combination is set. - * - * Although `.ignore` files are uncommon, they can be leveraged if there are patterns - * that should not be known to git, but should be known to the search providers. - * They should be in the same locations where `.gitignore` files are found, and they follow the same format. - */ - useIgnoreFiles?: { - /** - * Use ignore files at the current workspace root. - * May default to `search.useIgnoreFiles` setting if not set. - */ - local?: boolean; - /** - * Use ignore files at the parent directory. When set to `true`, {@link FindFiles2OptionsNew.useIgnoreFiles.local} must also be `true`. - * May default to `search.useParentIgnoreFiles` setting if not set. - */ - parent?: boolean; - /** - * Use global ignore files. When set to `true`, {@link FindFiles2OptionsNew.useIgnoreFiles.local} must also be `true`. - * May default to `search.useGlobalIgnoreFiles` setting if not set. - */ - global?: boolean; - }; - - /** - * Whether symlinks should be followed while searching. - * Defaults to the value for `search.followSymlinks` in settings. - * For more info, see the setting description for `search.followSymlinks`. - */ - followSymlinks?: boolean; - } - - export namespace workspace { - /** - * WARNING: VERY EXPERIMENTAL. - * - * Find files across all {@link workspace.workspaceFolders workspace folders} in the workspace. - * - * @example - * findFiles(['**​/*.js'], {exclude: ['**​/out/**'], useIgnoreFiles: true, maxResults: 10}) - * - * @param filePattern An array of {@link GlobPattern GlobPattern} that defines the files to search for. - * The glob patterns will be matched against the file paths of files relative to their workspace or {@link baseUri GlobPattern.baseUri} if applicable. - * Use a {@link RelativePattern RelativePatten} to restrict the search results to a {@link WorkspaceFolder workspace folder}. - * - * If more than one value is used, the values are combined with a logical OR. - * - * For example, consider the following code: - * - * ```ts - * const ab = findFiles(['*.ts', '*.js']); - * const a = findFiles(['**​/*.ts']); - * const b = findFiles(['**​/*.js']); - * ``` - * - * In this, `ab` will be the union of results from `a` and `b`. - * @param options A set of {@link FindFiles2Options FindFiles2Options} that defines where and how to search (e.g. exclude settings). - * @param token A token that can be used to signal cancellation to the underlying search engine. - * @returns A thenable that resolves to an array of resource identifiers. Will return no results if no - * {@link workspace.workspaceFolders workspace folders} are opened. - */ - export function findFiles2New(filePattern: GlobPattern[], options?: FindFiles2OptionsNew, token?: CancellationToken): Thenable; - } -} diff --git a/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts b/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts index 6fcec3d0a1edb..95a01a850f38c 100644 --- a/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts @@ -130,24 +130,6 @@ declare module 'vscode' { complete: Thenable; } - /** - * Options for following search.exclude and files.exclude settings. - */ - export enum ExcludeSettingOptions { - /** - * Don't use any exclude settings. - */ - None = 1, - /** - * Use the `files.exclude` setting - */ - FilesExclude = 2, - /** - * Use the `files.exclude` and `search.exclude` settings - */ - SearchAndFilesExclude = 3 - } - export namespace workspace { /** * WARNING: VERY EXPERIMENTAL. From 8f0188c73ff77ecc251a549e9af9b64837205fdd Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 30 Oct 2024 08:17:48 +0000 Subject: [PATCH 074/555] removing old issue reporter flow (#232517) * removing lot's of old issue reporter stuffs * cleanup --- build/buildfile.js | 3 - build/gulpfile.vscode.js | 7 - src/vs/code/electron-main/app.ts | 12 +- .../processExplorer/processExplorer.ts | 2 +- .../processExplorer/processExplorerMain.ts | 2 +- .../issue/common/issueReporterUtil.ts | 26 - .../issue/electron-main/issueMainService.ts | 305 ---- .../issue.ts => process/common/process.ts} | 81 -- .../electron-main/processMainService.ts | 2 +- .../issue/browser/baseIssueReporterService.ts | 3 +- .../issue/browser/issueReporterModel.ts | 3 +- .../contrib/issue/browser/issueService.ts | 2 +- .../workbench/contrib/issue/common/issue.ts | 11 - .../electron-sandbox/issue.contribution.ts | 15 +- .../electron-sandbox/issueReporter-dev.html | 46 - .../issue/electron-sandbox/issueReporter.html | 45 - .../issue/electron-sandbox/issueReporter.ts | 26 - .../electron-sandbox/issueReporterMain.ts | 57 - .../electron-sandbox/issueReporterService.ts | 1287 +---------------- .../electron-sandbox/issueReporterService2.ts | 297 ---- .../issue/electron-sandbox/issueService.ts | 125 +- .../electron-sandbox/media/issueReporter.css | 254 ++-- .../media/newIssueReporter.css | 470 ------ .../nativeIssueFormService.ts | 6 +- .../electron-sandbox/process.contribution.ts | 5 +- ...ueMainService.ts => processMainService.ts} | 3 +- .../issue/electron-sandbox/processService.ts | 2 +- 27 files changed, 226 insertions(+), 2871 deletions(-) delete mode 100644 src/vs/platform/issue/common/issueReporterUtil.ts delete mode 100644 src/vs/platform/issue/electron-main/issueMainService.ts rename src/vs/platform/{issue/common/issue.ts => process/common/process.ts} (52%) rename src/vs/platform/{issue => process}/electron-main/processMainService.ts (99%) delete mode 100644 src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.html delete mode 100644 src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.html delete mode 100644 src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.ts delete mode 100644 src/vs/workbench/contrib/issue/electron-sandbox/issueReporterMain.ts delete mode 100644 src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService2.ts delete mode 100644 src/vs/workbench/contrib/issue/electron-sandbox/media/newIssueReporter.css rename src/vs/workbench/contrib/issue/electron-sandbox/{issueMainService.ts => processMainService.ts} (76%) diff --git a/build/buildfile.js b/build/buildfile.js index 607e79e13d783..683e20fc46b79 100644 --- a/build/buildfile.js +++ b/build/buildfile.js @@ -36,7 +36,6 @@ exports.workbenchDesktop = [ createModuleDescription('vs/platform/files/node/watcher/watcherMain'), createModuleDescription('vs/platform/terminal/node/ptyHostMain'), createModuleDescription('vs/workbench/api/node/extensionHostProcess'), - createModuleDescription('vs/workbench/contrib/issue/electron-sandbox/issueReporterMain'), createModuleDescription('vs/workbench/workbench.desktop.main') ]; @@ -55,8 +54,6 @@ exports.code = [ createModuleDescription('vs/code/electron-utility/sharedProcess/sharedProcessMain'), createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain'), createModuleDescription('vs/code/electron-sandbox/workbench/workbench'), - // TODO: @justchen https://github.com/microsoft/vscode/issues/213332 make sure to remove when we use window.open on desktop. - createModuleDescription('vs/workbench/contrib/issue/electron-sandbox/issueReporter'), createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorer') ]; diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index f75608ba412a2..5dc9437bc2c10 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -101,9 +101,6 @@ const vscodeResourceIncludes = [ // Tree Sitter highlights 'out-build/vs/editor/common/languages/highlights/*.scm', - - // Issue Reporter - 'out-build/vs/workbench/contrib/issue/electron-sandbox/issueReporter.html' ]; const vscodeResources = [ @@ -144,8 +141,6 @@ const bundleVSCodeTask = task.define('bundle-vscode', task.series( fileContentMapper: filePath => { if ( filePath.endsWith('vs/code/electron-sandbox/workbench/workbench.js') || - // TODO: @justchen https://github.com/microsoft/vscode/issues/213332 make sure to remove when we use window.open on desktop - filePath.endsWith('vs/workbench/contrib/issue/electron-sandbox/issueReporter.js') || filePath.endsWith('vs/code/electron-sandbox/processExplorer/processExplorer.js')) { return async (content) => { const bootstrapWindowContent = await fs.promises.readFile(path.join(root, 'out-build', 'bootstrap-window.js'), 'utf-8'); @@ -156,8 +151,6 @@ const bundleVSCodeTask = task.define('bundle-vscode', task.series( }, skipTSBoilerplateRemoval: entryPoint => entryPoint === 'vs/code/electron-sandbox/workbench/workbench' || - // TODO: @justchen https://github.com/microsoft/vscode/issues/213332 make sure to remove when we use window.open on desktop - entryPoint === 'vs/workbench/contrib/issue/electron-sandbox/issueReporter' || entryPoint === 'vs/code/electron-sandbox/processExplorer/processExplorer', } } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index dd1e90bc65b3a..f334f01d5baa4 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -51,9 +51,8 @@ import { DiskFileSystemProvider } from '../../platform/files/node/diskFileSystem import { SyncDescriptor } from '../../platform/instantiation/common/descriptors.js'; import { IInstantiationService, ServicesAccessor } from '../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../platform/instantiation/common/serviceCollection.js'; -import { IProcessMainService, IIssueMainService } from '../../platform/issue/common/issue.js'; -import { IssueMainService } from '../../platform/issue/electron-main/issueMainService.js'; -import { ProcessMainService } from '../../platform/issue/electron-main/processMainService.js'; +import { IProcessMainService } from '../../platform/process/common/process.js'; +import { ProcessMainService } from '../../platform/process/electron-main/processMainService.js'; import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from '../../platform/keyboardLayout/electron-main/keyboardLayoutMainService.js'; import { ILaunchMainService, LaunchMainService } from '../../platform/launch/electron-main/launchMainService.js'; import { ILifecycleMainService, LifecycleMainPhase, ShutdownReason } from '../../platform/lifecycle/electron-main/lifecycleMainService.js'; @@ -1023,9 +1022,6 @@ export class CodeApplication extends Disposable { services.set(IDiagnosticsMainService, new SyncDescriptor(DiagnosticsMainService, undefined, false /* proxied to other processes */)); services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics'))))); - // Issues - services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [this.userEnv])); - // Process services.set(IProcessMainService, new SyncDescriptor(ProcessMainService, [this.userEnv])); @@ -1159,10 +1155,6 @@ export class CodeApplication extends Disposable { const updateChannel = new UpdateChannel(accessor.get(IUpdateService)); mainProcessElectronServer.registerChannel('update', updateChannel); - // Issues - const issueChannel = ProxyChannel.fromService(accessor.get(IIssueMainService), disposables); - mainProcessElectronServer.registerChannel('issue', issueChannel); - // Process const processChannel = ProxyChannel.fromService(accessor.get(IProcessMainService), disposables); mainProcessElectronServer.registerChannel('process', processChannel); diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorer.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorer.ts index 6df8c102be947..35db481251514 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorer.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer.ts @@ -9,7 +9,7 @@ type IBootstrapWindow = import('vs/platform/window/electron-sandbox/window.js').IBootstrapWindow; type IProcessExplorerMain = import('vs/code/electron-sandbox/processExplorer/processExplorerMain.js').IProcessExplorerMain; - type ProcessExplorerWindowConfiguration = import('vs/platform/issue/common/issue.js').ProcessExplorerWindowConfiguration; + type ProcessExplorerWindowConfiguration = import('vs/platform/process/common/process.js').ProcessExplorerWindowConfiguration; const bootstrapWindow: IBootstrapWindow = (window as any).MonacoBootstrapWindow; // defined by bootstrap-window.ts diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index d9f79c61fbb9f..13901ed9264c2 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -18,7 +18,7 @@ import { ipcRenderer } from '../../../base/parts/sandbox/electron-sandbox/global import { IRemoteDiagnosticError, isRemoteDiagnosticError } from '../../../platform/diagnostics/common/diagnostics.js'; import { ByteSize } from '../../../platform/files/common/files.js'; import { ElectronIPCMainProcessService } from '../../../platform/ipc/electron-sandbox/mainProcessService.js'; -import { ProcessExplorerData, ProcessExplorerStyles, ProcessExplorerWindowConfiguration } from '../../../platform/issue/common/issue.js'; +import { ProcessExplorerData, ProcessExplorerStyles, ProcessExplorerWindowConfiguration } from '../../../platform/process/common/process.js'; import { INativeHostService } from '../../../platform/native/common/native.js'; import { NativeHostService } from '../../../platform/native/common/nativeHostService.js'; import { getIconsStyleSheet } from '../../../platform/theme/browser/iconsStyleSheet.js'; diff --git a/src/vs/platform/issue/common/issueReporterUtil.ts b/src/vs/platform/issue/common/issueReporterUtil.ts deleted file mode 100644 index 3a2266e308e43..0000000000000 --- a/src/vs/platform/issue/common/issueReporterUtil.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { rtrim } from '../../../base/common/strings.js'; - -export function normalizeGitHubUrl(url: string): string { - // If the url has a .git suffix, remove it - if (url.endsWith('.git')) { - url = url.substr(0, url.length - 4); - } - - // Remove trailing slash - url = rtrim(url, '/'); - - if (url.endsWith('/new')) { - url = rtrim(url, '/new'); - } - - if (url.endsWith('/issues')) { - url = rtrim(url, '/issues'); - } - - return url; -} diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts deleted file mode 100644 index 5be83d34d2234..0000000000000 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ /dev/null @@ -1,305 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BrowserWindow, BrowserWindowConstructorOptions, Display, screen } from 'electron'; -import { arch, release, type } from 'os'; -import { raceTimeout } from '../../../base/common/async.js'; -import { CancellationTokenSource } from '../../../base/common/cancellation.js'; -import { DisposableStore } from '../../../base/common/lifecycle.js'; -import { FileAccess } from '../../../base/common/network.js'; -import { IProcessEnvironment, isMacintosh } from '../../../base/common/platform.js'; -import { validatedIpcMain } from '../../../base/parts/ipc/electron-main/ipcMain.js'; -import { getNLSLanguage, getNLSMessages, localize } from '../../../nls.js'; -import { IDialogMainService } from '../../dialogs/electron-main/dialogMainService.js'; -import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js'; -import { IIssueMainService, OldIssueReporterData, OldIssueReporterWindowConfiguration } from '../common/issue.js'; -import { ILogService } from '../../log/common/log.js'; -import { INativeHostMainService } from '../../native/electron-main/nativeHostMainService.js'; -import product from '../../product/common/product.js'; -import { IIPCObjectUrl, IProtocolMainService } from '../../protocol/electron-main/protocol.js'; -import { zoomLevelToZoomFactor } from '../../window/common/window.js'; -import { ICodeWindow, IWindowState } from '../../window/electron-main/window.js'; -import { IWindowsMainService } from '../../windows/electron-main/windows.js'; -import { ICSSDevelopmentService } from '../../cssDev/node/cssDevService.js'; - -interface IBrowserWindowOptions { - backgroundColor: string | undefined; - title: string; - zoomLevel: number; - alwaysOnTop: boolean; -} - -type IStrictWindowState = Required>; - -export class IssueMainService implements IIssueMainService { - - declare readonly _serviceBrand: undefined; - - private static readonly DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; - - private issueReporterWindow: BrowserWindow | null = null; - private issueReporterParentWindow: BrowserWindow | null = null; - - constructor( - private userEnv: IProcessEnvironment, - @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, - @ILogService private readonly logService: ILogService, - @IDialogMainService private readonly dialogMainService: IDialogMainService, - @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, - @IProtocolMainService private readonly protocolMainService: IProtocolMainService, - @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @ICSSDevelopmentService private readonly cssDevelopmentService: ICSSDevelopmentService, - ) { } - - //#region Used by renderer - - async openReporter(data: OldIssueReporterData): Promise { - if (!this.issueReporterWindow) { - this.issueReporterParentWindow = BrowserWindow.getFocusedWindow(); - if (this.issueReporterParentWindow) { - const issueReporterDisposables = new DisposableStore(); - - const issueReporterWindowConfigUrl = issueReporterDisposables.add(this.protocolMainService.createIPCObjectUrl()); - const position = this.getWindowPosition(this.issueReporterParentWindow, 700, 800); - - this.issueReporterWindow = this.createBrowserWindow(position, issueReporterWindowConfigUrl, { - backgroundColor: data.styles.backgroundColor, - title: localize('issueReporter', "Issue Reporter"), - zoomLevel: data.zoomLevel, - alwaysOnTop: false - }, 'issue-reporter'); - - // Store into config object URL - issueReporterWindowConfigUrl.update({ - appRoot: this.environmentMainService.appRoot, - windowId: this.issueReporterWindow.id, - userEnv: this.userEnv, - data, - disableExtensions: !!this.environmentMainService.disableExtensions, - os: { - type: type(), - arch: arch(), - release: release(), - }, - product, - nls: { - messages: getNLSMessages(), - language: getNLSLanguage() - }, - cssModules: this.cssDevelopmentService.isEnabled ? await this.cssDevelopmentService.getCssModules() : undefined - }); - - this.issueReporterWindow.loadURL( - FileAccess.asBrowserUri(`vs/workbench/contrib/issue/electron-sandbox/issueReporter${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true) - ); - - this.issueReporterWindow.on('close', () => { - this.issueReporterWindow = null; - issueReporterDisposables.dispose(); - }); - - this.issueReporterParentWindow.on('closed', () => { - if (this.issueReporterWindow) { - this.issueReporterWindow.close(); - this.issueReporterWindow = null; - issueReporterDisposables.dispose(); - } - }); - } - } - - else if (this.issueReporterWindow) { - this.focusWindow(this.issueReporterWindow); - } - } - - //#endregion - - //#region used by issue reporter window - async $reloadWithExtensionsDisabled(): Promise { - if (this.issueReporterParentWindow) { - try { - await this.nativeHostMainService.reload(this.issueReporterParentWindow.id, { disableExtensions: true }); - } catch (error) { - this.logService.error(error); - } - } - } - - async $showConfirmCloseDialog(): Promise { - if (this.issueReporterWindow) { - const { response } = await this.dialogMainService.showMessageBox({ - type: 'warning', - message: localize('confirmCloseIssueReporter', "Your input will not be saved. Are you sure you want to close this window?"), - buttons: [ - localize({ key: 'yes', comment: ['&& denotes a mnemonic'] }, "&&Yes"), - localize('cancel', "Cancel") - ] - }, this.issueReporterWindow); - - if (response === 0) { - if (this.issueReporterWindow) { - this.issueReporterWindow.destroy(); - this.issueReporterWindow = null; - } - } - } - } - - async $showClipboardDialog(): Promise { - if (this.issueReporterWindow) { - const { response } = await this.dialogMainService.showMessageBox({ - type: 'warning', - message: localize('issueReporterWriteToClipboard', "There is too much data to send to GitHub directly. The data will be copied to the clipboard, please paste it into the GitHub issue page that is opened."), - buttons: [ - localize({ key: 'ok', comment: ['&& denotes a mnemonic'] }, "&&OK"), - localize('cancel', "Cancel") - ] - }, this.issueReporterWindow); - - return response === 0; - } - - return false; - } - - issueReporterWindowCheck(): ICodeWindow { - if (!this.issueReporterParentWindow) { - throw new Error('Issue reporter window not available'); - } - const window = this.windowsMainService.getWindowById(this.issueReporterParentWindow.id); - if (!window) { - throw new Error('Window not found'); - } - return window; - } - - async $sendReporterMenu(extensionId: string, extensionName: string): Promise { - const window = this.issueReporterWindowCheck(); - const replyChannel = `vscode:triggerReporterMenu`; - const cts = new CancellationTokenSource(); - window.sendWhenReady(replyChannel, cts.token, { replyChannel, extensionId, extensionName }); - const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once(`vscode:triggerReporterMenuResponse:${extensionId}`, (_: unknown, data: OldIssueReporterData | undefined) => resolve(data))), 5000, () => { - this.logService.error(`Error: Extension ${extensionId} timed out waiting for menu response`); - cts.cancel(); - }); - return result as OldIssueReporterData | undefined; - } - - async $closeReporter(): Promise { - this.issueReporterWindow?.close(); - } - - //#endregion - - private focusWindow(window: BrowserWindow): void { - if (window.isMinimized()) { - window.restore(); - } - - window.focus(); - } - - private createBrowserWindow(position: IWindowState, ipcObjectUrl: IIPCObjectUrl, options: IBrowserWindowOptions, windowKind: string): BrowserWindow { - const windowOptions: BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } = { - fullscreen: false, - skipTaskbar: false, - resizable: true, - width: position.width, - height: position.height, - minWidth: 300, - minHeight: 200, - x: position.x, - y: position.y, - title: options.title, - backgroundColor: options.backgroundColor || IssueMainService.DEFAULT_BACKGROUND_COLOR, - webPreferences: { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-sandbox/preload.js').fsPath, - additionalArguments: [`--vscode-window-config=${ipcObjectUrl.resource.toString()}`], - v8CacheOptions: this.environmentMainService.useCodeCache ? 'bypassHeatCheck' : 'none', - enableWebSQL: false, - spellcheck: false, - zoomFactor: zoomLevelToZoomFactor(options.zoomLevel), - sandbox: true - }, - alwaysOnTop: options.alwaysOnTop, - experimentalDarkMode: true - }; - const window = new BrowserWindow(windowOptions); - - window.setMenuBarVisibility(false); - - return window; - } - - private getWindowPosition(parentWindow: BrowserWindow, defaultWidth: number, defaultHeight: number): IStrictWindowState { - - // We want the new window to open on the same display that the parent is in - let displayToUse: Display | undefined; - const displays = screen.getAllDisplays(); - - // Single Display - if (displays.length === 1) { - displayToUse = displays[0]; - } - - // Multi Display - else { - - // on mac there is 1 menu per window so we need to use the monitor where the cursor currently is - if (isMacintosh) { - const cursorPoint = screen.getCursorScreenPoint(); - displayToUse = screen.getDisplayNearestPoint(cursorPoint); - } - - // if we have a last active window, use that display for the new window - if (!displayToUse && parentWindow) { - displayToUse = screen.getDisplayMatching(parentWindow.getBounds()); - } - - // fallback to primary display or first display - if (!displayToUse) { - displayToUse = screen.getPrimaryDisplay() || displays[0]; - } - } - - const displayBounds = displayToUse.bounds; - - const state: IStrictWindowState = { - width: defaultWidth, - height: defaultHeight, - x: displayBounds.x + (displayBounds.width / 2) - (defaultWidth / 2), - y: displayBounds.y + (displayBounds.height / 2) - (defaultHeight / 2) - }; - - if (displayBounds.width > 0 && displayBounds.height > 0 /* Linux X11 sessions sometimes report wrong display bounds */) { - if (state.x < displayBounds.x) { - state.x = displayBounds.x; // prevent window from falling out of the screen to the left - } - - if (state.y < displayBounds.y) { - state.y = displayBounds.y; // prevent window from falling out of the screen to the top - } - - if (state.x > (displayBounds.x + displayBounds.width)) { - state.x = displayBounds.x; // prevent window from falling out of the screen to the right - } - - if (state.y > (displayBounds.y + displayBounds.height)) { - state.y = displayBounds.y; // prevent window from falling out of the screen to the bottom - } - - if (state.width > displayBounds.width) { - state.width = displayBounds.width; // prevent window from exceeding display bounds width - } - - if (state.height > displayBounds.height) { - state.height = displayBounds.height; // prevent window from exceeding display bounds height - } - } - - return state; - } -} diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/process/common/process.ts similarity index 52% rename from src/vs/platform/issue/common/issue.ts rename to src/vs/platform/process/common/process.ts index 3fc9e76e85200..50f155da42c56 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/process/common/process.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { UriComponents } from '../../../base/common/uri.js'; import { ISandboxConfiguration } from '../../../base/parts/sandbox/common/sandboxTypes.js'; import { PerformanceInfo, SystemInfo } from '../../diagnostics/common/diagnostics.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; @@ -19,68 +18,11 @@ export interface WindowData { zoomLevel: number; } -export const enum OldIssueType { - Bug, - PerformanceIssue, - FeatureRequest -} - export enum IssueSource { VSCode = 'vscode', Extension = 'extension', Marketplace = 'marketplace' } - -export interface OldIssueReporterStyles extends WindowStyles { - textLinkColor?: string; - textLinkActiveForeground?: string; - inputBackground?: string; - inputForeground?: string; - inputBorder?: string; - inputErrorBorder?: string; - inputErrorBackground?: string; - inputErrorForeground?: string; - inputActiveBorder?: string; - buttonBackground?: string; - buttonForeground?: string; - buttonHoverBackground?: string; - sliderBackgroundColor?: string; - sliderHoverColor?: string; - sliderActiveColor?: string; -} - -export interface OldIssueReporterExtensionData { - name: string; - publisher: string | undefined; - version: string; - id: string; - isTheme: boolean; - isBuiltin: boolean; - displayName: string | undefined; - repositoryUrl: string | undefined; - bugsUrl: string | undefined; - extensionData?: string; - extensionTemplate?: string; - data?: string; - uri?: UriComponents; -} - -export interface OldIssueReporterData extends WindowData { - styles: OldIssueReporterStyles; - enabledExtensions: OldIssueReporterExtensionData[]; - issueType?: OldIssueType; - issueSource?: IssueSource; - extensionId?: string; - experiments?: string; - restrictedMode: boolean; - isUnsupported: boolean; - githubAccessToken: string; - issueTitle?: string; - issueBody?: string; - data?: string; - uri?: UriComponents; -} - export interface ISettingSearchResult { extensionId: string; key: string; @@ -109,33 +51,10 @@ export interface ProcessExplorerData extends WindowData { applicationName: string; } -export interface OldIssueReporterWindowConfiguration extends ISandboxConfiguration { - disableExtensions: boolean; - data: OldIssueReporterData; - os: { - type: string; - arch: string; - release: string; - }; -} - export interface ProcessExplorerWindowConfiguration extends ISandboxConfiguration { data: ProcessExplorerData; } -export const IIssueMainService = createDecorator('issueService'); - -export interface IIssueMainService { - readonly _serviceBrand: undefined; - // Used by the issue reporter - openReporter(data: OldIssueReporterData): Promise; - $reloadWithExtensionsDisabled(): Promise; - $showConfirmCloseDialog(): Promise; - $showClipboardDialog(): Promise; - $sendReporterMenu(extensionId: string, extensionName: string): Promise; - $closeReporter(): Promise; -} - export const IProcessMainService = createDecorator('processService'); export interface IProcessMainService { diff --git a/src/vs/platform/issue/electron-main/processMainService.ts b/src/vs/platform/process/electron-main/processMainService.ts similarity index 99% rename from src/vs/platform/issue/electron-main/processMainService.ts rename to src/vs/platform/process/electron-main/processMainService.ts index bbf187a92b426..d2f6bbfd98a91 100644 --- a/src/vs/platform/issue/electron-main/processMainService.ts +++ b/src/vs/platform/process/electron-main/processMainService.ts @@ -16,7 +16,7 @@ import { IDiagnosticsMainService } from '../../diagnostics/electron-main/diagnos import { IDialogMainService } from '../../dialogs/electron-main/dialogMainService.js'; import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js'; import { ICSSDevelopmentService } from '../../cssDev/node/cssDevService.js'; -import { IProcessMainService, ProcessExplorerData, ProcessExplorerWindowConfiguration } from '../common/issue.js'; +import { IProcessMainService, ProcessExplorerData, ProcessExplorerWindowConfiguration } from '../common/process.js'; import { ILogService } from '../../log/common/log.js'; import { INativeHostMainService } from '../../native/electron-main/nativeHostMainService.js'; import product from '../../product/common/product.js'; diff --git a/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts b/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts index 0fbc0b71baa4f..26d796b87f3d8 100644 --- a/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts +++ b/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts @@ -18,7 +18,6 @@ import { escape } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; -import { OldIssueReporterData } from '../../../../platform/issue/common/issue.js'; import { getIconsStyleSheet } from '../../../../platform/theme/browser/iconsStyleSheet.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from './issueReporterModel.js'; @@ -57,7 +56,7 @@ export class BaseIssueReporterService extends Disposable { constructor( public disableExtensions: boolean, - public data: IssueReporterData | OldIssueReporterData, + public data: IssueReporterData, public os: { type: string; arch: string; diff --git a/src/vs/workbench/contrib/issue/browser/issueReporterModel.ts b/src/vs/workbench/contrib/issue/browser/issueReporterModel.ts index 97d1199d1e6c7..0bbd8acf09aeb 100644 --- a/src/vs/workbench/contrib/issue/browser/issueReporterModel.ts +++ b/src/vs/workbench/contrib/issue/browser/issueReporterModel.ts @@ -5,11 +5,10 @@ import { mainWindow } from '../../../../base/browser/window.js'; import { isRemoteDiagnosticError, SystemInfo } from '../../../../platform/diagnostics/common/diagnostics.js'; -import { OldIssueType } from '../../../../platform/issue/common/issue.js'; import { ISettingSearchResult, IssueReporterExtensionData, IssueType } from '../common/issue.js'; export interface IssueReporterData { - issueType: IssueType | OldIssueType; + issueType: IssueType; issueDescription?: string; issueTitle?: string; extensionData?: string; diff --git a/src/vs/workbench/contrib/issue/browser/issueService.ts b/src/vs/workbench/contrib/issue/browser/issueService.ts index e0df98d023534..92cdd1bee2a96 100644 --- a/src/vs/workbench/contrib/issue/browser/issueService.ts +++ b/src/vs/workbench/contrib/issue/browser/issueService.ts @@ -11,7 +11,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { ExtensionType, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { normalizeGitHubUrl } from '../../../../platform/issue/common/issueReporterUtil.js'; +import { normalizeGitHubUrl } from '../common/issueReporterUtil.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { buttonBackground, buttonForeground, buttonHoverBackground, foreground, inputActiveOptionBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textLinkActiveForeground, textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js'; import { IColorTheme, IThemeService } from '../../../../platform/theme/common/themeService.js'; diff --git a/src/vs/workbench/contrib/issue/common/issue.ts b/src/vs/workbench/contrib/issue/common/issue.ts index d0e970de5f138..f8f05bbb64d03 100644 --- a/src/vs/workbench/contrib/issue/common/issue.ts +++ b/src/vs/workbench/contrib/issue/common/issue.ts @@ -6,7 +6,6 @@ import { UriComponents } from '../../../../base/common/uri.js'; import { ISandboxConfiguration } from '../../../../base/parts/sandbox/common/sandboxTypes.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { OldIssueReporterData } from '../../../../platform/issue/common/issue.js'; // Since data sent through the service is serialized to JSON, functions will be lost, so Color objects // should not be sent as their 'toString' method will be stripped. Instead convert to strings before sending. @@ -109,16 +108,6 @@ export interface ProcessExplorerData extends WindowData { applicationName: string; } -export interface IssueReporterWindowConfiguration extends ISandboxConfiguration { - disableExtensions: boolean; - data: IssueReporterData | OldIssueReporterData; - os: { - type: string; - arch: string; - release: string; - }; -} - export interface ProcessExplorerWindowConfiguration extends ISandboxConfiguration { data: ProcessExplorerData; } diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts index 91a0f9b270655..4ff3e642e27b1 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts @@ -19,14 +19,11 @@ import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from '../.. import { IssueQuickAccess } from '../browser/issueQuickAccess.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { NativeIssueService } from './issueService.js'; -import './issueMainService.js'; +import './processMainService.js'; import '../browser/issueTroubleshoot.js'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js'; import { NativeIssueFormService } from './nativeIssueFormService.js'; - //#region Issue Contribution - registerSingleton(IWorkbenchIssueService, NativeIssueService, InstantiationType.Delayed); registerSingleton(IIssueFormService, NativeIssueFormService, InstantiationType.Delayed); @@ -57,16 +54,6 @@ class NativeIssueContribution extends BaseIssueContribution { }); }; - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - properties: { - 'issueReporter.experimental.auxWindow': { - type: 'boolean', - default: true, - description: 'Enable the new experimental issue reporter in electron.', - }, - } - }); - this._register(configurationService.onDidChangeConfiguration(e => { if (!configurationService.getValue('extensions.experimental.issueQuickAccess') && disposable) { disposable.dispose(); diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.html b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.html deleted file mode 100644 index f14661a283ce6..0000000000000 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.html b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.html deleted file mode 100644 index 2f87d2489cef9..0000000000000 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.ts deleted file mode 100644 index 629a6185ccb73..0000000000000 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/* eslint-disable no-restricted-globals */ - -(async function () { - - type IBootstrapWindow = import('vs/platform/window/electron-sandbox/window.js').IBootstrapWindow; - type IIssueReporterMain = import('vs/workbench/contrib/issue/electron-sandbox/issueReporterMain').IIssueReporterMain; - type OldIssueReporterWindowConfiguration = import('vs/platform/issue/common/issue.js').OldIssueReporterWindowConfiguration; - - const bootstrapWindow: IBootstrapWindow = (window as any).MonacoBootstrapWindow; // defined by bootstrap-window.ts - - const { result, configuration } = await bootstrapWindow.load('vs/workbench/contrib/issue/electron-sandbox/issueReporterMain', { - configureDeveloperSettings: function () { - return { - forceEnableDeveloperKeybindings: true, - disallowReloadKeybinding: true - }; - } - }); - - result.startup(configuration); -}()); diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterMain.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterMain.ts deleted file mode 100644 index fa1628e3be887..0000000000000 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterMain.ts +++ /dev/null @@ -1,57 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { safeInnerHtml } from '../../../../base/browser/dom.js'; -import '../../../../base/browser/ui/codicons/codiconStyles.js'; // make sure codicon css is loaded -import { mainWindow } from '../../../../base/browser/window.js'; -import { isLinux, isWindows } from '../../../../base/common/platform.js'; -import './media/issueReporter.css'; -import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; -import { getSingletonServiceDescriptors } from '../../../../platform/instantiation/common/extensions.js'; -import { InstantiationService } from '../../../../platform/instantiation/common/instantiationService.js'; -import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; -import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js'; -import { ElectronIPCMainProcessService } from '../../../../platform/ipc/electron-sandbox/mainProcessService.js'; -import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; -import { INativeHostService } from '../../../../platform/native/common/native.js'; -import { NativeHostService } from '../../../../platform/native/common/nativeHostService.js'; -import BaseHtml from '../browser/issueReporterPage.js'; -import { IProcessMainService, IIssueMainService, OldIssueReporterWindowConfiguration } from '../../../../platform/issue/common/issue.js'; -import { IssueReporter } from './issueReporterService.js'; - -export interface IIssueReporterMain { - startup(configuration: OldIssueReporterWindowConfiguration): void; -} - -export function startup(configuration: OldIssueReporterWindowConfiguration): void { - const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac'; - mainWindow.document.body.classList.add(platformClass); // used by our fonts - - safeInnerHtml(mainWindow.document.body, BaseHtml()); - - const instantiationService = initServices(configuration.windowId); - - const issueReporter = instantiationService.createInstance(IssueReporter, configuration); - issueReporter.render(); - mainWindow.document.body.style.display = 'block'; - issueReporter.setInitialFocus(); -} - -function initServices(windowId: number) { - const services = new ServiceCollection(); - - const contributedServices = getSingletonServiceDescriptors(); - for (const [id, descriptor] of contributedServices) { - services.set(id, descriptor); - } - - services.set(IMainProcessService, new SyncDescriptor(ElectronIPCMainProcessService, [windowId])); - services.set(INativeHostService, new SyncDescriptor(NativeHostService, [windowId])); - - return new InstantiationService(services, true); -} - -registerMainProcessRemoteService(IIssueMainService, 'issue'); -registerMainProcessRemoteService(IProcessMainService, 'process'); diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts index bc6f5eb9de27d..5232574fab6ef 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts @@ -2,112 +2,44 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, createStyleSheet, isHTMLInputElement, isHTMLTextAreaElement, reset, windowOpenNoOpener } from '../../../../base/browser/dom.js'; -import { Button, unthemedButtonStyles } from '../../../../base/browser/ui/button/button.js'; -import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; -import { mainWindow } from '../../../../base/browser/window.js'; -import { Delayer, RunOnceScheduler } from '../../../../base/common/async.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { groupBy } from '../../../../base/common/collections.js'; -import { debounce } from '../../../../base/common/decorators.js'; +import { $, reset } from '../../../../base/browser/dom.js'; import { CancellationError } from '../../../../base/common/errors.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { isLinuxSnap, isMacintosh } from '../../../../base/common/platform.js'; -import { escape } from '../../../../base/common/strings.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; +import { IProductConfiguration } from '../../../../base/common/product.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; import { isRemoteDiagnosticError } from '../../../../platform/diagnostics/common/diagnostics.js'; -import { IIssueMainService, IProcessMainService, OldIssueReporterData, OldIssueReporterExtensionData, OldIssueReporterStyles, OldIssueReporterWindowConfiguration, OldIssueType } from '../../../../platform/issue/common/issue.js'; +import { IProcessMainService } from '../../../../platform/process/common/process.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; -import { getIconsStyleSheet } from '../../../../platform/theme/browser/iconsStyleSheet.js'; -import { applyZoom, zoomIn, zoomOut } from '../../../../platform/window/electron-sandbox/window.js'; -import { IssueReporterData, IssueReporterModel, IssueReporterData as IssueReporterModelData } from '../browser/issueReporterModel.js'; -import { normalizeGitHubUrl } from '../common/issueReporterUtil.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { applyZoom } from '../../../../platform/window/electron-sandbox/window.js'; +import { BaseIssueReporterService } from '../browser/baseIssueReporterService.js'; +import { IssueReporterData as IssueReporterModelData } from '../browser/issueReporterModel.js'; +import { IIssueFormService, IssueReporterData, IssueType } from '../common/issue.js'; // GitHub has let us know that we could up our limit here to 8k. We chose 7500 to play it safe. // ref https://github.com/microsoft/vscode/issues/159191 const MAX_URL_LENGTH = 7500; -interface SearchResult { - html_url: string; - title: string; - state?: string; -} - -enum IssueSource { - VSCode = 'vscode', - Extension = 'extension', - Marketplace = 'marketplace', - Unknown = 'unknown' -} - -export class IssueReporter extends Disposable { - private readonly issueReporterModel: IssueReporterModel; - private numberOfSearchResultsDisplayed = 0; - private receivedSystemInfo = false; - private receivedPerformanceInfo = false; - private shouldQueueSearch = false; - private hasBeenSubmitted = false; - private openReporter = false; - private loadingExtensionData = false; - private selectedExtension = ''; - private delayedSubmit = new Delayer(300); - private readonly previewButton!: Button; - private nonGitHubIssueUrl = false; +export class IssueReporter extends BaseIssueReporterService { + private readonly processMainService: IProcessMainService; constructor( - private readonly configuration: OldIssueReporterWindowConfiguration, + disableExtensions: boolean, + data: IssueReporterData, + os: { + type: string; + arch: string; + release: string; + }, + product: IProductConfiguration, + window: Window, @INativeHostService private readonly nativeHostService: INativeHostService, - @IIssueMainService private readonly issueMainService: IIssueMainService, - @IProcessMainService private readonly processMainService: IProcessMainService + @IIssueFormService issueFormService: IIssueFormService, + @IProcessMainService processMainService: IProcessMainService, + @IThemeService themeService: IThemeService ) { - super(); - const targetExtension = configuration.data.extensionId ? configuration.data.enabledExtensions.find(extension => extension.id.toLocaleLowerCase() === configuration.data.extensionId?.toLocaleLowerCase()) : undefined; - this.issueReporterModel = new IssueReporterModel({ - ...configuration.data, - issueType: configuration.data.issueType || OldIssueType.Bug, - versionInfo: { - vscodeVersion: `${configuration.product.nameShort} ${!!configuration.product.darwinUniversalAssetId ? `${configuration.product.version} (Universal)` : configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`, - os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${isLinuxSnap ? ' snap' : ''}` - }, - extensionsDisabled: !!configuration.disableExtensions, - fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined, - selectedExtension: targetExtension - }); - - const fileOnMarketplace = configuration.data.issueSource === IssueSource.Marketplace; - const fileOnProduct = configuration.data.issueSource === IssueSource.VSCode; - this.issueReporterModel.update({ fileOnMarketplace, fileOnProduct }); - - //TODO: Handle case where extension is not activated - const issueReporterElement = this.getElementById('issue-reporter'); - if (issueReporterElement) { - this.previewButton = new Button(issueReporterElement, unthemedButtonStyles); - const issueRepoName = document.createElement('a'); - issueReporterElement.appendChild(issueRepoName); - issueRepoName.id = 'show-repo-name'; - issueRepoName.classList.add('hidden'); - this.updatePreviewButtonState(); - } - - const issueTitle = configuration.data.issueTitle; - if (issueTitle) { - const issueTitleElement = this.getElementById('issue-title'); - if (issueTitleElement) { - issueTitleElement.value = issueTitle; - } - } - - const issueBody = configuration.data.issueBody; - if (issueBody) { - const description = this.getElementById('description'); - if (description) { - description.value = issueBody; - this.issueReporterModel.update({ issueDescription: issueBody }); - } - } - + super(disableExtensions, data, os, product, window, false, issueFormService, themeService); + this.processMainService = processMainService; this.processMainService.$getSystemInfo().then(info => { this.issueReporterModel.update({ systemInfo: info }); this.receivedSystemInfo = true; @@ -115,178 +47,26 @@ export class IssueReporter extends Disposable { this.updateSystemInfo(this.issueReporterModel.getData()); this.updatePreviewButtonState(); }); - if (configuration.data.issueType === OldIssueType.PerformanceIssue) { + if (this.data.issueType === IssueType.PerformanceIssue) { this.processMainService.$getPerformanceInfo().then(info => { this.updatePerformanceInfo(info as Partial); }); } - if (mainWindow.document.documentElement.lang !== 'en') { - show(this.getElementById('english')); - } - - const codiconStyleSheet = createStyleSheet(); - codiconStyleSheet.id = 'codiconStyles'; - - // TODO: Is there a way to use the IThemeService here instead - const iconsStyleSheet = this._register(getIconsStyleSheet(undefined)); - function updateAll() { - codiconStyleSheet.textContent = iconsStyleSheet.getCSS(); - } - - const delayer = new RunOnceScheduler(updateAll, 0); - iconsStyleSheet.onDidChange(() => delayer.schedule()); - delayer.schedule(); - - this.setUpTypes(); this.setEventHandlers(); - applyZoom(configuration.data.zoomLevel, mainWindow); - this.applyStyles(configuration.data.styles); - this.handleExtensionData(configuration.data.enabledExtensions); - this.updateExperimentsInfo(configuration.data.experiments); - this.updateRestrictedMode(configuration.data.restrictedMode); - this.updateUnsupportedMode(configuration.data.isUnsupported); - - // Handle case where extension is pre-selected through the command - if ((configuration.data.data || configuration.data.uri) && targetExtension) { - this.updateExtensionStatus(targetExtension); - } - } - - render(): void { - this.renderBlocks(); - } - - setInitialFocus() { - const { fileOnExtension } = this.issueReporterModel.getData(); - if (fileOnExtension) { - const issueTitle = mainWindow.document.getElementById('issue-title'); - issueTitle?.focus(); - } else { - const issueType = mainWindow.document.getElementById('issue-type'); - issueType?.focus(); - } - } - - // TODO @justschen: After migration to Aux Window, switch to dedicated css. - private applyStyles(styles: OldIssueReporterStyles) { - const styleTag = document.createElement('style'); - const content: string[] = []; - - if (styles.inputBackground) { - content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { background-color: ${styles.inputBackground}; }`); - } - - if (styles.inputBorder) { - content.push(`input[type="text"], textarea, select { border: 1px solid ${styles.inputBorder}; }`); - } else { - content.push(`input[type="text"], textarea, select { border: 1px solid transparent; }`); - } - - if (styles.inputForeground) { - content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { color: ${styles.inputForeground}; }`); - } - - if (styles.inputErrorBorder) { - content.push(`.invalid-input, .invalid-input:focus, .validation-error { border: 1px solid ${styles.inputErrorBorder} !important; }`); - content.push(`.required-input { color: ${styles.inputErrorBorder}; }`); - } - - if (styles.inputErrorBackground) { - content.push(`.validation-error { background: ${styles.inputErrorBackground}; }`); - } - - if (styles.inputErrorForeground) { - content.push(`.validation-error { color: ${styles.inputErrorForeground}; }`); - } - - if (styles.inputActiveBorder) { - content.push(`input[type='text']:focus, textarea:focus, select:focus, summary:focus, button:focus, a:focus, .workbenchCommand:focus { border: 1px solid ${styles.inputActiveBorder}; outline-style: none; }`); - } - - if (styles.textLinkColor) { - content.push(`a, .workbenchCommand { color: ${styles.textLinkColor}; }`); - } - - if (styles.textLinkColor) { - content.push(`a { color: ${styles.textLinkColor}; }`); - } - - if (styles.textLinkActiveForeground) { - content.push(`a:hover, .workbenchCommand:hover { color: ${styles.textLinkActiveForeground}; }`); - } - - if (styles.sliderBackgroundColor) { - content.push(`::-webkit-scrollbar-thumb { background-color: ${styles.sliderBackgroundColor}; }`); - } - - if (styles.sliderActiveColor) { - content.push(`::-webkit-scrollbar-thumb:active { background-color: ${styles.sliderActiveColor}; }`); - } - - if (styles.sliderHoverColor) { - content.push(`::--webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`); - } - - if (styles.buttonBackground) { - content.push(`.monaco-text-button { background-color: ${styles.buttonBackground} !important; }`); - } - - if (styles.buttonForeground) { - content.push(`.monaco-text-button { color: ${styles.buttonForeground} !important; }`); - } - - if (styles.buttonHoverBackground) { - content.push(`.monaco-text-button:not(.disabled):hover, .monaco-text-button:focus { background-color: ${styles.buttonHoverBackground} !important; }`); - } - - styleTag.textContent = content.join('\n'); - mainWindow.document.head.appendChild(styleTag); - mainWindow.document.body.style.color = styles.color || ''; - } - - private handleExtensionData(extensions: OldIssueReporterExtensionData[]) { - const installedExtensions = extensions.filter(x => !x.isBuiltin); - const { nonThemes, themes } = groupBy(installedExtensions, ext => { - return ext.isTheme ? 'themes' : 'nonThemes'; - }); - - const numberOfThemeExtesions = themes && themes.length; - this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); - this.updateExtensionTable(nonThemes, numberOfThemeExtesions); - if (this.configuration.disableExtensions || installedExtensions.length === 0) { - (this.getElementById('disableExtensions')).disabled = true; - } - - this.updateExtensionSelector(installedExtensions); - } - - private async updateIssueReporterUri(extension: OldIssueReporterExtensionData): Promise { - try { - if (extension.uri) { - const uri = URI.revive(extension.uri); - extension.bugsUrl = uri.toString(); - } - } catch (e) { - this.renderBlocks(); - } + applyZoom(this.data.zoomLevel, this.window); + this.updateExperimentsInfo(this.data.experiments); + this.updateRestrictedMode(this.data.restrictedMode); + this.updateUnsupportedMode(this.data.isUnsupported); } - private async sendReporterMenu(extension: OldIssueReporterExtensionData): Promise { - try { - const data = await this.issueMainService.$sendReporterMenu(extension.id, extension.name); - return data; - } catch (e) { - console.error(e); - return undefined; - } - } + public override setEventHandlers(): void { + super.setEventHandlers(); - private setEventHandlers(): void { this.addEventListener('issue-type', 'change', (event: Event) => { const issueType = parseInt((event.target).value); this.issueReporterModel.update({ issueType: issueType }); - if (issueType === OldIssueType.PerformanceIssue && !this.receivedPerformanceInfo) { + if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) { this.processMainService.$getPerformanceInfo().then(info => { this.updatePerformanceInfo(info as Partial); }); @@ -302,630 +82,9 @@ export class IssueReporter extends Disposable { this.setSourceOptions(); this.render(); }); - - (['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeExperiments', 'includeExtensionData'] as const).forEach(elementId => { - this.addEventListener(elementId, 'click', (event: Event) => { - event.stopPropagation(); - this.issueReporterModel.update({ [elementId]: !this.issueReporterModel.getData()[elementId] }); - }); - }); - - const showInfoElements = mainWindow.document.getElementsByClassName('showInfo'); - for (let i = 0; i < showInfoElements.length; i++) { - const showInfo = showInfoElements.item(i)!; - (showInfo as HTMLAnchorElement).addEventListener('click', (e: MouseEvent) => { - e.preventDefault(); - const label = (e.target); - if (label) { - const containingElement = label.parentElement && label.parentElement.parentElement; - const info = containingElement && containingElement.lastElementChild; - if (info && info.classList.contains('hidden')) { - show(info); - label.textContent = localize('hide', "hide"); - } else { - hide(info); - label.textContent = localize('show', "show"); - } - } - }); - } - - this.addEventListener('issue-source', 'change', (e: Event) => { - const value = (e.target).value; - const problemSourceHelpText = this.getElementById('problem-source-help-text')!; - if (value === '') { - this.issueReporterModel.update({ fileOnExtension: undefined }); - show(problemSourceHelpText); - this.clearSearchResults(); - this.render(); - return; - } else { - hide(problemSourceHelpText); - } - - const descriptionTextArea = this.getElementById('issue-title'); - if (value === IssueSource.VSCode) { - descriptionTextArea.placeholder = localize('vscodePlaceholder', "E.g Workbench is missing problems panel"); - } else if (value === IssueSource.Extension) { - descriptionTextArea.placeholder = localize('extensionPlaceholder', "E.g. Missing alt text on extension readme image"); - } else if (value === IssueSource.Marketplace) { - descriptionTextArea.placeholder = localize('marketplacePlaceholder', "E.g Cannot disable installed extension"); - } else { - descriptionTextArea.placeholder = localize('undefinedPlaceholder', "Please enter a title"); - } - - let fileOnExtension, fileOnMarketplace = false; - if (value === IssueSource.Extension) { - fileOnExtension = true; - } else if (value === IssueSource.Marketplace) { - fileOnMarketplace = true; - } - - this.issueReporterModel.update({ fileOnExtension, fileOnMarketplace }); - this.render(); - - const title = (this.getElementById('issue-title')).value; - this.searchIssues(title, fileOnExtension, fileOnMarketplace); - }); - - this.addEventListener('description', 'input', (e: Event) => { - const issueDescription = (e.target).value; - this.issueReporterModel.update({ issueDescription }); - - // Only search for extension issues on title change - if (this.issueReporterModel.fileOnExtension() === false) { - const title = (this.getElementById('issue-title')).value; - this.searchVSCodeIssues(title, issueDescription); - } - }); - - this.addEventListener('issue-title', 'input', (e: Event) => { - const title = (e.target).value; - const lengthValidationMessage = this.getElementById('issue-title-length-validation-error'); - const issueUrl = this.getIssueUrl(); - if (title && this.getIssueUrlWithTitle(title, issueUrl).length > MAX_URL_LENGTH) { - show(lengthValidationMessage); - } else { - hide(lengthValidationMessage); - } - const issueSource = this.getElementById('issue-source'); - if (!issueSource || issueSource.value === '') { - return; - } - - const { fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData(); - this.searchIssues(title, fileOnExtension, fileOnMarketplace); - }); - - this.previewButton.onDidClick(async () => { - this.delayedSubmit.trigger(async () => { - this.createIssue(); - }); - }); - - this.addEventListener('disableExtensions', 'click', () => { - this.issueMainService.$reloadWithExtensionsDisabled(); - }); - - this.addEventListener('extensionBugsLink', 'click', (e: Event) => { - const url = (e.target).innerText; - windowOpenNoOpener(url); - }); - - this.addEventListener('disableExtensions', 'keydown', (e: Event) => { - e.stopPropagation(); - if ((e as KeyboardEvent).keyCode === 13 || (e as KeyboardEvent).keyCode === 32) { - this.issueMainService.$reloadWithExtensionsDisabled(); - } - }); - - mainWindow.document.onkeydown = async (e: KeyboardEvent) => { - const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey; - // Cmd/Ctrl+Enter previews issue and closes window - if (cmdOrCtrlKey && e.keyCode === 13) { - this.delayedSubmit.trigger(async () => { - if (await this.createIssue()) { - this.close(); - } - }); - } - - // Cmd/Ctrl + w closes issue window - if (cmdOrCtrlKey && e.keyCode === 87) { - e.stopPropagation(); - e.preventDefault(); - - const issueTitle = (this.getElementById('issue-title'))!.value; - const { issueDescription } = this.issueReporterModel.getData(); - if (!this.hasBeenSubmitted && (issueTitle || issueDescription)) { - // fire and forget - this.issueMainService.$showConfirmCloseDialog(); - } else { - this.close(); - } - } - - // Cmd/Ctrl + zooms in - if (cmdOrCtrlKey && e.keyCode === 187) { - zoomIn(mainWindow); - } - - // Cmd/Ctrl - zooms out - if (cmdOrCtrlKey && e.keyCode === 189) { - zoomOut(mainWindow); - } - - // With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac - // Manually perform the selection - if (isMacintosh) { - if (cmdOrCtrlKey && e.keyCode === 65 && e.target) { - if (isHTMLInputElement(e.target) || isHTMLTextAreaElement(e.target)) { - (e.target).select(); - } - } - } - }; - } - - private updatePerformanceInfo(info: Partial) { - this.issueReporterModel.update(info); - this.receivedPerformanceInfo = true; - - const state = this.issueReporterModel.getData(); - this.updateProcessInfo(state); - this.updateWorkspaceInfo(state); - this.updatePreviewButtonState(); - } - - private updatePreviewButtonState() { - if (this.isPreviewEnabled()) { - if (this.configuration.data.githubAccessToken) { - this.previewButton.label = localize('createOnGitHub', "Create on GitHub"); - } else { - this.previewButton.label = localize('previewOnGitHub', "Preview on GitHub"); - } - this.previewButton.enabled = true; - } else { - this.previewButton.enabled = false; - this.previewButton.label = localize('loadingData', "Loading data..."); - } - - const issueRepoName = this.getElementById('show-repo-name')! as HTMLAnchorElement; - const selectedExtension = this.issueReporterModel.getData().selectedExtension; - if (selectedExtension && selectedExtension.uri) { - const urlString = URI.revive(selectedExtension.uri).toString(); - issueRepoName.href = urlString; - issueRepoName.addEventListener('click', (e) => this.openLink(e)); - issueRepoName.addEventListener('auxclick', (e) => this.openLink(e)); - const gitHubInfo = this.parseGitHubUrl(urlString); - issueRepoName.textContent = gitHubInfo ? gitHubInfo.owner + '/' + gitHubInfo.repositoryName : urlString; - Object.assign(issueRepoName.style, { - alignSelf: 'flex-end', - display: 'block', - fontSize: '13px', - marginBottom: '10px', - padding: '4px 0px', - textDecoration: 'none', - width: 'auto' - }); - show(issueRepoName); - } else { - // clear styles - issueRepoName.removeAttribute('style'); - hide(issueRepoName); - } - - // Initial check when first opened. - this.getExtensionGitHubUrl(); - } - - private isPreviewEnabled() { - const issueType = this.issueReporterModel.getData().issueType; - - if (this.loadingExtensionData) { - return false; - } - - if (issueType === OldIssueType.Bug && this.receivedSystemInfo) { - return true; - } - - if (issueType === OldIssueType.PerformanceIssue && this.receivedSystemInfo && this.receivedPerformanceInfo) { - return true; - } - - if (issueType === OldIssueType.FeatureRequest) { - return true; - } - - return false; - } - - private getExtensionRepositoryUrl(): string | undefined { - const selectedExtension = this.issueReporterModel.getData().selectedExtension; - return selectedExtension && selectedExtension.repositoryUrl; - } - - private getExtensionBugsUrl(): string | undefined { - const selectedExtension = this.issueReporterModel.getData().selectedExtension; - return selectedExtension && selectedExtension.bugsUrl; - } - - private searchVSCodeIssues(title: string, issueDescription?: string): void { - if (title) { - this.searchDuplicates(title, issueDescription); - } else { - this.clearSearchResults(); - } - } - - private searchIssues(title: string, fileOnExtension: boolean | undefined, fileOnMarketplace: boolean | undefined): void { - if (fileOnExtension) { - return this.searchExtensionIssues(title); - } - - if (fileOnMarketplace) { - return this.searchMarketplaceIssues(title); - } - - const description = this.issueReporterModel.getData().issueDescription; - this.searchVSCodeIssues(title, description); - } - - private searchExtensionIssues(title: string): void { - const url = this.getExtensionGitHubUrl(); - if (title) { - const matches = /^https?:\/\/github\.com\/(.*)/.exec(url); - if (matches && matches.length) { - const repo = matches[1]; - return this.searchGitHub(repo, title); - } - - // If the extension has no repository, display empty search results - if (this.issueReporterModel.getData().selectedExtension) { - this.clearSearchResults(); - return this.displaySearchResults([]); - - } - } - - this.clearSearchResults(); - } - - private searchMarketplaceIssues(title: string): void { - if (title) { - const gitHubInfo = this.parseGitHubUrl(this.configuration.product.reportMarketplaceIssueUrl!); - if (gitHubInfo) { - return this.searchGitHub(`${gitHubInfo.owner}/${gitHubInfo.repositoryName}`, title); - } - } - } - - private async close(): Promise { - await this.issueMainService.$closeReporter(); - } - - private clearSearchResults(): void { - const similarIssues = this.getElementById('similar-issues')!; - similarIssues.innerText = ''; - this.numberOfSearchResultsDisplayed = 0; - } - - @debounce(300) - private searchGitHub(repo: string, title: string): void { - const query = `is:issue+repo:${repo}+${title}`; - const similarIssues = this.getElementById('similar-issues')!; - - fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => { - response.json().then(result => { - similarIssues.innerText = ''; - if (result && result.items) { - this.displaySearchResults(result.items); - } else { - // If the items property isn't present, the rate limit has been hit - const message = $('div.list-title'); - message.textContent = localize('rateLimited', "GitHub query limit exceeded. Please wait."); - similarIssues.appendChild(message); - - const resetTime = response.headers.get('X-RateLimit-Reset'); - const timeToWait = resetTime ? parseInt(resetTime) - Math.floor(Date.now() / 1000) : 1; - if (this.shouldQueueSearch) { - this.shouldQueueSearch = false; - setTimeout(() => { - this.searchGitHub(repo, title); - this.shouldQueueSearch = true; - }, timeToWait * 1000); - } - } - }).catch(_ => { - // Ignore - }); - }).catch(_ => { - // Ignore - }); - } - - @debounce(300) - private searchDuplicates(title: string, body?: string): void { - const url = 'https://vscode-probot.westus.cloudapp.azure.com:7890/duplicate_candidates'; - const init = { - method: 'POST', - body: JSON.stringify({ - title, - body - }), - headers: new Headers({ - 'Content-Type': 'application/json' - }) - }; - - fetch(url, init).then((response) => { - response.json().then(result => { - this.clearSearchResults(); - - if (result && result.candidates) { - this.displaySearchResults(result.candidates); - } else { - throw new Error('Unexpected response, no candidates property'); - } - }).catch(_ => { - // Ignore - }); - }).catch(_ => { - // Ignore - }); - } - - private displaySearchResults(results: SearchResult[]) { - const similarIssues = this.getElementById('similar-issues')!; - if (results.length) { - const issues = $('div.issues-container'); - const issuesText = $('div.list-title'); - issuesText.textContent = localize('similarIssues', "Similar issues"); - - this.numberOfSearchResultsDisplayed = results.length < 5 ? results.length : 5; - for (let i = 0; i < this.numberOfSearchResultsDisplayed; i++) { - const issue = results[i]; - const link = $('a.issue-link', { href: issue.html_url }); - link.textContent = issue.title; - link.title = issue.title; - link.addEventListener('click', (e) => this.openLink(e)); - link.addEventListener('auxclick', (e) => this.openLink(e)); - - let issueState: HTMLElement; - let item: HTMLElement; - if (issue.state) { - issueState = $('span.issue-state'); - - const issueIcon = $('span.issue-icon'); - issueIcon.appendChild(renderIcon(issue.state === 'open' ? Codicon.issueOpened : Codicon.issueClosed)); - - const issueStateLabel = $('span.issue-state.label'); - issueStateLabel.textContent = issue.state === 'open' ? localize('open', "Open") : localize('closed', "Closed"); - - issueState.title = issue.state === 'open' ? localize('open', "Open") : localize('closed', "Closed"); - issueState.appendChild(issueIcon); - issueState.appendChild(issueStateLabel); - - item = $('div.issue', undefined, issueState, link); - } else { - item = $('div.issue', undefined, link); - } - - issues.appendChild(item); - } - - similarIssues.appendChild(issuesText); - similarIssues.appendChild(issues); - } else { - const message = $('div.list-title'); - message.textContent = localize('noSimilarIssues', "No similar issues found"); - similarIssues.appendChild(message); - } - } - - private setUpTypes(): void { - const makeOption = (issueType: OldIssueType, description: string) => $('option', { 'value': issueType.valueOf() }, escape(description)); - - const typeSelect = this.getElementById('issue-type')! as HTMLSelectElement; - const { issueType } = this.issueReporterModel.getData(); - reset(typeSelect, - makeOption(OldIssueType.Bug, localize('bugReporter', "Bug Report")), - makeOption(OldIssueType.FeatureRequest, localize('featureRequest', "Feature Request")), - makeOption(OldIssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue (freeze, slow, crash)")) - ); - - typeSelect.value = issueType.toString(); - - this.setSourceOptions(); - } - - private makeOption(value: string, description: string, disabled: boolean): HTMLOptionElement { - const option: HTMLOptionElement = document.createElement('option'); - option.disabled = disabled; - option.value = value; - option.textContent = description; - - return option; - } - - private setSourceOptions(): void { - const sourceSelect = this.getElementById('issue-source')! as HTMLSelectElement; - const { issueType, fileOnExtension, selectedExtension, fileOnMarketplace, fileOnProduct } = this.issueReporterModel.getData(); - let selected = sourceSelect.selectedIndex; - if (selected === -1) { - if (fileOnExtension !== undefined) { - selected = fileOnExtension ? 2 : 1; - } else if (selectedExtension?.isBuiltin) { - selected = 1; - } else if (fileOnMarketplace) { - selected = 3; - } else if (fileOnProduct) { - selected = 1; - } - } - - sourceSelect.innerText = ''; - sourceSelect.append(this.makeOption('', localize('selectSource', "Select source"), true)); - sourceSelect.append(this.makeOption(IssueSource.VSCode, localize('vscode', "Visual Studio Code"), false)); - sourceSelect.append(this.makeOption(IssueSource.Extension, localize('extension', "A VS Code extension"), false)); - if (this.configuration.product.reportMarketplaceIssueUrl) { - sourceSelect.append(this.makeOption(IssueSource.Marketplace, localize('marketplace', "Extensions Marketplace"), false)); - } - - if (issueType !== OldIssueType.FeatureRequest) { - sourceSelect.append(this.makeOption(IssueSource.Unknown, localize('unknown', "Don't know"), false)); - } - - if (selected !== -1 && selected < sourceSelect.options.length) { - sourceSelect.selectedIndex = selected; - } else { - sourceSelect.selectedIndex = 0; - hide(this.getElementById('problem-source-help-text')); - } - } - - private renderBlocks(): void { - // Depending on Issue Type, we render different blocks and text - const { issueType, fileOnExtension, fileOnMarketplace, selectedExtension } = this.issueReporterModel.getData(); - const blockContainer = this.getElementById('block-container'); - const systemBlock = mainWindow.document.querySelector('.block-system'); - const processBlock = mainWindow.document.querySelector('.block-process'); - const workspaceBlock = mainWindow.document.querySelector('.block-workspace'); - const extensionsBlock = mainWindow.document.querySelector('.block-extensions'); - const experimentsBlock = mainWindow.document.querySelector('.block-experiments'); - const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data'); - - const problemSource = this.getElementById('problem-source')!; - const descriptionTitle = this.getElementById('issue-description-label')!; - const descriptionSubtitle = this.getElementById('issue-description-subtitle')!; - const extensionSelector = this.getElementById('extension-selection')!; - - const titleTextArea = this.getElementById('issue-title-container')!; - const descriptionTextArea = this.getElementById('description')!; - const extensionDataTextArea = this.getElementById('extension-data')!; - - // Hide all by default - hide(blockContainer); - hide(systemBlock); - hide(processBlock); - hide(workspaceBlock); - hide(extensionsBlock); - hide(experimentsBlock); - hide(extensionSelector); - hide(extensionDataTextArea); - hide(extensionDataBlock); - - show(problemSource); - show(titleTextArea); - show(descriptionTextArea); - - if (fileOnExtension) { - show(extensionSelector); - } - - - if (selectedExtension && this.nonGitHubIssueUrl) { - hide(titleTextArea); - hide(descriptionTextArea); - reset(descriptionTitle, localize('handlesIssuesElsewhere', "This extension handles issues outside of VS Code")); - reset(descriptionSubtitle, localize('elsewhereDescription', "The '{0}' extension prefers to use an external issue reporter. To be taken to that issue reporting experience, click the button below.", selectedExtension.displayName)); - this.previewButton.label = localize('openIssueReporter', "Open External Issue Reporter"); - return; - } - - if (fileOnExtension && selectedExtension?.data) { - const data = selectedExtension?.data; - (extensionDataTextArea as HTMLElement).innerText = data.toString(); - (extensionDataTextArea as HTMLTextAreaElement).readOnly = true; - show(extensionDataBlock); - } - - // only if we know comes from the open reporter command - if (fileOnExtension && this.openReporter) { - (extensionDataTextArea as HTMLTextAreaElement).readOnly = true; - setTimeout(() => { - // delay to make sure from command or not - if (this.openReporter) { - show(extensionDataBlock); - } - }, 100); - } - - if (issueType === OldIssueType.Bug) { - if (!fileOnMarketplace) { - show(blockContainer); - show(systemBlock); - show(experimentsBlock); - if (!fileOnExtension) { - show(extensionsBlock); - } - } - - reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce") + ' ', $('span.required-input', undefined, '*')); - reset(descriptionSubtitle, localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); - } else if (issueType === OldIssueType.PerformanceIssue) { - if (!fileOnMarketplace) { - show(blockContainer); - show(systemBlock); - show(processBlock); - show(workspaceBlock); - show(experimentsBlock); - } - - if (fileOnExtension) { - show(extensionSelector); - } else if (!fileOnMarketplace) { - show(extensionsBlock); - } - - reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce") + ' ', $('span.required-input', undefined, '*')); - reset(descriptionSubtitle, localize('performanceIssueDesciption', "When did this performance issue happen? Does it occur on startup or after a specific series of actions? We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); - } else if (issueType === OldIssueType.FeatureRequest) { - reset(descriptionTitle, localize('description', "Description") + ' ', $('span.required-input', undefined, '*')); - reset(descriptionSubtitle, localize('featureRequestDescription', "Please describe the feature you would like to see. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); - } - } - - private validateInput(inputId: string): boolean { - const inputElement = (this.getElementById(inputId)); - const inputValidationMessage = this.getElementById(`${inputId}-empty-error`); - const descriptionShortMessage = this.getElementById(`description-short-error`); - if (!inputElement.value) { - inputElement.classList.add('invalid-input'); - inputValidationMessage?.classList.remove('hidden'); - descriptionShortMessage?.classList.add('hidden'); - return false; - } else if (inputId === 'description' && inputElement.value.length < 10) { - inputElement.classList.add('invalid-input'); - descriptionShortMessage?.classList.remove('hidden'); - inputValidationMessage?.classList.add('hidden'); - return false; - } - else { - inputElement.classList.remove('invalid-input'); - inputValidationMessage?.classList.add('hidden'); - if (inputId === 'description') { - descriptionShortMessage?.classList.add('hidden'); - } - return true; - } - } - - private validateInputs(): boolean { - let isValid = true; - ['issue-title', 'description', 'issue-source'].forEach(elementId => { - isValid = this.validateInput(elementId) && isValid; - }); - - if (this.issueReporterModel.fileOnExtension()) { - isValid = this.validateInput('extension-selector') && isValid; - } - - return isValid; } - private async submitToGitHub(issueTitle: string, issueBody: string, gitHubDetails: { owner: string; repositoryName: string }): Promise { + public override async submitToGitHub(issueTitle: string, issueBody: string, gitHubDetails: { owner: string; repositoryName: string }): Promise { const url = `https://api.github.com/repos/${gitHubDetails.owner}/${gitHubDetails.repositoryName}/issues`; const init = { method: 'POST', @@ -935,7 +94,7 @@ export class IssueReporter extends Disposable { }), headers: new Headers({ 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.configuration.data.githubAccessToken}` + 'Authorization': `Bearer ${this.data.githubAccessToken}` }) }; @@ -950,7 +109,7 @@ export class IssueReporter extends Disposable { return true; } - private async createIssue(): Promise { + public override async createIssue(): Promise { const selectedExtension = this.issueReporterModel.getData().selectedExtension; const hasUri = this.nonGitHubIssueUrl; // Short circuit if the extension provides a custom issue handler @@ -966,7 +125,7 @@ export class IssueReporter extends Disposable { if (!this.validateInputs()) { // If inputs are invalid, set focus to the first one and add listeners on them // to detect further changes - const invalidInput = mainWindow.document.getElementsByClassName('invalid-input'); + const invalidInput = this.window.document.getElementsByClassName('invalid-input'); if (invalidInput.length) { (invalidInput[0]).focus(); } @@ -986,6 +145,7 @@ export class IssueReporter extends Disposable { if (this.issueReporterModel.fileOnExtension()) { this.addEventListener('extension-selector', 'change', _ => { this.validateInput('extension-selector'); + this.validateInput('description'); }); } @@ -1020,7 +180,7 @@ export class IssueReporter extends Disposable { console.error('Writing to clipboard failed'); return false; } - } else if (this.configuration.data.githubAccessToken && gitHubDetails) { + } else if (this.data.githubAccessToken && gitHubDetails) { return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); } @@ -1028,8 +188,8 @@ export class IssueReporter extends Disposable { return true; } - private async writeToClipboard(baseUrl: string, issueBody: string): Promise { - const shouldWrite = await this.issueMainService.$showClipboardDialog(); + public override async writeToClipboard(baseUrl: string, issueBody: string): Promise { + const shouldWrite = await this.issueFormService.showClipboardDialog(); if (!shouldWrite) { throw new CancellationError(); } @@ -1039,60 +199,8 @@ export class IssueReporter extends Disposable { return baseUrl + `&body=${encodeURIComponent(localize('pasteData', "We have written the needed data into your clipboard because it was too large to send. Please paste."))}`; } - private getIssueUrl(): string { - return this.issueReporterModel.fileOnExtension() - ? this.getExtensionGitHubUrl() - : this.issueReporterModel.getData().fileOnMarketplace - ? this.configuration.product.reportMarketplaceIssueUrl! - : this.configuration.product.reportIssueUrl!; - } - - private parseGitHubUrl(url: string): undefined | { repositoryName: string; owner: string } { - // Assumes a GitHub url to a particular repo, https://github.com/repositoryName/owner. - // Repository name and owner cannot contain '/' - const match = /^https?:\/\/github\.com\/([^\/]*)\/([^\/]*).*/.exec(url); - if (match && match.length) { - return { - owner: match[1], - repositoryName: match[2] - }; - } else { - console.error('No GitHub issues match'); - } - - return undefined; - } - - private getExtensionGitHubUrl(): string { - let repositoryUrl = ''; - const bugsUrl = this.getExtensionBugsUrl(); - const extensionUrl = this.getExtensionRepositoryUrl(); - // If given, try to match the extension's bug url - if (bugsUrl && bugsUrl.match(/^https?:\/\/github\.com\/([^\/]*)\/([^\/]*)\/?(\/issues)?$/)) { - // matches exactly: https://github.com/owner/repo/issues - repositoryUrl = normalizeGitHubUrl(bugsUrl); - } else if (extensionUrl && extensionUrl.match(/^https?:\/\/github\.com\/([^\/]*)\/([^\/]*)$/)) { - // matches exactly: https://github.com/owner/repo - repositoryUrl = normalizeGitHubUrl(extensionUrl); - } else { - this.nonGitHubIssueUrl = true; - repositoryUrl = bugsUrl || extensionUrl || ''; - } - - return repositoryUrl; - } - - private getIssueUrlWithTitle(issueTitle: string, repositoryUrl: string): string { - if (this.issueReporterModel.fileOnExtension()) { - repositoryUrl = repositoryUrl + '/issues/new'; - } - - const queryStringPrefix = repositoryUrl.indexOf('?') === -1 ? '?' : '&'; - return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`; - } - private updateSystemInfo(state: IssueReporterModelData) { - const target = mainWindow.document.querySelector('.block-system .block-info'); + const target = this.window.document.querySelector('.block-system .block-info'); if (target) { const systemInfo = state.systemInfo!; @@ -1171,270 +279,6 @@ export class IssueReporter extends Disposable { } } - private updateExtensionSelector(extensions: OldIssueReporterExtensionData[]): void { - interface IOption { - name: string; - id: string; - } - - const extensionOptions: IOption[] = extensions.map(extension => { - return { - name: extension.displayName || extension.name || '', - id: extension.id - }; - }); - - // Sort extensions by name - extensionOptions.sort((a, b) => { - const aName = a.name.toLowerCase(); - const bName = b.name.toLowerCase(); - if (aName > bName) { - return 1; - } - - if (aName < bName) { - return -1; - } - - return 0; - }); - - const makeOption = (extension: IOption, selectedExtension?: OldIssueReporterExtensionData): HTMLOptionElement => { - const selected = selectedExtension && extension.id === selectedExtension.id; - return $('option', { - 'value': extension.id, - 'selected': selected || '' - }, extension.name); - }; - - const extensionsSelector = this.getElementById('extension-selector'); - if (extensionsSelector) { - const { selectedExtension } = this.issueReporterModel.getData(); - reset(extensionsSelector, this.makeOption('', localize('selectExtension', "Select extension"), true), ...extensionOptions.map(extension => makeOption(extension, selectedExtension))); - - if (!selectedExtension) { - extensionsSelector.selectedIndex = 0; - } - - this.addEventListener('extension-selector', 'change', async (e: Event) => { - this.clearExtensionData(); - const selectedExtensionId = (e.target).value; - this.selectedExtension = selectedExtensionId; - const extensions = this.issueReporterModel.getData().allExtensions; - const matches = extensions.filter(extension => extension.id === selectedExtensionId); - if (matches.length) { - this.issueReporterModel.update({ selectedExtension: matches[0] }); - const selectedExtension = this.issueReporterModel.getData().selectedExtension; - if (selectedExtension) { - const iconElement = document.createElement('span'); - iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); - this.setLoading(iconElement); - const openReporterData = await this.sendReporterMenu(selectedExtension); - if (openReporterData) { - if (this.selectedExtension === selectedExtensionId) { - this.removeLoading(iconElement, true); - this.configuration.data = openReporterData; - } else if (this.selectedExtension !== selectedExtensionId) { - } - } - else { - if (!this.loadingExtensionData) { - iconElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); - } - this.removeLoading(iconElement); - // if not using command, should have no configuration data in fields we care about and check later. - this.clearExtensionData(); - - // case when previous extension was opened from normal openIssueReporter command - selectedExtension.data = undefined; - selectedExtension.uri = undefined; - } - if (this.selectedExtension === selectedExtensionId) { - // repopulates the fields with the new data given the selected extension. - this.updateExtensionStatus(matches[0]); - this.openReporter = false; - } - } else { - this.issueReporterModel.update({ selectedExtension: undefined }); - this.clearSearchResults(); - this.clearExtensionData(); - this.validateSelectedExtension(); - this.updateExtensionStatus(matches[0]); - } - } - }); - } - - this.addEventListener('problem-source', 'change', (_) => { - this.validateSelectedExtension(); - }); - } - - private clearExtensionData(): void { - this.nonGitHubIssueUrl = false; - this.issueReporterModel.update({ extensionData: undefined }); - this.configuration.data.issueBody = undefined; - this.configuration.data.data = undefined; - this.configuration.data.uri = undefined; - } - - private async updateExtensionStatus(extension: OldIssueReporterExtensionData) { - this.issueReporterModel.update({ selectedExtension: extension }); - - // uses this.configuuration.data to ensure that data is coming from `openReporter` command. - const template = this.configuration.data.issueBody; - if (template) { - const descriptionTextArea = this.getElementById('description')!; - const descriptionText = (descriptionTextArea as HTMLTextAreaElement).value; - if (descriptionText === '' || !descriptionText.includes(template.toString())) { - const fullTextArea = descriptionText + (descriptionText === '' ? '' : '\n') + template.toString(); - (descriptionTextArea as HTMLTextAreaElement).value = fullTextArea; - this.issueReporterModel.update({ issueDescription: fullTextArea }); - } - } - - const data = this.configuration.data.data; - if (data) { - this.issueReporterModel.update({ extensionData: data }); - extension.data = data; - const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!; - show(extensionDataBlock); - this.renderBlocks(); - } - - const uri = this.configuration.data.uri; - if (uri) { - extension.uri = uri; - this.updateIssueReporterUri(extension); - } - - this.validateSelectedExtension(); - const title = (this.getElementById('issue-title')).value; - this.searchExtensionIssues(title); - - this.updatePreviewButtonState(); - this.renderBlocks(); - } - - private validateSelectedExtension(): void { - const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!; - const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!; - hide(extensionValidationMessage); - hide(extensionValidationNoUrlsMessage); - - const extension = this.issueReporterModel.getData().selectedExtension; - if (!extension) { - this.previewButton.enabled = true; - return; - } - - if (this.loadingExtensionData) { - return; - } - - const hasValidGitHubUrl = this.getExtensionGitHubUrl(); - if (hasValidGitHubUrl) { - this.previewButton.enabled = true; - } else { - this.setExtensionValidationMessage(); - this.previewButton.enabled = false; - } - } - - private setLoading(element: HTMLElement) { - // Show loading - this.openReporter = true; - this.loadingExtensionData = true; - this.updatePreviewButtonState(); - - const extensionDataCaption = this.getElementById('extension-id')!; - hide(extensionDataCaption); - - const extensionDataCaption2 = Array.from(mainWindow.document.querySelectorAll('.ext-parens')); - extensionDataCaption2.forEach(extensionDataCaption2 => hide(extensionDataCaption2)); - - const showLoading = this.getElementById('ext-loading')!; - show(showLoading); - while (showLoading.firstChild) { - showLoading.firstChild.remove(); - } - showLoading.append(element); - - this.renderBlocks(); - } - - private removeLoading(element: HTMLElement, fromReporter: boolean = false) { - this.openReporter = fromReporter; - this.loadingExtensionData = false; - this.updatePreviewButtonState(); - - const extensionDataCaption = this.getElementById('extension-id')!; - show(extensionDataCaption); - - const extensionDataCaption2 = Array.from(mainWindow.document.querySelectorAll('.ext-parens')); - extensionDataCaption2.forEach(extensionDataCaption2 => show(extensionDataCaption2)); - - const hideLoading = this.getElementById('ext-loading')!; - hide(hideLoading); - if (hideLoading.firstChild) { - element.remove(); - } - this.renderBlocks(); - } - - private setExtensionValidationMessage(): void { - const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!; - const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!; - const bugsUrl = this.getExtensionBugsUrl(); - if (bugsUrl) { - show(extensionValidationMessage); - const link = this.getElementById('extensionBugsLink')!; - link.textContent = bugsUrl; - return; - } - - const extensionUrl = this.getExtensionRepositoryUrl(); - if (extensionUrl) { - show(extensionValidationMessage); - const link = this.getElementById('extensionBugsLink'); - link!.textContent = extensionUrl; - return; - } - - show(extensionValidationNoUrlsMessage); - } - - private updateProcessInfo(state: IssueReporterModelData) { - const target = mainWindow.document.querySelector('.block-process .block-info') as HTMLElement; - if (target) { - reset(target, $('code', undefined, state.processInfo ?? '')); - } - } - - private updateWorkspaceInfo(state: IssueReporterModelData) { - mainWindow.document.querySelector('.block-workspace .block-info code')!.textContent = '\n' + state.workspaceInfo; - } - - private updateExtensionTable(extensions: OldIssueReporterExtensionData[], numThemeExtensions: number): void { - const target = mainWindow.document.querySelector('.block-extensions .block-info'); - if (target) { - if (this.configuration.disableExtensions) { - reset(target, localize('disabledExtensions', "Extensions are disabled")); - return; - } - - const themeExclusionStr = numThemeExtensions ? `\n(${numThemeExtensions} theme extensions excluded)` : ''; - extensions = extensions || []; - - if (!extensions.length) { - target.innerText = 'Extensions: none' + themeExclusionStr; - return; - } - - reset(target, this.getExtensionTableHtml(extensions), document.createTextNode(themeExclusionStr)); - } - } - private updateRestrictedMode(restrictedMode: boolean) { this.issueReporterModel.update({ restrictedMode }); } @@ -1445,56 +289,9 @@ export class IssueReporter extends Disposable { private updateExperimentsInfo(experimentInfo: string | undefined) { this.issueReporterModel.update({ experimentInfo }); - const target = mainWindow.document.querySelector('.block-experiments .block-info'); + const target = this.window.document.querySelector('.block-experiments .block-info'); if (target) { target.textContent = experimentInfo ? experimentInfo : localize('noCurrentExperiments', "No current experiments."); } } - - private getExtensionTableHtml(extensions: OldIssueReporterExtensionData[]): HTMLTableElement { - return $('table', undefined, - $('tr', undefined, - $('th', undefined, 'Extension'), - $('th', undefined, 'Author (truncated)' as string), - $('th', undefined, 'Version') - ), - ...extensions.map(extension => $('tr', undefined, - $('td', undefined, extension.name), - $('td', undefined, extension.publisher?.substr(0, 3) ?? 'N/A'), - $('td', undefined, extension.version) - )) - ); - } - - private openLink(event: MouseEvent): void { - event.preventDefault(); - event.stopPropagation(); - // Exclude right click - if (event.which < 3) { - windowOpenNoOpener((event.target).href); - } - } - - private getElementById(elementId: string): T | undefined { - const element = mainWindow.document.getElementById(elementId) as T | undefined; - if (element) { - return element; - } else { - return undefined; - } - } - - private addEventListener(elementId: string, eventType: string, handler: (event: Event) => void): void { - const element = this.getElementById(elementId); - element?.addEventListener(eventType, handler); - } -} - -// helper functions - -export function hide(el: Element | undefined | null) { - el?.classList.add('hidden'); -} -export function show(el: Element | undefined | null) { - el?.classList.remove('hidden'); } diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService2.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService2.ts deleted file mode 100644 index 305ee9a7bc95c..0000000000000 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService2.ts +++ /dev/null @@ -1,297 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { $, reset } from '../../../../base/browser/dom.js'; -import { CancellationError } from '../../../../base/common/errors.js'; -import { IProductConfiguration } from '../../../../base/common/product.js'; -import { URI } from '../../../../base/common/uri.js'; -import { localize } from '../../../../nls.js'; -import { isRemoteDiagnosticError } from '../../../../platform/diagnostics/common/diagnostics.js'; -import { IProcessMainService } from '../../../../platform/issue/common/issue.js'; -import { INativeHostService } from '../../../../platform/native/common/native.js'; -import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { applyZoom } from '../../../../platform/window/electron-sandbox/window.js'; -import { BaseIssueReporterService } from '../browser/baseIssueReporterService.js'; -import { IssueReporterData as IssueReporterModelData } from '../browser/issueReporterModel.js'; -import { IIssueFormService, IssueReporterData, IssueType } from '../common/issue.js'; - -// GitHub has let us know that we could up our limit here to 8k. We chose 7500 to play it safe. -// ref https://github.com/microsoft/vscode/issues/159191 -const MAX_URL_LENGTH = 7500; - - -export class IssueReporter2 extends BaseIssueReporterService { - private readonly processMainService: IProcessMainService; - constructor( - disableExtensions: boolean, - data: IssueReporterData, - os: { - type: string; - arch: string; - release: string; - }, - product: IProductConfiguration, - window: Window, - @INativeHostService private readonly nativeHostService: INativeHostService, - @IIssueFormService issueFormService: IIssueFormService, - @IProcessMainService processMainService: IProcessMainService, - @IThemeService themeService: IThemeService - ) { - super(disableExtensions, data, os, product, window, false, issueFormService, themeService); - this.processMainService = processMainService; - this.processMainService.$getSystemInfo().then(info => { - this.issueReporterModel.update({ systemInfo: info }); - this.receivedSystemInfo = true; - - this.updateSystemInfo(this.issueReporterModel.getData()); - this.updatePreviewButtonState(); - }); - if (this.data.issueType === IssueType.PerformanceIssue) { - this.processMainService.$getPerformanceInfo().then(info => { - this.updatePerformanceInfo(info as Partial); - }); - } - - this.setEventHandlers(); - applyZoom(this.data.zoomLevel, this.window); - this.updateExperimentsInfo(this.data.experiments); - this.updateRestrictedMode(this.data.restrictedMode); - this.updateUnsupportedMode(this.data.isUnsupported); - } - - public override setEventHandlers(): void { - super.setEventHandlers(); - - this.addEventListener('issue-type', 'change', (event: Event) => { - const issueType = parseInt((event.target).value); - this.issueReporterModel.update({ issueType: issueType }); - if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) { - this.processMainService.$getPerformanceInfo().then(info => { - this.updatePerformanceInfo(info as Partial); - }); - } - - // Resets placeholder - const descriptionTextArea = this.getElementById('issue-title'); - if (descriptionTextArea) { - descriptionTextArea.placeholder = localize('undefinedPlaceholder', "Please enter a title"); - } - - this.updatePreviewButtonState(); - this.setSourceOptions(); - this.render(); - }); - } - - public override async submitToGitHub(issueTitle: string, issueBody: string, gitHubDetails: { owner: string; repositoryName: string }): Promise { - const url = `https://api.github.com/repos/${gitHubDetails.owner}/${gitHubDetails.repositoryName}/issues`; - const init = { - method: 'POST', - body: JSON.stringify({ - title: issueTitle, - body: issueBody - }), - headers: new Headers({ - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.data.githubAccessToken}` - }) - }; - - const response = await fetch(url, init); - if (!response.ok) { - console.error('Invalid GitHub URL provided.'); - return false; - } - const result = await response.json(); - await this.nativeHostService.openExternal(result.html_url); - this.close(); - return true; - } - - public override async createIssue(): Promise { - const selectedExtension = this.issueReporterModel.getData().selectedExtension; - const hasUri = this.nonGitHubIssueUrl; - // Short circuit if the extension provides a custom issue handler - if (hasUri) { - const url = this.getExtensionBugsUrl(); - if (url) { - this.hasBeenSubmitted = true; - await this.nativeHostService.openExternal(url); - return true; - } - } - - if (!this.validateInputs()) { - // If inputs are invalid, set focus to the first one and add listeners on them - // to detect further changes - const invalidInput = this.window.document.getElementsByClassName('invalid-input'); - if (invalidInput.length) { - (invalidInput[0]).focus(); - } - - this.addEventListener('issue-title', 'input', _ => { - this.validateInput('issue-title'); - }); - - this.addEventListener('description', 'input', _ => { - this.validateInput('description'); - }); - - this.addEventListener('issue-source', 'change', _ => { - this.validateInput('issue-source'); - }); - - if (this.issueReporterModel.fileOnExtension()) { - this.addEventListener('extension-selector', 'change', _ => { - this.validateInput('extension-selector'); - this.validateInput('description'); - }); - } - - return false; - } - - this.hasBeenSubmitted = true; - - const issueTitle = (this.getElementById('issue-title')).value; - const issueBody = this.issueReporterModel.serialize(); - - let issueUrl = this.getIssueUrl(); - if (!issueUrl) { - console.error('No issue url found'); - return false; - } - - if (selectedExtension?.uri) { - const uri = URI.revive(selectedExtension.uri); - issueUrl = uri.toString(); - } - - const gitHubDetails = this.parseGitHubUrl(issueUrl); - - const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value, issueUrl); - let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`; - - if (url.length > MAX_URL_LENGTH) { - try { - url = await this.writeToClipboard(baseUrl, issueBody); - } catch (_) { - console.error('Writing to clipboard failed'); - return false; - } - } else if (this.data.githubAccessToken && gitHubDetails) { - return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); - } - - await this.nativeHostService.openExternal(url); - return true; - } - - public override async writeToClipboard(baseUrl: string, issueBody: string): Promise { - const shouldWrite = await this.issueFormService.showClipboardDialog(); - if (!shouldWrite) { - throw new CancellationError(); - } - - await this.nativeHostService.writeClipboardText(issueBody); - - return baseUrl + `&body=${encodeURIComponent(localize('pasteData', "We have written the needed data into your clipboard because it was too large to send. Please paste."))}`; - } - - private updateSystemInfo(state: IssueReporterModelData) { - const target = this.window.document.querySelector('.block-system .block-info'); - - if (target) { - const systemInfo = state.systemInfo!; - const renderedDataTable = $('table', undefined, - $('tr', undefined, - $('td', undefined, 'CPUs'), - $('td', undefined, systemInfo.cpus || '') - ), - $('tr', undefined, - $('td', undefined, 'GPU Status' as string), - $('td', undefined, Object.keys(systemInfo.gpuStatus).map(key => `${key}: ${systemInfo.gpuStatus[key]}`).join('\n')) - ), - $('tr', undefined, - $('td', undefined, 'Load (avg)' as string), - $('td', undefined, systemInfo.load || '') - ), - $('tr', undefined, - $('td', undefined, 'Memory (System)' as string), - $('td', undefined, systemInfo.memory) - ), - $('tr', undefined, - $('td', undefined, 'Process Argv' as string), - $('td', undefined, systemInfo.processArgs) - ), - $('tr', undefined, - $('td', undefined, 'Screen Reader' as string), - $('td', undefined, systemInfo.screenReader) - ), - $('tr', undefined, - $('td', undefined, 'VM'), - $('td', undefined, systemInfo.vmHint) - ) - ); - reset(target, renderedDataTable); - - systemInfo.remoteData.forEach(remote => { - target.appendChild($('hr')); - if (isRemoteDiagnosticError(remote)) { - const remoteDataTable = $('table', undefined, - $('tr', undefined, - $('td', undefined, 'Remote'), - $('td', undefined, remote.hostName) - ), - $('tr', undefined, - $('td', undefined, ''), - $('td', undefined, remote.errorMessage) - ) - ); - target.appendChild(remoteDataTable); - } else { - const remoteDataTable = $('table', undefined, - $('tr', undefined, - $('td', undefined, 'Remote'), - $('td', undefined, remote.latency ? `${remote.hostName} (latency: ${remote.latency.current.toFixed(2)}ms last, ${remote.latency.average.toFixed(2)}ms average)` : remote.hostName) - ), - $('tr', undefined, - $('td', undefined, 'OS'), - $('td', undefined, remote.machineInfo.os) - ), - $('tr', undefined, - $('td', undefined, 'CPUs'), - $('td', undefined, remote.machineInfo.cpus || '') - ), - $('tr', undefined, - $('td', undefined, 'Memory (System)' as string), - $('td', undefined, remote.machineInfo.memory) - ), - $('tr', undefined, - $('td', undefined, 'VM'), - $('td', undefined, remote.machineInfo.vmHint) - ) - ); - target.appendChild(remoteDataTable); - } - }); - } - } - - private updateRestrictedMode(restrictedMode: boolean) { - this.issueReporterModel.update({ restrictedMode }); - } - - private updateUnsupportedMode(isUnsupported: boolean) { - this.issueReporterModel.update({ isUnsupported }); - } - - private updateExperimentsInfo(experimentInfo: string | undefined) { - this.issueReporterModel.update({ experimentInfo }); - const target = this.window.document.querySelector('.block-experiments .block-info'); - if (target) { - target.textContent = experimentInfo ? experimentInfo : localize('noCurrentExperiments', "No current experiments."); - } - } -} diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts index 2ee3df7cf706e..7587d24af5be7 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts @@ -5,15 +5,10 @@ import { getZoomLevel } from '../../../../base/browser/browser.js'; import { mainWindow } from '../../../../base/browser/window.js'; -import { ipcRenderer } from '../../../../base/parts/sandbox/electron-sandbox/globals.js'; -import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; -import { ExtensionIdentifier, ExtensionIdentifierSet, ExtensionType } from '../../../../platform/extensions/common/extensions.js'; +import { ExtensionType } from '../../../../platform/extensions/common/extensions.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { IIssueMainService, OldIssueReporterData, OldIssueReporterExtensionData, OldIssueReporterStyles } from '../../../../platform/issue/common/issue.js'; -import { buttonBackground, buttonForeground, buttonHoverBackground, foreground, inputActiveOptionBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textLinkActiveForeground, textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js'; +import { buttonBackground, buttonForeground, buttonHoverBackground, foreground, inputActiveOptionBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, scrollbarSliderActiveBackground, scrollbarSliderHoverBackground, textLinkActiveForeground, textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js'; import { IColorTheme, IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js'; import { SIDE_BAR_BACKGROUND } from '../../../common/theme.js'; @@ -25,10 +20,8 @@ import { IIntegrityService } from '../../../services/integrity/common/integrity. export class NativeIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; - private extensionIdentifierSet: ExtensionIdentifierSet = new ExtensionIdentifierSet(); constructor( - @IIssueMainService private readonly issueMainService: IIssueMainService, @IIssueFormService private readonly issueFormService: IIssueFormService, @IThemeService private readonly themeService: IThemeService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @@ -37,39 +30,10 @@ export class NativeIssueService implements IWorkbenchIssueService { @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IIntegrityService private readonly integrityService: IIntegrityService, - @IMenuService private readonly menuService: IMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - ipcRenderer.on('vscode:triggerReporterMenu', async (event, arg) => { - const extensionId = arg.extensionId; - - // gets menu from contributed - const actions = this.menuService.getMenuActions(MenuId.IssueReporter, this.contextKeyService, { renderShortTitle: true }).flatMap(entry => entry[1]); - - // render menu and dispose - actions.forEach(async action => { - try { - if (action.item && 'source' in action.item && action.item.source?.id === extensionId) { - this.extensionIdentifierSet.add(extensionId); - await action.run(); - } - } catch (error) { - console.error(error); - } - }); - - if (!this.extensionIdentifierSet.has(extensionId)) { - // send undefined to indicate no action was taken - ipcRenderer.send(`vscode:triggerReporterMenuResponse:${extensionId}`, undefined); - } - }); - } + ) { } async openReporter(dataOverrides: Partial = {}): Promise { const extensionData: IssueReporterExtensionData[] = []; - const oldExtensionData: OldIssueReporterExtensionData[] = []; - const oldDataOverrides = dataOverrides as Partial; try { const extensions = await this.extensionManagementService.getInstalled(); const enabledExtensions = extensions.filter(extension => this.extensionEnablementService.isEnabled(extension) || (dataOverrides.extensionId && extension.identifier.id === dataOverrides.extensionId)); @@ -93,26 +57,6 @@ export class NativeIssueService implements IWorkbenchIssueService { extensionData: 'Extensions data loading', }; })); - oldExtensionData.push(...enabledExtensions.map((extension): OldIssueReporterExtensionData => { - const { manifest } = extension; - const manifestKeys = manifest.contributes ? Object.keys(manifest.contributes) : []; - const isTheme = !manifest.main && !manifest.browser && manifestKeys.length === 1 && manifestKeys[0] === 'themes'; - const isBuiltin = extension.type === ExtensionType.System; - return { - name: manifest.name, - publisher: manifest.publisher, - version: manifest.version, - repositoryUrl: manifest.repository && manifest.repository.url, - bugsUrl: manifest.bugs && manifest.bugs.url, - displayName: manifest.displayName, - id: extension.identifier.id, - data: dataOverrides.data, - uri: dataOverrides.uri, - isTheme, - isBuiltin, - extensionData: 'Extensions data loading', - }; - })); } catch (e) { extensionData.push({ name: 'Workbench Issue Service', @@ -126,18 +70,6 @@ export class NativeIssueService implements IWorkbenchIssueService { isTheme: false, isBuiltin: true }); - oldExtensionData.push({ - name: 'Workbench Issue Service', - publisher: 'Unknown', - version: '0.0.0', - repositoryUrl: undefined, - bugsUrl: undefined, - extensionData: 'Extensions data loading', - displayName: `Extensions not loaded: ${e}`, - id: 'workbench.issue', - isTheme: false, - isBuiltin: true - }); } const experiments = await this.experimentService.getCurrentExperiments(); @@ -169,34 +101,7 @@ export class NativeIssueService implements IWorkbenchIssueService { githubAccessToken }, dataOverrides); - const oldIssueReporterData: OldIssueReporterData = Object.assign({ - styles: oldGetIssueReporterStyles(theme), - zoomLevel: getZoomLevel(mainWindow), - enabledExtensions: oldExtensionData, - experiments: experiments?.join('\n'), - restrictedMode: !this.workspaceTrustManagementService.isWorkspaceTrusted(), - isUnsupported, - githubAccessToken - }, oldDataOverrides); - - if (issueReporterData.extensionId) { - const extensionExists = extensionData.some(extension => ExtensionIdentifier.equals(extension.id, issueReporterData.extensionId)); - if (!extensionExists) { - console.error(`Extension with ID ${issueReporterData.extensionId} does not exist.`); - } - } - - if (issueReporterData.extensionId && this.extensionIdentifierSet.has(issueReporterData.extensionId)) { - ipcRenderer.send(`vscode:triggerReporterMenuResponse:${issueReporterData.extensionId}`, issueReporterData); - this.extensionIdentifierSet.delete(new ExtensionIdentifier(issueReporterData.extensionId)); - } - - - if (this.configurationService.getValue('issueReporter.experimental.auxWindow')) { - return this.issueFormService.openReporter(issueReporterData); - } - - return this.issueMainService.openReporter(oldIssueReporterData); + return this.issueFormService.openReporter(issueReporterData); } } @@ -223,28 +128,6 @@ export function getIssueReporterStyles(theme: IColorTheme): IssueReporterStyles }; } -export function oldGetIssueReporterStyles(theme: IColorTheme): OldIssueReporterStyles { - return { - backgroundColor: getColor(theme, SIDE_BAR_BACKGROUND), - color: getColor(theme, foreground), - textLinkColor: getColor(theme, textLinkForeground), - textLinkActiveForeground: getColor(theme, textLinkActiveForeground), - inputBackground: getColor(theme, inputBackground), - inputForeground: getColor(theme, inputForeground), - inputBorder: getColor(theme, inputBorder), - inputActiveBorder: getColor(theme, inputActiveOptionBorder), - inputErrorBorder: getColor(theme, inputValidationErrorBorder), - inputErrorBackground: getColor(theme, inputValidationErrorBackground), - inputErrorForeground: getColor(theme, inputValidationErrorForeground), - buttonBackground: getColor(theme, buttonBackground), - buttonForeground: getColor(theme, buttonForeground), - buttonHoverBackground: getColor(theme, buttonHoverBackground), - sliderActiveColor: getColor(theme, scrollbarSliderActiveBackground), - sliderBackgroundColor: getColor(theme, scrollbarSliderBackground), - sliderHoverColor: getColor(theme, scrollbarSliderHoverBackground), - }; -} - function getColor(theme: IColorTheme, key: string): string | undefined { const color = theme.getColor(key); return color ? color.toString() : undefined; diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/media/issueReporter.css b/src/vs/workbench/contrib/issue/electron-sandbox/media/issueReporter.css index 6f8e7abe204fd..c88a95a95e025 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/media/issueReporter.css +++ b/src/vs/workbench/contrib/issue/electron-sandbox/media/issueReporter.css @@ -3,56 +3,54 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* -* TODO: @justschen - remove this file once the new issue reporter is enabled by default. -* Split between this and a `newIssueReporter` because of specificy from new issue reporter monaco-workbench stylesheets. -*/ - /** * Table */ -table { +.issue-reporter-body table { width: 100%; max-width: 100%; background-color: transparent; border-collapse: collapse; } -th { + +.issue-reporter-body th { vertical-align: bottom; border-bottom: 1px solid; padding: 5px; text-align: inherit; } -td { + +.issue-reporter-body td { padding: 5px; vertical-align: top; } -tr td:first-child { +.issue-reporter-body tr td:first-child { width: 30%; } -label { +.issue-reporter-body label { user-select: none; } -.block-settingsSearchResults-details { +.issue-reporter-body .block-settingsSearchResults-details { padding-bottom: .5rem; } -.block-settingsSearchResults-details > div { +.issue-reporter-body .block-settingsSearchResults-details > div { padding: .5rem .75rem; } -.section { +.issue-reporter-body .section { margin-bottom: .5em; } /** * Forms */ -input[type="text"], textarea { +.issue-reporter-body input[type="text"], +.issue-reporter-body textarea { display: block; width: 100%; padding: .375rem .75rem; @@ -63,7 +61,7 @@ input[type="text"], textarea { border: 1px solid #ced4da; } -textarea { +.issue-reporter-body textarea { overflow: auto; resize: vertical; } @@ -72,7 +70,7 @@ textarea { * Button */ -.monaco-text-button { +.issue-reporter-body .monaco-text-button { display: block; width: auto; padding: 4px 10px; @@ -81,7 +79,7 @@ textarea { font-size: 13px; } -select { +.issue-reporter-body select { height: calc(2.25rem + 2px); display: inline-block; padding: 3px 3px; @@ -92,60 +90,104 @@ select { border: none; } -* { +.issue-reporter-body * { box-sizing: border-box; } -textarea, input, select { +.issue-reporter-body textarea, +.issue-reporter-body input, +.issue-reporter-body select { font-family: inherit; } -html { +.issue-reporter-body html { color: #CCCCCC; height: 100%; } -.extension-caption .codicon-modifier-spin { +.issue-reporter-body .extension-caption .codicon-modifier-spin { padding-bottom: 3px; margin-left: 2px; } /* Font Families (with CJK support) */ -.mac { font-family: -apple-system, BlinkMacSystemFont, sans-serif; } -.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; } -.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; } -.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; } -.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Nanum Gothic", "AppleGothic", sans-serif; } +.issue-reporter-body .mac { + font-family: -apple-system, BlinkMacSystemFont, sans-serif; +} + +.issue-reporter-body .mac:lang(zh-Hans) { + font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; +} + +.issue-reporter-body .mac:lang(zh-Hant) { + font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; +} + +.issue-reporter-body .mac:lang(ja) { + font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; +} + +.issue-reporter-body .mac:lang(ko) { + font-family: -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Nanum Gothic", "AppleGothic", sans-serif; +} + +.issue-reporter-body .windows { + font-family: "Segoe WPC", "Segoe UI", sans-serif; +} + +.issue-reporter-body .windows:lang(zh-Hans) { + font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; +} -.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; } -.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; } -.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; } -.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; } -.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; } +.issue-reporter-body .windows:lang(zh-Hant) { + font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; +} + +.issue-reporter-body .windows:lang(ja) { + font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; +} + +.issue-reporter-body .windows:lang(ko) { + font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; +} /* Linux: add `system-ui` as first font and not `Ubuntu` to allow other distribution pick their standard OS font */ -.linux { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; } -.linux:lang(zh-Hans) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } -.linux:lang(zh-Hant) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; } -.linux:lang(ja) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } -.linux:lang(ko) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } - -body { - margin: 0; - overflow-y: scroll; - height: 100%; +.issue-reporter-body .linux { + font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; +} + +.issue-reporter-body .linux:lang(zh-Hans) { + font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; +} + +.issue-reporter-body .linux:lang(zh-Hant) { + font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; +} + +.issue-reporter-body .linux:lang(ja) { + font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; +} + +.issue-reporter-body .linux:lang(ko) { + font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } -.hidden { +body.issue-reporter-body { + margin: 0 !important; + overflow-y: scroll !important; + height: 100% !important; +} + +.issue-reporter-body .hidden { display: none; } -.block { +.issue-reporter-body .block { font-size: 12px; } -.block .block-info { +.issue-reporter-body .block .block-info { width: 100%; font-size: 12px; overflow: auto; @@ -154,7 +196,7 @@ body { padding: 10px; } -#issue-reporter { +.issue-reporter-body #issue-reporter { max-width: 85vw; margin-left: auto; margin-right: auto; @@ -166,65 +208,67 @@ body { overflow: visible; } -.description-section { +.issue-reporter-body .description-section { flex-grow: 1; display: flex; flex-direction: column; flex-shrink: 0; } -textarea { +.issue-reporter-body textarea { flex-grow: 1; min-height: 150px; } -.block-info-text { +.issue-reporter-body .block-info-text { display: flex; flex-grow: 1; } -#github-submit-btn { +.issue-reporter-body #github-submit-btn { flex-shrink: 0; margin-left: auto; margin-top: 10px; margin-bottom: 10px; } -.two-col { +.issue-reporter-body .two-col { display: inline-block; width: 49%; } -#vscode-version { +.issue-reporter-body #vscode-version { width: 90%; } -.input-group { +.issue-reporter-body .input-group { margin-bottom: 1em; } -#extension-selection { +.issue-reporter-body #extension-selection { margin-top: 1em; } -select, input, textarea { +.issue-reporter-body .issue-reporter select, +.issue-reporter-body .issue-reporter input, +.issue-reporter-body .issue-reporter textarea { border: 1px solid transparent; margin-top: 10px; } -#issue-reporter .validation-error { +.issue-reporter-body #issue-reporter .validation-error { font-size: 12px; padding: 10px; border-top: 0px !important; } -#issue-reporter .system-info { +.issue-reporter-body #issue-reporter .system-info { margin-bottom: 10px; } -input[type="checkbox"] { +.issue-reporter-body input[type="checkbox"] { width: auto; display: inline-block; margin-top: 0; @@ -232,136 +276,166 @@ input[type="checkbox"] { cursor: pointer; } -input:disabled { +.issue-reporter-body input:disabled { opacity: 0.6; } -.list-title { +.issue-reporter-body .list-title { margin-top: 1em; margin-left: 1em; } -.instructions { +.issue-reporter-body .instructions { font-size: 12px; margin-top: .5em; } -a, .workbenchCommand { +.issue-reporter-body a, +.issue-reporter-body .workbenchCommand { cursor: pointer; border: 1px solid transparent; } -.workbenchCommand:disabled { +.issue-reporter-body .workbenchCommand:disabled { color: #868e96; cursor: default } -.block-extensions .block-info { +.issue-reporter-body .block-extensions .block-info { margin-bottom: 1.5em; } /* Default styles, overwritten if a theme is provided */ -input, select, textarea { +.issue-reporter-body input, +.issue-reporter-body select, +.issue-reporter-body textarea { background-color: #3c3c3c; border: none; color: #cccccc; } -a { +.issue-reporter-body a { color: #CCCCCC; text-decoration: none; } -.section .input-group .validation-error { +.issue-reporter-body .showInfo, +.issue-reporter-body .input-group a { + color: var(--vscode-textLink-foreground); +} + +.issue-reporter-body .section .input-group .validation-error { margin-left: 100px; } -.section .inline-form-control, .section .inline-label { +.issue-reporter-body .section .inline-form-control, +.issue-reporter-body .section .inline-label { display: inline-block; + font-size: initial; } -.section .inline-label { +.issue-reporter-body .section .inline-label { width: 95px; } -.section .inline-form-control, .section .input-group .validation-error { +.issue-reporter-body .issue-reporter .inline-label, +.issue-reporter-body .issue-reporter #issue-description-label { + font-size: initial; + cursor: default; +} + +.issue-reporter-body .monaco-workbench .issue-reporter label { + cursor: default; +} + +.issue-reporter-body .section .inline-form-control, +.issue-reporter-body .section .input-group .validation-error { width: calc(100% - 100px); } -#issue-type { +.issue-reporter-body #issue-type, +.issue-reporter-body #issue-source, +.issue-reporter-body #extension-selector { cursor: pointer; + appearance: auto; + border: none; + border-right: 6px solid transparent; + padding-left: 10px; } -#similar-issues { +.issue-reporter-body #similar-issues { margin-left: 15%; display: block; } -#problem-source-help-text { +.issue-reporter-body #problem-source-help-text { margin-left: calc(15% + 1em); } @media (max-width: 950px) { - .section .inline-label { + .issue-reporter-body .section .inline-label { width: 15%; } - #problem-source-help-text { + .issue-reporter-body #problem-source-help-text { margin-left: calc(15% + 1em); } - .section .inline-form-control, .section .input-group .validation-error { + .issue-reporter-body .section .inline-form-control, + .issue-reporter-body .section .input-group .validation-error { width: calc(85% - 5px); } - .section .input-group .validation-error { + .issue-reporter-body .section .input-group .validation-error { margin-left: calc(15% + 4px); } } @media (max-width: 620px) { - .section .inline-label { + .issue-reporter-body .section .inline-label { display: none !important; } - #problem-source-help-text { + .issue-reporter-body #problem-source-help-text { margin-left: 1em; } - .section .inline-form-control, .section .input-group .validation-error { + .issue-reporter-body .section .inline-form-control, + .issue-reporter-body .section .input-group .validation-error { width: 100%; } - #similar-issues, .section .input-group .validation-error { + .issue-reporter-body #similar-issues, + .issue-reporter-body .section .input-group .validation-error { margin-left: 0; } } -::-webkit-scrollbar { +.issue-reporter-body::-webkit-scrollbar { width: 14px; } -::-webkit-scrollbar-thumb { +.issue-reporter-body::-webkit-scrollbar-thumb { min-height: 20px; } -::-webkit-scrollbar-corner { +.issue-reporter-body::-webkit-scrollbar-corner { display: none; } -.issues-container { +.issue-reporter-body .issues-container { margin-left: 1.5em; margin-top: .5em; max-height: 92px; overflow-y: auto; } -.issues-container > .issue { +.issue-reporter-body .issues-container > .issue { padding: 4px 0; display: flex; } -.issues-container > .issue > .issue-link { +.issue-reporter-body .issues-container > .issue > .issue-link { width: calc(100% - 82px); overflow: hidden; padding-top: 3px; @@ -369,11 +443,11 @@ a { text-overflow: ellipsis; } -.issues-container > .issue > .issue-state .codicon { +.issue-reporter-body .issues-container > .issue > .issue-state .codicon { width: 16px; } -.issues-container > .issue > .issue-state { +.issue-reporter-body .issues-container > .issue > .issue-state { display: flex; width: 77px; padding: 3px 6px; @@ -383,7 +457,7 @@ a { border-radius: .25rem; } -.issues-container > .issue .label { +.issue-reporter-body .issues-container > .issue .label { padding-top: 2px; margin-left: 5px; width: 44px; @@ -391,6 +465,6 @@ a { overflow: hidden; } -.issues-container > .issue .issue-icon{ +.issue-reporter-body .issues-container > .issue .issue-icon { padding-top: 2px; } diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/media/newIssueReporter.css b/src/vs/workbench/contrib/issue/electron-sandbox/media/newIssueReporter.css deleted file mode 100644 index c88a95a95e025..0000000000000 --- a/src/vs/workbench/contrib/issue/electron-sandbox/media/newIssueReporter.css +++ /dev/null @@ -1,470 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * Table - */ - -.issue-reporter-body table { - width: 100%; - max-width: 100%; - background-color: transparent; - border-collapse: collapse; -} - -.issue-reporter-body th { - vertical-align: bottom; - border-bottom: 1px solid; - padding: 5px; - text-align: inherit; -} - -.issue-reporter-body td { - padding: 5px; - vertical-align: top; -} - -.issue-reporter-body tr td:first-child { - width: 30%; -} - -.issue-reporter-body label { - user-select: none; -} - -.issue-reporter-body .block-settingsSearchResults-details { - padding-bottom: .5rem; -} - -.issue-reporter-body .block-settingsSearchResults-details > div { - padding: .5rem .75rem; -} - -.issue-reporter-body .section { - margin-bottom: .5em; -} - -/** - * Forms - */ -.issue-reporter-body input[type="text"], -.issue-reporter-body textarea { - display: block; - width: 100%; - padding: .375rem .75rem; - font-size: 1rem; - line-height: 1.5; - color: #495057; - background-color: #fff; - border: 1px solid #ced4da; -} - -.issue-reporter-body textarea { - overflow: auto; - resize: vertical; -} - -/** - * Button - */ - -.issue-reporter-body .monaco-text-button { - display: block; - width: auto; - padding: 4px 10px; - align-self: flex-end; - margin-bottom: 1em; - font-size: 13px; -} - -.issue-reporter-body select { - height: calc(2.25rem + 2px); - display: inline-block; - padding: 3px 3px; - font-size: 14px; - line-height: 1.5; - color: #495057; - background-color: #fff; - border: none; -} - -.issue-reporter-body * { - box-sizing: border-box; -} - -.issue-reporter-body textarea, -.issue-reporter-body input, -.issue-reporter-body select { - font-family: inherit; -} - -.issue-reporter-body html { - color: #CCCCCC; - height: 100%; -} - -.issue-reporter-body .extension-caption .codicon-modifier-spin { - padding-bottom: 3px; - margin-left: 2px; -} - -/* Font Families (with CJK support) */ - -.issue-reporter-body .mac { - font-family: -apple-system, BlinkMacSystemFont, sans-serif; -} - -.issue-reporter-body .mac:lang(zh-Hans) { - font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; -} - -.issue-reporter-body .mac:lang(zh-Hant) { - font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; -} - -.issue-reporter-body .mac:lang(ja) { - font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; -} - -.issue-reporter-body .mac:lang(ko) { - font-family: -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Nanum Gothic", "AppleGothic", sans-serif; -} - -.issue-reporter-body .windows { - font-family: "Segoe WPC", "Segoe UI", sans-serif; -} - -.issue-reporter-body .windows:lang(zh-Hans) { - font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; -} - -.issue-reporter-body .windows:lang(zh-Hant) { - font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; -} - -.issue-reporter-body .windows:lang(ja) { - font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; -} - -.issue-reporter-body .windows:lang(ko) { - font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; -} - -/* Linux: add `system-ui` as first font and not `Ubuntu` to allow other distribution pick their standard OS font */ -.issue-reporter-body .linux { - font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; -} - -.issue-reporter-body .linux:lang(zh-Hans) { - font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; -} - -.issue-reporter-body .linux:lang(zh-Hant) { - font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; -} - -.issue-reporter-body .linux:lang(ja) { - font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; -} - -.issue-reporter-body .linux:lang(ko) { - font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; -} - -body.issue-reporter-body { - margin: 0 !important; - overflow-y: scroll !important; - height: 100% !important; -} - -.issue-reporter-body .hidden { - display: none; -} - -.issue-reporter-body .block { - font-size: 12px; -} - -.issue-reporter-body .block .block-info { - width: 100%; - font-size: 12px; - overflow: auto; - overflow-wrap: break-word; - margin: 5px; - padding: 10px; -} - -.issue-reporter-body #issue-reporter { - max-width: 85vw; - margin-left: auto; - margin-right: auto; - padding-top: 2em; - padding-bottom: 2em; - display: flex; - flex-direction: column; - min-height: 100%; - overflow: visible; -} - -.issue-reporter-body .description-section { - flex-grow: 1; - display: flex; - flex-direction: column; - flex-shrink: 0; -} - -.issue-reporter-body textarea { - flex-grow: 1; - min-height: 150px; -} - -.issue-reporter-body .block-info-text { - display: flex; - flex-grow: 1; -} - -.issue-reporter-body #github-submit-btn { - flex-shrink: 0; - margin-left: auto; - margin-top: 10px; - margin-bottom: 10px; -} - -.issue-reporter-body .two-col { - display: inline-block; - width: 49%; -} - -.issue-reporter-body #vscode-version { - width: 90%; -} - -.issue-reporter-body .input-group { - margin-bottom: 1em; -} - -.issue-reporter-body #extension-selection { - margin-top: 1em; -} - -.issue-reporter-body .issue-reporter select, -.issue-reporter-body .issue-reporter input, -.issue-reporter-body .issue-reporter textarea { - border: 1px solid transparent; - margin-top: 10px; -} - - -.issue-reporter-body #issue-reporter .validation-error { - font-size: 12px; - padding: 10px; - border-top: 0px !important; -} - -.issue-reporter-body #issue-reporter .system-info { - margin-bottom: 10px; -} - - -.issue-reporter-body input[type="checkbox"] { - width: auto; - display: inline-block; - margin-top: 0; - vertical-align: middle; - cursor: pointer; -} - -.issue-reporter-body input:disabled { - opacity: 0.6; -} - -.issue-reporter-body .list-title { - margin-top: 1em; - margin-left: 1em; -} - -.issue-reporter-body .instructions { - font-size: 12px; - margin-top: .5em; -} - -.issue-reporter-body a, -.issue-reporter-body .workbenchCommand { - cursor: pointer; - border: 1px solid transparent; -} - -.issue-reporter-body .workbenchCommand:disabled { - color: #868e96; - cursor: default -} - -.issue-reporter-body .block-extensions .block-info { - margin-bottom: 1.5em; -} - -/* Default styles, overwritten if a theme is provided */ -.issue-reporter-body input, -.issue-reporter-body select, -.issue-reporter-body textarea { - background-color: #3c3c3c; - border: none; - color: #cccccc; -} - -.issue-reporter-body a { - color: #CCCCCC; - text-decoration: none; -} - -.issue-reporter-body .showInfo, -.issue-reporter-body .input-group a { - color: var(--vscode-textLink-foreground); -} - -.issue-reporter-body .section .input-group .validation-error { - margin-left: 100px; -} - -.issue-reporter-body .section .inline-form-control, -.issue-reporter-body .section .inline-label { - display: inline-block; - font-size: initial; -} - -.issue-reporter-body .section .inline-label { - width: 95px; -} - -.issue-reporter-body .issue-reporter .inline-label, -.issue-reporter-body .issue-reporter #issue-description-label { - font-size: initial; - cursor: default; -} - -.issue-reporter-body .monaco-workbench .issue-reporter label { - cursor: default; -} - -.issue-reporter-body .section .inline-form-control, -.issue-reporter-body .section .input-group .validation-error { - width: calc(100% - 100px); -} - -.issue-reporter-body #issue-type, -.issue-reporter-body #issue-source, -.issue-reporter-body #extension-selector { - cursor: pointer; - appearance: auto; - border: none; - border-right: 6px solid transparent; - padding-left: 10px; -} - -.issue-reporter-body #similar-issues { - margin-left: 15%; - display: block; -} - -.issue-reporter-body #problem-source-help-text { - margin-left: calc(15% + 1em); -} - -@media (max-width: 950px) { - .issue-reporter-body .section .inline-label { - width: 15%; - } - - .issue-reporter-body #problem-source-help-text { - margin-left: calc(15% + 1em); - } - - .issue-reporter-body .section .inline-form-control, - .issue-reporter-body .section .input-group .validation-error { - width: calc(85% - 5px); - } - - .issue-reporter-body .section .input-group .validation-error { - margin-left: calc(15% + 4px); - } -} - -@media (max-width: 620px) { - .issue-reporter-body .section .inline-label { - display: none !important; - } - - .issue-reporter-body #problem-source-help-text { - margin-left: 1em; - } - - .issue-reporter-body .section .inline-form-control, - .issue-reporter-body .section .input-group .validation-error { - width: 100%; - } - - .issue-reporter-body #similar-issues, - .issue-reporter-body .section .input-group .validation-error { - margin-left: 0; - } -} - -.issue-reporter-body::-webkit-scrollbar { - width: 14px; -} - -.issue-reporter-body::-webkit-scrollbar-thumb { - min-height: 20px; -} - -.issue-reporter-body::-webkit-scrollbar-corner { - display: none; -} - -.issue-reporter-body .issues-container { - margin-left: 1.5em; - margin-top: .5em; - max-height: 92px; - overflow-y: auto; -} - -.issue-reporter-body .issues-container > .issue { - padding: 4px 0; - display: flex; -} - -.issue-reporter-body .issues-container > .issue > .issue-link { - width: calc(100% - 82px); - overflow: hidden; - padding-top: 3px; - white-space: nowrap; - text-overflow: ellipsis; -} - -.issue-reporter-body .issues-container > .issue > .issue-state .codicon { - width: 16px; -} - -.issue-reporter-body .issues-container > .issue > .issue-state { - display: flex; - width: 77px; - padding: 3px 6px; - margin-right: 5px; - color: #CCCCCC; - background-color: #3c3c3c; - border-radius: .25rem; -} - -.issue-reporter-body .issues-container > .issue .label { - padding-top: 2px; - margin-left: 5px; - width: 44px; - text-overflow: ellipsis; - overflow: hidden; -} - -.issue-reporter-body .issues-container > .issue .issue-icon { - padding-top: 2px; -} diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService.ts b/src/vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService.ts index 508137162ca02..c202234088a86 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import './media/newIssueReporter.css'; +import './media/issueReporter.css'; import { IMenuService } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; @@ -14,7 +14,7 @@ import { INativeHostService } from '../../../../platform/native/common/native.js import product from '../../../../platform/product/common/product.js'; import { IssueFormService } from '../browser/issueFormService.js'; import { IIssueFormService, IssueReporterData } from '../common/issue.js'; -import { IssueReporter2 } from './issueReporterService2.js'; +import { IssueReporter } from './issueReporterService.js'; import { IAuxiliaryWindowService } from '../../../services/auxiliaryWindow/browser/auxiliaryWindowService.js'; import { IHostService } from '../../../services/host/browser/host.js'; @@ -53,7 +53,7 @@ export class NativeIssueFormService extends IssueFormService implements IIssueFo // create issue reporter and instantiate if (this.issueReporterWindow) { - const issueReporter = this.instantiationService.createInstance(IssueReporter2, !!this.environmentService.disableExtensions, data, { type: this.type, arch: this.arch, release: this.release }, product, this.issueReporterWindow); + const issueReporter = this.instantiationService.createInstance(IssueReporter, !!this.environmentService.disableExtensions, data, { type: this.type, arch: this.arch, release: this.release }, product, this.issueReporterWindow); issueReporter.render(); } } diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts b/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts index 6c33d9f813391..75826f51d8ecc 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts @@ -13,10 +13,9 @@ import { INativeEnvironmentService } from '../../../../platform/environment/comm import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; -import { IProcessMainService } from '../../../../platform/issue/common/issue.js'; +import { IProcessMainService } from '../../../../platform/process/common/process.js'; import './processService.js'; -import './issueMainService.js'; - +import './processMainService.js'; //#region Commands diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueMainService.ts b/src/vs/workbench/contrib/issue/electron-sandbox/processMainService.ts similarity index 76% rename from src/vs/workbench/contrib/issue/electron-sandbox/issueMainService.ts rename to src/vs/workbench/contrib/issue/electron-sandbox/processMainService.ts index 32cbb69428d92..d2073829039bd 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueMainService.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/processMainService.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { registerMainProcessRemoteService } from '../../../../platform/ipc/electron-sandbox/services.js'; -import { IProcessMainService, IIssueMainService } from '../../../../platform/issue/common/issue.js'; +import { IProcessMainService } from '../../../../platform/process/common/process.js'; -registerMainProcessRemoteService(IIssueMainService, 'issue'); registerMainProcessRemoteService(IProcessMainService, 'process'); diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/processService.ts b/src/vs/workbench/contrib/issue/electron-sandbox/processService.ts index c781a4af73c94..d5837297bed69 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/processService.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/processService.ts @@ -6,7 +6,7 @@ import { getZoomLevel } from '../../../../base/browser/browser.js'; import { platform } from '../../../../base/common/process.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { IProcessMainService, ProcessExplorerData } from '../../../../platform/issue/common/issue.js'; +import { IProcessMainService, ProcessExplorerData } from '../../../../platform/process/common/process.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { activeContrastBorder, editorBackground, editorForeground, listActiveSelectionBackground, listActiveSelectionForeground, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from '../../../../platform/theme/common/colorRegistry.js'; import { IColorTheme, IThemeService } from '../../../../platform/theme/common/themeService.js'; From adfb37d5ebae2f0583f7c1dc28c4379fbace3d83 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 30 Oct 2024 10:06:56 +0100 Subject: [PATCH 075/555] [1035] potential listener LEAK detected, having 175 listeners already. MOST frequent listener (1): (#232569) Fixes #232400 --- src/vs/editor/common/model/tokenizationTextModelPart.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts index ae4bc09c82ed4..811c24b72561e 100644 --- a/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -59,12 +59,6 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz ) { super(); - this._register(this._languageConfigurationService.onDidChange(e => { - if (e.affects(this._languageId)) { - this._onDidChangeLanguageConfiguration.fire({}); - } - })); - // We just look at registry changes to determine whether to use tree sitter. // This means that removing a language from the setting will not cause a switch to textmate and will require a reload. // Adding a language to the setting will not need a reload, however. From 2d4694a225a5d72406f252b52830d9ad6fb7398f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 30 Oct 2024 10:16:55 +0100 Subject: [PATCH 076/555] Quick open has duplicate "recently opened" entries (fix #232426) (#232570) --- .../services/history/browser/historyService.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 3106e6791e139..d89dea3f23f1b 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -1028,13 +1028,18 @@ export class HistoryService extends Disposable implements IHistoryService { continue; } - // Add into history - this.addToHistory(editor); - - // Remember as added + // Make sure to skip duplicates from the editors LRU if (editor.resource) { - handledEditors.add(`${editor.resource.toString()}/${editor.editorId}`); + const historyEntryId = `${editor.resource.toString()}/${editor.editorId}`; + if (handledEditors.has(historyEntryId)) { + continue; // already added + } + + handledEditors.add(historyEntryId); } + + // Add into history + this.addToHistory(editor); } // Add remaining from storage if not there already From 5d6be574abc632f75c1cb10ce027fea3eb21d479 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 30 Oct 2024 02:33:56 -0700 Subject: [PATCH 077/555] Try to simplify usage of `createAndFillInContextMenuActions` (#232546) Try to simplify `createAndFillInContextMenuActions` This is one of the rare functions that we have that uses an out parameter. This often results in a lot of boilerplate code to call it In this PR I've renamed it to `getContextMenuActions` to make use of a standard return type. Instead of an overload, there are now two different versions of the function: `getContextMenuActions` and `getFlatContextMenuActions` --- .../browser/menuEntryActionViewItem.ts | 81 +++++++++++-------- .../contextview/browser/contextMenuService.ts | 6 +- .../parts/activitybar/activitybarPart.ts | 5 +- .../parts/auxiliarybar/auxiliaryBarPart.ts | 5 +- .../browser/parts/panel/panelPart.ts | 8 +- .../browser/parts/titlebar/menubarControl.ts | 6 +- .../workbench/browser/parts/views/treeView.ts | 7 +- .../chatMarkdownContentPart.ts | 7 +- .../chatReferencesContentPart.ts | 7 +- .../chat/browser/chatInlineAnchorWidget.ts | 7 +- .../contrib/comments/browser/commentMenus.ts | 16 +--- .../comments/browser/commentsTreeViewer.ts | 9 +-- .../contrib/debug/browser/breakpointsView.ts | 5 +- .../contrib/debug/browser/callStackView.ts | 7 +- .../workbench/contrib/debug/browser/repl.ts | 5 +- .../contrib/debug/browser/variablesView.ts | 11 +-- .../debug/browser/watchExpressionsView.ts | 9 +-- .../abstractRuntimeExtensionsEditor.ts | 5 +- .../notebookVariablesView.ts | 8 +- .../contrib/scm/browser/scmViewPane.ts | 5 +- src/vs/workbench/contrib/scm/browser/util.ts | 7 +- .../terminal/browser/terminalContextMenu.ts | 6 +- .../contrib/terminal/browser/terminalView.ts | 5 +- .../testing/browser/testingDecorations.ts | 6 +- .../testing/browser/testingExplorerView.ts | 5 +- .../contrib/timeline/browser/timelinePane.ts | 9 +-- .../parts/titlebar/menubarControl.ts | 5 +- 27 files changed, 105 insertions(+), 157 deletions(-) diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 3386feda5ddcc..0cd8f06791987 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -3,20 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, addDisposableListener, append, EventType, ModifierKeyEmitter, prepend } from '../../../base/browser/dom.js'; import { asCSSUrl } from '../../../base/browser/cssValue.js'; +import { $, addDisposableListener, append, EventType, ModifierKeyEmitter, prepend } from '../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js'; import { ActionViewItem, BaseActionViewItem, SelectActionViewItem } from '../../../base/browser/ui/actionbar/actionViewItems.js'; import { DropdownMenuActionViewItem, IDropdownMenuActionViewItemOptions } from '../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; +import { IHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegate.js'; import { ActionRunner, IAction, IRunEvent, Separator, SubmenuAction } from '../../../base/common/actions.js'; import { Event } from '../../../base/common/event.js'; import { UILabelProvider } from '../../../base/common/keybindingLabels.js'; +import { ResolvedKeybinding } from '../../../base/common/keybindings.js'; import { KeyCode } from '../../../base/common/keyCodes.js'; import { combinedDisposable, MutableDisposable, toDisposable } from '../../../base/common/lifecycle.js'; import { isLinux, isWindows, OS } from '../../../base/common/platform.js'; -import './menuEntryActionViewItem.css'; +import { ThemeIcon } from '../../../base/common/themables.js'; +import { assertType } from '../../../base/common/types.js'; import { localize } from '../../../nls.js'; -import { IMenu, IMenuActionOptions, IMenuService, MenuItemAction, SubmenuItemAction } from '../common/actions.js'; +import { IAccessibilityService } from '../../accessibility/common/accessibility.js'; import { ICommandAction, isICommandActionToggleInfo } from '../../action/common/action.js'; import { IContextKeyService } from '../../contextkey/common/contextkey.js'; import { IContextMenuService, IContextViewService } from '../../contextview/browser/contextView.js'; @@ -24,33 +27,41 @@ import { IInstantiationService } from '../../instantiation/common/instantiation. import { IKeybindingService } from '../../keybinding/common/keybinding.js'; import { INotificationService } from '../../notification/common/notification.js'; import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js'; -import { IThemeService } from '../../theme/common/themeService.js'; -import { ThemeIcon } from '../../../base/common/themables.js'; -import { isDark } from '../../theme/common/theme.js'; -import { IHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegate.js'; -import { assertType } from '../../../base/common/types.js'; -import { asCssVariable, selectBorder } from '../../theme/common/colorRegistry.js'; import { defaultSelectBoxStyles } from '../../theme/browser/defaultStyles.js'; -import { IAccessibilityService } from '../../accessibility/common/accessibility.js'; -import { ResolvedKeybinding } from '../../../base/common/keybindings.js'; +import { asCssVariable, selectBorder } from '../../theme/common/colorRegistry.js'; +import { isDark } from '../../theme/common/theme.js'; +import { IThemeService } from '../../theme/common/themeService.js'; +import { IMenu, IMenuActionOptions, IMenuService, MenuItemAction, SubmenuItemAction } from '../common/actions.js'; +import './menuEntryActionViewItem.css'; -export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string): void; -export function createAndFillInContextMenuActions(menu: [string, Array][], target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string): void; -export function createAndFillInContextMenuActions(menu: IMenu | [string, Array][], optionsOrTarget: IMenuActionOptions | undefined | IAction[] | { primary: IAction[]; secondary: IAction[] }, targetOrPrimaryGroup?: IAction[] | { primary: IAction[]; secondary: IAction[] } | string, primaryGroupOrUndefined?: string): void { - let target: IAction[] | { primary: IAction[]; secondary: IAction[] }; - let primaryGroup: string | ((actionGroup: string) => boolean) | undefined; - let groups: [string, Array][]; - if (Array.isArray(menu)) { - groups = menu; - target = optionsOrTarget as IAction[] | { primary: IAction[]; secondary: IAction[] }; - primaryGroup = targetOrPrimaryGroup as string | undefined; - } else { - const options: IMenuActionOptions | undefined = optionsOrTarget as IMenuActionOptions | undefined; - groups = menu.getActions(options); - target = targetOrPrimaryGroup as IAction[] | { primary: IAction[]; secondary: IAction[] }; - primaryGroup = primaryGroupOrUndefined; - } +interface PrimaryAndSecondaryActions { + primary: IAction[]; + secondary: IAction[]; +} + +export function getContextMenuActions( + groups: ReadonlyArray<[string, ReadonlyArray]>, + primaryGroup?: string +): PrimaryAndSecondaryActions { + const target: PrimaryAndSecondaryActions = { primary: [], secondary: [] }; + getContextMenuActionsImpl(groups, target, primaryGroup); + return target; +} + +export function getFlatContextMenuActions( + groups: ReadonlyArray<[string, ReadonlyArray]>, + primaryGroup?: string +): IAction[] { + const target: IAction[] = []; + getContextMenuActionsImpl(groups, target, primaryGroup); + return target; +} +function getContextMenuActionsImpl( + groups: ReadonlyArray<[string, ReadonlyArray]>, + target: IAction[] | PrimaryAndSecondaryActions, + primaryGroup?: string +) { const modifierKeyEmitter = ModifierKeyEmitter.getInstance(); const useAlternativeActions = modifierKeyEmitter.keyStatus.altKey || ((isWindows || isLinux) && modifierKeyEmitter.keyStatus.shiftKey); fillInActions(groups, target, useAlternativeActions, primaryGroup ? actionGroup => actionGroup === primaryGroup : actionGroup => actionGroup === 'navigation'); @@ -59,41 +70,41 @@ export function createAndFillInContextMenuActions(menu: IMenu | [string, Array boolean), shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, useSeparatorsInPrimaryActions?: boolean ): void; export function createAndFillInActionBarActions( menu: [string, Array][], - target: IAction[] | { primary: IAction[]; secondary: IAction[] }, + target: IAction[] | PrimaryAndSecondaryActions, primaryGroup?: string | ((actionGroup: string) => boolean), shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, useSeparatorsInPrimaryActions?: boolean ): void; export function createAndFillInActionBarActions( menu: IMenu | [string, Array][], - optionsOrTarget: IMenuActionOptions | undefined | IAction[] | { primary: IAction[]; secondary: IAction[] }, - targetOrPrimaryGroup?: IAction[] | { primary: IAction[]; secondary: IAction[] } | string | ((actionGroup: string) => boolean), + optionsOrTarget: IMenuActionOptions | undefined | IAction[] | PrimaryAndSecondaryActions, + targetOrPrimaryGroup?: IAction[] | PrimaryAndSecondaryActions | string | ((actionGroup: string) => boolean), primaryGroupOrShouldInlineSubmenu?: string | ((actionGroup: string) => boolean) | ((action: SubmenuAction, group: string, groupSize: number) => boolean), shouldInlineSubmenuOrUseSeparatorsInPrimaryActions?: ((action: SubmenuAction, group: string, groupSize: number) => boolean) | boolean, useSeparatorsInPrimaryActionsOrUndefined?: boolean ): void { - let target: IAction[] | { primary: IAction[]; secondary: IAction[] }; + let target: IAction[] | PrimaryAndSecondaryActions; let primaryGroup: string | ((actionGroup: string) => boolean) | undefined; let shouldInlineSubmenu: ((action: SubmenuAction, group: string, groupSize: number) => boolean) | undefined; let useSeparatorsInPrimaryActions: boolean | undefined; let groups: [string, Array][]; if (Array.isArray(menu)) { groups = menu; - target = optionsOrTarget as IAction[] | { primary: IAction[]; secondary: IAction[] }; + target = optionsOrTarget as IAction[] | PrimaryAndSecondaryActions; primaryGroup = targetOrPrimaryGroup as string | ((actionGroup: string) => boolean) | undefined; shouldInlineSubmenu = primaryGroupOrShouldInlineSubmenu as (action: SubmenuAction, group: string, groupSize: number) => boolean; useSeparatorsInPrimaryActions = shouldInlineSubmenuOrUseSeparatorsInPrimaryActions as boolean | undefined; } else { const options: IMenuActionOptions | undefined = optionsOrTarget as IMenuActionOptions | undefined; groups = menu.getActions(options); - target = targetOrPrimaryGroup as IAction[] | { primary: IAction[]; secondary: IAction[] }; + target = targetOrPrimaryGroup as IAction[] | PrimaryAndSecondaryActions; primaryGroup = primaryGroupOrShouldInlineSubmenu as string | ((actionGroup: string) => boolean) | undefined; shouldInlineSubmenu = shouldInlineSubmenuOrUseSeparatorsInPrimaryActions as (action: SubmenuAction, group: string, groupSize: number) => boolean; useSeparatorsInPrimaryActions = useSeparatorsInPrimaryActionsOrUndefined; @@ -106,7 +117,7 @@ export function createAndFillInActionBarActions( } function fillInActions( - groups: ReadonlyArray<[string, ReadonlyArray]>, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, + groups: ReadonlyArray<[string, ReadonlyArray]>, target: IAction[] | PrimaryAndSecondaryActions, useAlternativeActions: boolean, isPrimaryAction: (actionGroup: string) => boolean = actionGroup => actionGroup === 'navigation', shouldInlineSubmenu: (action: SubmenuAction, group: string, groupSize: number) => boolean = () => false, diff --git a/src/vs/platform/contextview/browser/contextMenuService.ts b/src/vs/platform/contextview/browser/contextMenuService.ts index b6b7bea2bac79..30577f4459375 100644 --- a/src/vs/platform/contextview/browser/contextMenuService.ts +++ b/src/vs/platform/contextview/browser/contextMenuService.ts @@ -8,7 +8,7 @@ import { ModifierKeyEmitter } from '../../../base/browser/dom.js'; import { IAction, Separator } from '../../../base/common/actions.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; -import { createAndFillInContextMenuActions } from '../../actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../actions/common/actions.js'; import { IContextKeyService } from '../../contextkey/common/contextkey.js'; import { IKeybindingService } from '../../keybinding/common/keybinding.js'; @@ -84,10 +84,10 @@ export namespace ContextMenuMenuDelegate { return { ...delegate, getActions: () => { - const target: IAction[] = []; + let target: IAction[] = []; if (menuId) { const menu = menuService.getMenuActions(menuId, contextKeyService ?? globalContextKeyService, menuActionOptions); - createAndFillInContextMenuActions(menu, target); + target = getFlatContextMenuActions(menu); } if (!delegate.getActions) { return target; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 0dafe5609aaec..6a7193a6e1554 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -32,7 +32,7 @@ import { IStorageService } from '../../../../platform/storage/common/storage.js' import { Action2, IAction2Options, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IViewDescriptorService, ViewContainerLocation, ViewContainerLocationToString } from '../../../common/views.js'; import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; @@ -370,8 +370,7 @@ export class ActivityBarCompositeBar extends PaneCompositeBar { getActivityBarContextMenuActions(): IAction[] { const activityBarPositionMenu = this.menuService.getMenuActions(MenuId.ActivityBarPositionMenu, this.contextKeyService, { shouldForwardArgs: true, renderShortTitle: true }); - const positionActions: IAction[] = []; - createAndFillInContextMenuActions(activityBarPositionMenu, { primary: [], secondary: positionActions }); + const positionActions = getContextMenuActions(activityBarPositionMenu).secondary; return [ new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions), toAction({ id: ToggleSidebarPositionAction.ID, label: ToggleSidebarPositionAction.getLabel(this.layoutService), run: () => this.instantiationService.invokeFunction(accessor => new ToggleSidebarPositionAction().run(accessor)) }) diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 264794c159260..7c930d5fbb4ec 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -30,7 +30,7 @@ import { ActionsOrientation, IActionViewItem, prepareActions } from '../../../.. import { IPaneCompositeBarOptions } from '../paneCompositeBar.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { $ } from '../../../../base/browser/dom.js'; import { HiddenItemStrategy, WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; @@ -192,8 +192,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { } const activityBarPositionMenu = this.menuService.getMenuActions(MenuId.ActivityBarPositionMenu, this.contextKeyService, { shouldForwardArgs: true, renderShortTitle: true }); - const positionActions: IAction[] = []; - createAndFillInContextMenuActions(activityBarPositionMenu, { primary: [], secondary: positionActions }); + const positionActions = getContextMenuActions(activityBarPositionMenu).secondary; actions.push(...[ new Separator(), diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index dbbefb43ab49a..76844b5208a73 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -27,7 +27,7 @@ import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js' import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { AbstractPaneCompositePart, CompositeBarPosition } from '../paneCompositePart.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IPaneCompositeBarOptions } from '../paneCompositeBar.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -161,10 +161,8 @@ export class PanelPart extends AbstractPaneCompositePart { private fillExtraContextMenuActions(actions: IAction[]): void { const panelPositionMenu = this.menuService.getMenuActions(MenuId.PanelPositionMenu, this.contextKeyService, { shouldForwardArgs: true }); const panelAlignMenu = this.menuService.getMenuActions(MenuId.PanelAlignmentMenu, this.contextKeyService, { shouldForwardArgs: true }); - const positionActions: IAction[] = []; - const alignActions: IAction[] = []; - createAndFillInContextMenuActions(panelPositionMenu, { primary: [], secondary: positionActions }); - createAndFillInContextMenuActions(panelAlignMenu, { primary: [], secondary: alignActions }); + const positionActions = getContextMenuActions(panelPositionMenu).secondary; + const alignActions = getContextMenuActions(panelAlignMenu).secondary; const panelShowLabels = this.configurationService.getValue('workbench.panel.showLabels'); const toggleShowLabelsAction = toAction({ diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index f495bad700a78..ac4940f1c28ce 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -38,7 +38,7 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { OpenRecentAction } from '../../actions/windowActions.js'; import { isICommandActionToggleInfo } from '../../../../platform/action/common/action.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { defaultMenuStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { mainWindow } from '../../../../base/browser/window.js'; import { ActivityBarPosition } from '../../../services/layout/browser/layoutService.js'; @@ -561,9 +561,7 @@ export class CustomMenubarControl extends MenubarControl { } private toActionsArray(menu: IMenu): IAction[] { - const result: IAction[] = []; - createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result); - return result; + return getFlatContextMenuActions(menu.getActions({ shouldForwardArgs: true })); } private readonly reinstallDisposables = this._register(new DisposableStore()); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index e003591cd42c0..2d65e66e01206 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -33,7 +33,7 @@ import { generateUuid } from '../../../../base/common/uuid.js'; import './media/views.css'; import { VSDataTransfer } from '../../../../base/common/dataTransfer.js'; import { localize } from '../../../../nls.js'; -import { createActionViewItem, createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -1665,10 +1665,7 @@ class TreeMenus implements IDisposable { const menuData = this.menuService.getMenuActions(menuId, contextKeyService, { shouldForwardArgs: true }); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - createAndFillInContextMenuActions(menuData, result, 'inline'); + const result = getContextMenuActions(menuData, 'inline'); if (i === 0) { primaryGroups = this.createGroups(result.primary); secondaryGroups = this.createGroups(result.secondary); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index f0ca929f866c7..d88ecccfb906a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -5,7 +5,6 @@ import * as dom from '../../../../../base/browser/dom.js'; import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; -import { IAction } from '../../../../../base/common/actions.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter } from '../../../../../base/common/event.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; @@ -18,7 +17,7 @@ import { ILanguageService } from '../../../../../editor/common/languages/languag import { getIconClasses } from '../../../../../editor/common/services/getIconClasses.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; -import { createAndFillInContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; @@ -308,9 +307,7 @@ class CollapsedCodeBlock extends Disposable { getAnchor: () => event, getActions: () => { const menu = this.menuService.getMenuActions(MenuId.ChatEditingCodeBlockContext, this.contextKeyService, { arg: { sessionId, requestId, uri: this.uri } }); - const primary: IAction[] = []; - createAndFillInContextMenuActions(menu, primary); - return primary; + return getFlatContextMenuActions(menu); }, }); })); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts index b83b47e69886a..839bc4403868f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts @@ -6,7 +6,6 @@ import * as dom from '../../../../../base/browser/dom.js'; import { Button } from '../../../../../base/browser/ui/button/button.js'; import { IListRenderer, IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; -import { IAction } from '../../../../../base/common/actions.js'; import { coalesce } from '../../../../../base/common/arrays.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; @@ -17,7 +16,7 @@ import { basenameOrAuthority, isEqualAuthority } from '../../../../../base/commo import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize, localize2 } from '../../../../../nls.js'; -import { createAndFillInContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -141,9 +140,7 @@ export class ChatCollapsibleListContentPart extends Disposable implements IChatC getAnchor: () => e.anchor, getActions: () => { const menu = menuService.getMenuActions(MenuId.ChatAttachmentsContext, list.contextKeyService, { shouldForwardArgs: true, arg: uri }); - const primary: IAction[] = []; - createAndFillInContextMenuActions(menu, primary); - return primary; + return getFlatContextMenuActions(menu); } }); })); diff --git a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts index aed5b07953210..669f08b918703 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts @@ -6,7 +6,6 @@ import * as dom from '../../../../base/browser/dom.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; -import { IAction } from '../../../../base/common/actions.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; @@ -25,7 +24,7 @@ import { ITextModelService } from '../../../../editor/common/services/resolverSe import { DefinitionAction } from '../../../../editor/contrib/gotoSymbol/browser/goToCommands.js'; import * as nls from '../../../../nls.js'; import { localize } from '../../../../nls.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; @@ -214,9 +213,7 @@ export class InlineAnchorWidget extends Disposable { getAnchor: () => event, getActions: () => { const menu = menuService.getMenuActions(contextMenuId, contextKeyService, { arg: contextMenuArg }); - const primary: IAction[] = []; - createAndFillInContextMenuActions(menu, primary); - return primary; + return getFlatContextMenuActions(menu); }, }); })); diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts index 486f5c4e3388c..1898237851ce9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentMenus.ts +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -4,11 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from '../../../../base/common/lifecycle.js'; -import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { IMenuService, MenuId, IMenu } from '../../../../platform/actions/common/actions.js'; -import { IAction } from '../../../../base/common/actions.js'; import { Comment } from '../../../../editor/common/languages.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IMenu, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; export class CommentMenus implements IDisposable { constructor( @@ -44,15 +42,7 @@ export class CommentMenus implements IDisposable { } private getMenu(menuId: MenuId, contextKeyService: IContextKeyService): IMenu { - const menu = this.menuService.createMenu(menuId, contextKeyService); - - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, 'inline'); - - return menu; + return this.menuService.createMenu(menuId, contextKeyService); } dispose(): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 0c58c66d95d25..69684b02b2da0 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -34,7 +34,7 @@ import { ILocalizedString } from '../../../../platform/action/common/action.js'; import { CommentsModel } from './commentsModel.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; -import { createActionViewItem, createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IAction } from '../../../../base/common/actions.js'; import { MarshalledId } from '../../../../base/common/marshallingIds.js'; @@ -169,12 +169,7 @@ export class CommentsMenus implements IDisposable { const contextKeyService = this.contextKeyService.createOverlay(overlay); const menu = this.menuService.getMenuActions(menuId, contextKeyService, { shouldForwardArgs: true }); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary, menu }; - createAndFillInContextMenuActions(menu, result, 'inline'); - - return result; + return getContextMenuActions(menu, 'inline'); } dispose() { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index cf094e01bd6af..643d1ece5a1d8 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -28,7 +28,7 @@ import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { localize, localize2 } from '../../../../nls.js'; -import { createAndFillInActionBarActions, createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createAndFillInActionBarActions, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -269,8 +269,7 @@ export class BreakpointsView extends ViewPane { this.breakpointSupportsCondition.set(conditionSupported); this.breakpointIsDataBytes.set(element instanceof DataBreakpoint && element.src.type === DataBreakpointSetType.Address); - const secondary: IAction[] = []; - createAndFillInContextMenuActions(this.menu, { arg: e.element, shouldForwardArgs: false }, { primary: [], secondary }, 'inline'); + const { secondary } = getContextMenuActions(this.menu.getActions({ arg: e.element, shouldForwardArgs: false }), 'inline'); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index c1f2e3e287eee..187eb123b7b2f 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -23,7 +23,7 @@ import { posix } from '../../../../base/common/path.js'; import { commonSuffixLength } from '../../../../base/common/strings.js'; import { localize } from '../../../../nls.js'; import { ICommandActionTitle, Icon } from '../../../../platform/action/common/action.js'; -import { createAndFillInActionBarActions, createAndFillInContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createAndFillInActionBarActions, getContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -461,12 +461,9 @@ export class CallStackView extends ViewPane { overlay = getStackFrameContextOverlay(element); } - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; const contextKeyService = this.contextKeyService.createOverlay(overlay); const menu = this.menuService.getMenuActions(MenuId.DebugCallStackContext, contextKeyService, { arg: getContextForContributedActions(element), shouldForwardArgs: true }); - createAndFillInContextMenuActions(menu, result, 'inline'); + const result = getContextMenuActions(menu, 'inline'); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => result.secondary, diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 613fed3ce95ba..35a30a941e5cd 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -39,7 +39,7 @@ import { ITextResourcePropertiesService } from '../../../../editor/common/servic import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js'; import { localize, localize2 } from '../../../../nls.js'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -772,8 +772,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { } private onContextMenu(e: ITreeContextMenuEvent): void { - const actions: IAction[] = []; - createAndFillInContextMenuActions(this.menu, { arg: e.element, shouldForwardArgs: false }, actions); + const actions = getFlatContextMenuActions(this.menu.getActions({ arg: e.element, shouldForwardArgs: false })); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 696d7eebc0ef6..a40de3b4a3648 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -19,7 +19,7 @@ import { FuzzyScore, createMatches } from '../../../../base/common/filters.js'; import { IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { localize } from '../../../../nls.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; @@ -250,8 +250,7 @@ export async function openContextMenuForVariableTreeElement(parentContextKeyServ const context: IVariablesContext = getVariablesContext(variable); const menu = menuService.getMenuActions(menuId, contextKeyService, { arg: context, shouldForwardArgs: false }); - const secondary: IAction[] = []; - createAndFillInContextMenuActions(menu, { primary: [], secondary }, 'inline'); + const { secondary } = getContextMenuActions(menu, 'inline'); contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => secondary @@ -496,8 +495,7 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { const context = viz.original ? getVariablesContext(viz.original) : undefined; const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false }); - const primary: IAction[] = []; - createAndFillInContextMenuActions(menu, { primary, secondary: [] }, 'inline'); + const { primary } = getContextMenuActions(menu, 'inline'); if (viz.original) { const action = new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.eye), true, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined)); @@ -572,10 +570,9 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { const variable = expression as Variable; const contextKeyService = getContextForVariableMenuBase(this.contextKeyService, variable); - const primary: IAction[] = []; const context = getVariablesContext(variable); const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false }); - createAndFillInContextMenuActions(menu, { primary, secondary: [] }, 'inline'); + const { primary } = getContextMenuActions(menu, 'inline'); actionBar.clear(); actionBar.context = context; diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 95d69163a663d..c785c370565a7 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -10,12 +10,11 @@ import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectTyp import { ElementsDragAndDropData, ListViewTargetSector } from '../../../../base/browser/ui/list/listView.js'; import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; import { ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeMouseEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; -import { IAction } from '../../../../base/common/actions.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { FuzzyScore } from '../../../../base/common/filters.js'; import { localize } from '../../../../nls.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -219,10 +218,9 @@ export class WatchExpressionsView extends ViewPane { const selection = this.tree.getSelection(); this.watchItemType.set(element instanceof Expression ? 'expression' : element instanceof Variable ? 'variable' : undefined); - const actions: IAction[] = []; const attributes = element instanceof Variable ? element.presentationHint?.attributes : undefined; this.variableReadonly.set(!!attributes && attributes.indexOf('readOnly') >= 0 || !!element?.presentationHint?.lazy); - createAndFillInContextMenuActions(this.menu, { arg: element, shouldForwardArgs: true }, actions); + const actions = getFlatContextMenuActions(this.menu.getActions({ arg: element, shouldForwardArgs: true })); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, @@ -379,8 +377,7 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { const context = expression; const menu = this.menuService.getMenuActions(MenuId.DebugWatchContext, contextKeyService, { arg: context, shouldForwardArgs: false }); - const primary: IAction[] = []; - createAndFillInContextMenuActions(menu, { primary, secondary: [] }, 'inline'); + const { primary } = getContextMenuActions(menu, 'inline'); actionBar.clear(); actionBar.context = context; diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index f6873bf5524f0..32f18eed45601 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -17,7 +17,7 @@ import { IDisposable, dispose } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import * as nls from '../../../../nls.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -497,9 +497,8 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } actions.push(new Separator()); - const menuActions = this._menuService.getMenuActions(MenuId.ExtensionEditorContextMenu, this.contextKeyService); - createAndFillInContextMenuActions(menuActions, { primary: [], secondary: actions }); + actions.push(...getContextMenuActions(menuActions,).secondary); this._contextMenuService.showContextMenu({ getAnchor: () => e.anchor, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index 3cc57762731f0..e6d84afad2f05 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { ITreeContextMenuEvent } from '../../../../../../base/browser/ui/tree/tree.js'; -import { IAction } from '../../../../../../base/common/actions.js'; import { RunOnceScheduler } from '../../../../../../base/common/async.js'; import { URI } from '../../../../../../base/common/uri.js'; import * as nls from '../../../../../../nls.js'; import { ILocalizedString } from '../../../../../../platform/action/common/action.js'; -import { createAndFillInContextMenuActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; @@ -121,7 +120,6 @@ export class NotebookVariablesView extends ViewPane { language: element.language, extensionId: element.extensionId }; - const actions: IAction[] = []; const overlayedContext = this.contextKeyService.createOverlay([ [CONTEXT_VARIABLE_NAME.key, element.name], @@ -131,8 +129,8 @@ export class NotebookVariablesView extends ViewPane { [CONTEXT_VARIABLE_LANGUAGE.key, element.language], [CONTEXT_VARIABLE_EXTENSIONID.key, element.extensionId] ]); - const menu = this.menuService.getMenuActions(MenuId.NotebookVariablesContext, overlayedContext, { arg, shouldForwardArgs: true }); - createAndFillInContextMenuActions(menu, actions); + const menuActions = this.menuService.getMenuActions(MenuId.NotebookVariablesContext, overlayedContext, { arg, shouldForwardArgs: true }); + const actions = getFlatContextMenuActions(menuActions); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index eba0d7372e759..8cd49f6b782e0 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -71,7 +71,7 @@ import { ColorScheme } from '../../../../platform/theme/common/theme.js'; import { LabelFuzzyScore } from '../../../../base/browser/ui/tree/abstractTree.js'; import { Selection } from '../../../../editor/common/core/selection.js'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js'; -import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, createAndFillInActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { Button, ButtonWithDescription, ButtonWithDropdown } from '../../../../base/browser/ui/button/button.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; @@ -2497,8 +2497,7 @@ export class SCMViewPane extends ViewPane { private onListContextMenu(e: ITreeContextMenuEvent): void { if (!e.element) { const menu = this.menuService.getMenuActions(Menus.ViewSort, this.contextKeyService); - const actions: IAction[] = []; - createAndFillInContextMenuActions(menu, actions); + const actions = getFlatContextMenuActions(menu); return this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 61a063c19317b..46801f922cff7 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -9,7 +9,7 @@ import { IMenu, MenuItemAction } from '../../../../platform/actions/common/actio import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; import { Action, IAction } from '../../../../base/common/actions.js'; -import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, createAndFillInActionBarActions, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { equals } from '../../../../base/common/arrays.js'; import { ActionViewItem, IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; @@ -96,10 +96,7 @@ export function connectPrimaryMenuToInlineActionBar(menu: IMenu, actionBar: Acti } export function collectContextMenuActions(menu: IMenu): IAction[] { - const primary: IAction[] = []; - const actions: IAction[] = []; - createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, { primary, secondary: actions }, 'inline'); - return actions; + return getContextMenuActions(menu.getActions({ shouldForwardArgs: true }), 'inline').secondary; } export class StatusBarAction extends Action { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts index eb3eab78e933f..16af6fc45e297 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts @@ -8,7 +8,7 @@ import { ActionRunner, IAction } from '../../../../base/common/actions.js'; import { asArray } from '../../../../base/common/arrays.js'; import { MarshalledId } from '../../../../base/common/marshallingIds.js'; import { SingleOrMany } from '../../../../base/common/types.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenu } from '../../../../platform/actions/common/actions.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { ITerminalInstance } from './terminal.js'; @@ -51,9 +51,7 @@ export class TerminalContextActionRunner extends ActionRunner { export function openContextMenu(targetWindow: Window, event: MouseEvent, contextInstances: SingleOrMany | undefined, menu: IMenu, contextMenuService: IContextMenuService, extraActions?: IAction[]): void { const standardEvent = new StandardMouseEvent(targetWindow, event); - const actions: IAction[] = []; - - createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, actions); + const actions = getFlatContextMenuActions(menu.getActions({ shouldForwardArgs: true })); if (extraActions) { actions.push(...extraActions); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index dd683b46fe2d4..903d5611bd81e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -32,7 +32,7 @@ import { TerminalTabbedView } from './terminalTabbedView.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { getColorForSeverity } from './terminalStatusList.js'; -import { createAndFillInContextMenuActions, MenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions, MenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { DropdownWithPrimaryActionViewItem } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; import { DisposableStore, dispose, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; @@ -268,8 +268,7 @@ export class TerminalViewPane extends ViewPane { } case TerminalCommandId.Focus: { if (action instanceof MenuItemAction) { - const actions: IAction[] = []; - createAndFillInContextMenuActions(this._singleTabMenu, { shouldForwardArgs: true }, actions); + const actions = getFlatContextMenuActions(this._singleTabMenu.getActions({ shouldForwardArgs: true })); return this._instantiationService.createInstance(SingleTerminalTabActionViewItem, action, actions); } } diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 2ba39540ddcf7..22111c77fe2b5 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -30,7 +30,7 @@ import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; import { GlyphMarginLane, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from '../../../../editor/common/model.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { localize } from '../../../../nls.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -966,11 +966,9 @@ abstract class RunTestDecoration { private getContributedTestActions(test: InternalTestItem, capabilities: number): IAction[] { const contextOverlay = this.contextKeyService.createOverlay(getTestItemContextOverlay(test, capabilities)); - const target: IAction[] = []; const arg = getContextForTestItem(this.testService.collection, test.item.extId); const menu = this.menuService.getMenuActions(MenuId.TestItemGutter, contextOverlay, { shouldForwardArgs: true, arg }); - createAndFillInContextMenuActions(menu, target); - return target; + return getFlatContextMenuActions(menu); } } diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 8528080e5e938..3cafc9409e865 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -30,7 +30,7 @@ import { URI } from '../../../../base/common/uri.js'; import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize } from '../../../../nls.js'; import { DropdownWithPrimaryActionViewItem } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; -import { MenuEntryActionViewItem, createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { MenuEntryActionViewItem, createActionViewItem, createAndFillInActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -374,7 +374,6 @@ export class TestingExplorerView extends ViewPane { } } - const menuActions: IAction[] = []; const contextKeys: [string, unknown][] = []; // allow extension author to define context for when to show the test menu actions for run or debug menus if (group === TestRunProfileBitset.Run) { @@ -390,7 +389,7 @@ export class TestingExplorerView extends ViewPane { const menu = this.menuService.getMenuActions(MenuId.TestProfilesContext, key); // fill if there are any actions - createAndFillInContextMenuActions(menu, menuActions); + const menuActions = getFlatContextMenuActions(menu); const postActions: IAction[] = []; if (profileActions.length > 1) { diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 60947efddb90c..d81e4c163ccfa 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -39,7 +39,7 @@ import { IViewDescriptorService } from '../../../common/views.js'; import { IProgressService } from '../../../../platform/progress/common/progress.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; -import { createAndFillInContextMenuActions, createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions, createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId, registerAction2, Action2, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; @@ -1317,12 +1317,7 @@ class TimelinePaneCommands extends Disposable { ]); const menu = this.menuService.getMenuActions(menuId, contextKeyService, { shouldForwardArgs: true }); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - createAndFillInContextMenuActions(menu, result, 'inline'); - - return result; + return getContextMenuActions(menu, 'inline'); } private updateTimelineSourceFilters() { diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts index 94321c78c7715..6bbdb1b7a317d 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts @@ -25,7 +25,7 @@ import { IPreferencesService } from '../../../services/preferences/common/prefer import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { OpenRecentAction } from '../../../browser/actions/windowActions.js'; import { isICommandActionToggleInfo } from '../../../../platform/action/common/action.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; export class NativeMenubarControl extends MenubarControl { @@ -93,8 +93,7 @@ export class NativeMenubarControl extends MenubarControl { const menu = this.menus[topLevelMenuName]; if (menu) { const menubarMenu: IMenubarMenu = { items: [] }; - const menuActions: IAction[] = []; - createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, menuActions); + const menuActions = getFlatContextMenuActions(menu.getActions({ shouldForwardArgs: true })); this.populateMenuItems(menuActions, menubarMenu, menubarData.keybindings); if (menubarMenu.items.length === 0) { return false; // Menus are incomplete From 98b23191eeac37aa4724f4f26d27a0f695bbadab Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 30 Oct 2024 11:43:30 +0100 Subject: [PATCH 078/555] missing `(next|previous)AuxilaryBarView` commands (fix #154724) (#232577) * missing `(next|previous)AuxilaryBarView` commands (fix #154724) * address feedback --- .../parts/activitybar/activitybarPart.ts | 74 +++++-------------- .../parts/auxiliarybar/auxiliaryBarActions.ts | 24 +++++- .../browser/parts/compositeBarActions.ts | 38 +++++++++- .../browser/parts/panel/panelActions.ts | 56 ++++---------- 4 files changed, 94 insertions(+), 98 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 6a7193a6e1554..0935255446a36 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -29,15 +29,15 @@ import { IPaneCompositePart } from '../paneCompositePart.js'; import { IPaneCompositeBarOptions, PaneCompositeBar } from '../paneCompositeBar.js'; import { GlobalCompositeBar } from '../globalCompositeBar.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; -import { Action2, IAction2Options, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IViewDescriptorService, ViewContainerLocation, ViewContainerLocationToString } from '../../../common/views.js'; -import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { SwitchCompositeViewAction } from '../compositeBarActions.js'; export class ActivitybarPart extends Part { @@ -506,61 +506,27 @@ MenuRegistry.appendMenuItem(MenuId.ViewTitleContext, { order: 1 }); -class SwitchSideBarViewAction extends Action2 { - - constructor( - desc: Readonly, - private readonly offset: number - ) { - super(desc); - } - - async run(accessor: ServicesAccessor): Promise { - const paneCompositeService = accessor.get(IPaneCompositePartService); - - const visibleViewletIds = paneCompositeService.getVisiblePaneCompositeIds(ViewContainerLocation.Sidebar); - - const activeViewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar); - if (!activeViewlet) { - return; - } - let targetViewletId: string | undefined; - for (let i = 0; i < visibleViewletIds.length; i++) { - if (visibleViewletIds[i] === activeViewlet.getId()) { - targetViewletId = visibleViewletIds[(i + visibleViewletIds.length + this.offset) % visibleViewletIds.length]; - break; - } - } - - await paneCompositeService.openPaneComposite(targetViewletId, ViewContainerLocation.Sidebar, true); - } -} - -registerAction2( - class PreviousSideBarViewAction extends SwitchSideBarViewAction { - constructor() { - super({ - id: 'workbench.action.previousSideBarView', - title: localize2('previousSideBarView', 'Previous Primary Side Bar View'), - category: Categories.View, - f1: true - }, -1); - } +registerAction2(class extends SwitchCompositeViewAction { + constructor() { + super({ + id: 'workbench.action.previousSideBarView', + title: localize2('previousSideBarView', 'Previous Primary Side Bar View'), + category: Categories.View, + f1: true + }, ViewContainerLocation.Sidebar, -1); } -); +}); -registerAction2( - class NextSideBarViewAction extends SwitchSideBarViewAction { - constructor() { - super({ - id: 'workbench.action.nextSideBarView', - title: localize2('nextSideBarView', 'Next Primary Side Bar View'), - category: Categories.View, - f1: true - }, 1); - } +registerAction2(class extends SwitchCompositeViewAction { + constructor() { + super({ + id: 'workbench.action.nextSideBarView', + title: localize2('nextSideBarView', 'Next Primary Side Bar View'), + category: Categories.View, + f1: true + }, ViewContainerLocation.Sidebar, 1); } -); +}); registerAction2( class FocusActivityBarAction extends Action2 { diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts index b2c7e5f01c77a..4d24cad6198c4 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts @@ -16,7 +16,7 @@ import { IPaneCompositePartService } from '../../../services/panecomposite/brows import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; - +import { SwitchCompositeViewAction } from '../compositeBarActions.js'; const auxiliaryBarRightIcon = registerIcon('auxiliarybar-right-layout-icon', Codicon.layoutSidebarRight, localize('toggleAuxiliaryIconRight', 'Icon to toggle the auxiliary bar off in its right position.')); const auxiliaryBarRightOffIcon = registerIcon('auxiliarybar-right-off-layout-icon', Codicon.layoutSidebarRightOff, localize('toggleAuxiliaryIconRightOn', 'Icon to toggle the auxiliary bar on in its right position.')); @@ -136,3 +136,25 @@ MenuRegistry.appendMenuItems([ } } ]); + +registerAction2(class extends SwitchCompositeViewAction { + constructor() { + super({ + id: 'workbench.action.previousAuxiliaryBarView', + title: localize2('previousAuxiliaryBarView', 'Previous Secondary Side Bar View'), + category: Categories.View, + f1: true + }, ViewContainerLocation.AuxiliaryBar, -1); + } +}); + +registerAction2(class extends SwitchCompositeViewAction { + constructor() { + super({ + id: 'workbench.action.nextAuxiliaryBarView', + title: localize2('nextAuxiliaryBarView', 'Next Secondary Side Bar View'), + category: Categories.View, + f1: true + }, ViewContainerLocation.AuxiliaryBar, 1); + } +}); diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 89876b6dffc61..deb1385838729 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -11,7 +11,7 @@ import { toDisposable, DisposableStore, MutableDisposable } from '../../../base/ import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js'; import { IThemeService, IColorTheme } from '../../../platform/theme/common/themeService.js'; import { NumberBadge, IBadge, IActivity, ProgressBadge, IconBadge } from '../../services/activity/common/activity.js'; -import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js'; import { DelayedDragHandler } from '../../../base/browser/dnd.js'; import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js'; import { Emitter, Event } from '../../../base/common/event.js'; @@ -27,6 +27,9 @@ import { HoverPosition } from '../../../base/browser/ui/hover/hoverWidget.js'; import { URI } from '../../../base/common/uri.js'; import { badgeBackground, badgeForeground, contrastBorder } from '../../../platform/theme/common/colorRegistry.js'; import type { IHoverWidget } from '../../../base/browser/ui/hover/hover.js'; +import { Action2, IAction2Options } from '../../../platform/actions/common/actions.js'; +import { ViewContainerLocation } from '../../common/views.js'; +import { IPaneCompositePartService } from '../../services/panecomposite/browser/panecomposite.js'; export interface ICompositeBar { @@ -798,3 +801,36 @@ export class ToggleCompositeBadgeAction extends Action { this.compositeBar.toggleBadgeEnablement(id); } } + +export class SwitchCompositeViewAction extends Action2 { + constructor( + desc: Readonly, + private readonly location: ViewContainerLocation, + private readonly offset: number + ) { + super(desc); + } + + async run(accessor: ServicesAccessor): Promise { + const paneCompositeService = accessor.get(IPaneCompositePartService); + + const activeComposite = paneCompositeService.getActivePaneComposite(this.location); + if (!activeComposite) { + return; + } + + let targetCompositeId: string | undefined; + + const visibleCompositeIds = paneCompositeService.getVisiblePaneCompositeIds(this.location); + for (let i = 0; i < visibleCompositeIds.length; i++) { + if (visibleCompositeIds[i] === activeComposite.getId()) { + targetCompositeId = visibleCompositeIds[(i + visibleCompositeIds.length + this.offset) % visibleCompositeIds.length]; + break; + } + } + + if (typeof targetCompositeId !== 'undefined') { + await paneCompositeService.openPaneComposite(targetCompositeId, this.location, true); + } + } +} diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 77ac81eea4440..b67259588c838 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -20,6 +20,7 @@ import { IPaneCompositePartService } from '../../../services/panecomposite/brows import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { ICommandActionTitle } from '../../../../platform/action/common/action.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { SwitchCompositeViewAction } from '../compositeBarActions.js'; const maximizeIcon = registerIcon('panel-maximize', Codicon.chevronUp, localize('maximizeIcon', 'Icon to maximize a panel.')); const restoreIcon = registerIcon('panel-restore', Codicon.chevronDown, localize('restoreIcon', 'Icon to restore a panel.')); @@ -223,54 +224,25 @@ AlignPanelActionConfigs.forEach(alignPanelAction => { }); }); -class SwitchPanelViewAction extends Action2 { - - constructor(id: string, title: ICommandActionTitle) { +registerAction2(class extends SwitchCompositeViewAction { + constructor() { super({ - id, - title, + id: 'workbench.action.previousPanelView', + title: localize2('previousPanelView', "Previous Panel View"), category: Categories.View, - f1: true, - }); - } - - override async run(accessor: ServicesAccessor, offset: number): Promise { - const paneCompositeService = accessor.get(IPaneCompositePartService); - const pinnedPanels = paneCompositeService.getVisiblePaneCompositeIds(ViewContainerLocation.Panel); - const activePanel = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel); - if (!activePanel) { - return; - } - let targetPanelId: string | undefined; - for (let i = 0; i < pinnedPanels.length; i++) { - if (pinnedPanels[i] === activePanel.getId()) { - targetPanelId = pinnedPanels[(i + pinnedPanels.length + offset) % pinnedPanels.length]; - break; - } - } - if (typeof targetPanelId === 'string') { - await paneCompositeService.openPaneComposite(targetPanelId, ViewContainerLocation.Panel, true); - } - } -} - -registerAction2(class extends SwitchPanelViewAction { - constructor() { - super('workbench.action.previousPanelView', localize2('previousPanelView', "Previous Panel View")); - } - - override run(accessor: ServicesAccessor): Promise { - return super.run(accessor, -1); + f1: true + }, ViewContainerLocation.Panel, -1); } }); -registerAction2(class extends SwitchPanelViewAction { +registerAction2(class extends SwitchCompositeViewAction { constructor() { - super('workbench.action.nextPanelView', localize2('nextPanelView', "Next Panel View")); - } - - override run(accessor: ServicesAccessor): Promise { - return super.run(accessor, 1); + super({ + id: 'workbench.action.nextPanelView', + title: localize2('nextPanelView', "Next Panel View"), + category: Categories.View, + f1: true + }, ViewContainerLocation.Panel, 1); } }); From ec9dfa4d214fe18afca2d227bb42af32ff32106b Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:54:36 +0100 Subject: [PATCH 079/555] SCM - add the ability to hide resource group menus (#232585) --- .../contrib/scm/browser/scmViewPane.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 8cd49f6b782e0..b6c02d72fa623 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -24,7 +24,7 @@ import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options, import { IAction, ActionRunner, Action, Separator, IActionRunner } from '../../../../base/common/actions.js'; import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IThemeService, IFileIconTheme } from '../../../../platform/theme/common/themeService.js'; -import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMResourceNode } from './util.js'; +import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMResourceNode, connectPrimaryMenu } from './util.js'; import { WorkbenchCompressibleAsyncDataTree, IOpenEvent } from '../../../../platform/list/browser/listService.js'; import { IConfigurationService, ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js'; import { disposableTimeout, Sequencer, ThrottledDelayer, Throttler } from '../../../../base/common/async.js'; @@ -417,7 +417,7 @@ class InputRenderer implements ICompressibleTreeRenderer, index: number, template: ResourceGroupTemplate): void { const group = node.element; template.name.textContent = group.label; - template.actionBar.clear(); - template.actionBar.context = group; template.count.setCount(group.resources.length); const menus = this.scmViewService.menus.getRepositoryMenus(group.provider); - template.elementDisposables.add(connectPrimaryMenuToInlineActionBar(menus.getResourceGroupMenu(group), template.actionBar)); + template.elementDisposables.add(connectPrimaryMenu(menus.getResourceGroupMenu(group), primary => { + template.actionBar.setActions(primary); + }, 'inline')); + template.actionBar.context = group; } renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: ResourceGroupTemplate, height: number | undefined): void { From 3e20dedaf51e9d6142176d2d2c1d62b28228243a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 30 Oct 2024 12:18:37 +0100 Subject: [PATCH 080/555] Set the color picker to undefined on hover hide in the color hover participant (#232586) handle the hide event --- .../browser/hoverColorPicker/hoverColorPickerParticipant.ts | 5 +++++ .../contrib/hover/browser/contentHoverWidgetWrapper.ts | 1 + src/vs/editor/contrib/hover/browser/hoverTypes.ts | 1 + 3 files changed, 7 insertions(+) diff --git a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts index 7c7d4afeb0689..d2e1179bea0b5 100644 --- a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts +++ b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts @@ -142,6 +142,11 @@ export class HoverColorPickerParticipant implements IEditorHoverParticipant participant.handleHide?.()); } private _getHoverContext(): IEditorHoverContext { diff --git a/src/vs/editor/contrib/hover/browser/hoverTypes.ts b/src/vs/editor/contrib/hover/browser/hoverTypes.ts index b557fce3ba3ab..7c1b9503bd3bd 100644 --- a/src/vs/editor/contrib/hover/browser/hoverTypes.ts +++ b/src/vs/editor/contrib/hover/browser/hoverTypes.ts @@ -161,6 +161,7 @@ export interface IEditorHoverParticipant { renderHoverParts(context: IEditorHoverRenderContext, hoverParts: T[]): IRenderedHoverParts; getAccessibleContent(hoverPart: T): string; handleResize?(): void; + handleHide?(): void; } export type IEditorHoverParticipantCtor = IConstructorSignature; From 26fd13e692f97cd303b3d98238da313c49ef0f53 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:29:08 +0100 Subject: [PATCH 081/555] SCM - add the ability to hide resource actions (#232587) --- .../contrib/scm/browser/scmViewPane.ts | 20 ++++++++++++------- src/vs/workbench/contrib/scm/browser/util.ts | 9 +-------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index b6c02d72fa623..d531945df81f3 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -24,7 +24,7 @@ import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options, import { IAction, ActionRunner, Action, Separator, IActionRunner } from '../../../../base/common/actions.js'; import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IThemeService, IFileIconTheme } from '../../../../platform/theme/common/themeService.js'; -import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMResourceNode, connectPrimaryMenu } from './util.js'; +import { isSCMResource, isSCMResourceGroup, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMResourceNode, connectPrimaryMenu } from './util.js'; import { WorkbenchCompressibleAsyncDataTree, IOpenEvent } from '../../../../platform/list/browser/listService.js'; import { IConfigurationService, ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js'; import { disposableTimeout, Sequencer, ThrottledDelayer, Throttler } from '../../../../base/common/async.js'; @@ -484,7 +484,7 @@ interface ResourceTemplate { name: HTMLElement; fileLabel: IResourceLabel; decorationIcon: HTMLElement; - actionBar: ActionBar; + actionBar: WorkbenchToolBar; actionBarMenu: IMenu | undefined; readonly actionBarMenuListener: MutableDisposable; readonly elementDisposables: DisposableStore; @@ -530,8 +530,14 @@ class ResourceRenderer implements ICompressibleTreeRenderer(); @@ -642,10 +648,10 @@ class ResourceRenderer implements ICompressibleTreeRenderer, menu: IMenu): void { if (!template.actionBarMenu || template.actionBarMenu !== menu) { - template.actionBar.clear(); - template.actionBarMenu = menu; - template.actionBarMenuListener.value = connectPrimaryMenuToInlineActionBar(menu, template.actionBar); + template.actionBarMenuListener.value = connectPrimaryMenu(menu, primary => { + template.actionBar.setActions(primary); + }, 'inline'); } template.actionBar.context = resourceOrFolder; diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 46801f922cff7..3406a15685b08 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -6,7 +6,7 @@ import { ISCMHistoryItem, ISCMHistoryItemRef, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput, ISCMActionButton, ISCMViewService, ISCMProvider } from '../common/scm.js'; import { IMenu, MenuItemAction } from '../../../../platform/actions/common/actions.js'; -import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; import { Action, IAction } from '../../../../base/common/actions.js'; import { createActionViewItem, createAndFillInActionBarActions, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; @@ -88,13 +88,6 @@ export function connectPrimaryMenu(menu: IMenu, callback: (primary: IAction[], s return menu.onDidChange(updateActions); } -export function connectPrimaryMenuToInlineActionBar(menu: IMenu, actionBar: ActionBar): IDisposable { - return connectPrimaryMenu(menu, (primary) => { - actionBar.clear(); - actionBar.push(primary, { icon: true, label: false }); - }, 'inline'); -} - export function collectContextMenuActions(menu: IMenu): IAction[] { return getContextMenuActions(menu.getActions({ shouldForwardArgs: true }), 'inline').secondary; } From a295ee87fa517bb5ccbc721d91092918b5ea9039 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 30 Oct 2024 12:52:11 +0100 Subject: [PATCH 082/555] Internal view container ID surfaced when view moved to alternate side bar (fix #201085) (#232591) --- src/vs/workbench/services/views/browser/viewsService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/services/views/browser/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts index 7970ae8a4ac70..7f27a3bc47083 100644 --- a/src/vs/workbench/services/views/browser/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -541,7 +541,6 @@ export class ViewsService extends Disposable implements IViewsService { super({ id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, title, - category, menu: [{ id: MenuId.CommandPalette, when: viewDescriptor.when, From 09ba66b21b65ced16b17f6c5ed8bc2b0a9cbcd16 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 30 Oct 2024 12:54:33 +0100 Subject: [PATCH 083/555] refactoring: Introducing new enum ColorPickerWidgetType to differentiate between hover color picker and standalone color picker (#232594) introducing new enum ColorPickerWidgetType to differentiate between hover color picker and standalone color picker --- .../browser/colorPickerParticipantUtils.ts | 5 ++++ .../colorPickerParts/colorPickerBody.ts | 9 ++++--- .../colorPickerParts/colorPickerHeader.ts | 5 ++-- .../colorPickerParts/colorPickerStrip.ts | 13 +++++----- ...orPickerWidget.ts => colorPickerWidget.ts} | 25 ++++++++++--------- .../hoverColorPickerParticipant.ts | 6 ++--- .../standaloneColorPickerParticipant.ts | 6 ++--- 7 files changed, 39 insertions(+), 30 deletions(-) rename src/vs/editor/contrib/colorPicker/browser/{hoverColorPicker/hoverColorPickerWidget.ts => colorPickerWidget.ts} (58%) diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerParticipantUtils.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerParticipantUtils.ts index 0975790a57a86..6c0744229c754 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerParticipantUtils.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerParticipantUtils.ts @@ -13,6 +13,11 @@ import { getColorPresentations } from './color.js'; import { ColorPickerModel } from './colorPickerModel.js'; import { Range } from '../../../common/core/range.js'; +export const enum ColorPickerWidgetType { + Hover = 'hover', + Standalone = 'standalone' +} + export interface BaseColor { readonly range: Range; readonly model: ColorPickerModel; diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerBody.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerBody.ts index aa8c542882691..5655055ab6454 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerBody.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerBody.ts @@ -10,6 +10,7 @@ import { ColorPickerModel } from '../colorPickerModel.js'; import { SaturationBox } from './colorPickerSaturationBox.js'; import { InsertButton } from './colorPickerInsertButton.js'; import { HueStrip, OpacityStrip, Strip } from './colorPickerStrip.js'; +import { ColorPickerWidgetType } from '../colorPickerParticipantUtils.js'; const $ = dom.$; @@ -21,7 +22,7 @@ export class ColorPickerBody extends Disposable { private readonly _opacityStrip: Strip; private readonly _insertButton: InsertButton | null = null; - constructor(container: HTMLElement, private readonly model: ColorPickerModel, private pixelRatio: number, isStandaloneColorPicker: boolean = false) { + constructor(container: HTMLElement, private readonly model: ColorPickerModel, private pixelRatio: number, type: ColorPickerWidgetType) { super(); this._domNode = $('.colorpicker-body'); @@ -32,17 +33,17 @@ export class ColorPickerBody extends Disposable { this._register(this._saturationBox.onDidChange(this.onDidSaturationValueChange, this)); this._register(this._saturationBox.onColorFlushed(this.flushColor, this)); - this._opacityStrip = new OpacityStrip(this._domNode, this.model, isStandaloneColorPicker); + this._opacityStrip = new OpacityStrip(this._domNode, this.model, type); this._register(this._opacityStrip); this._register(this._opacityStrip.onDidChange(this.onDidOpacityChange, this)); this._register(this._opacityStrip.onColorFlushed(this.flushColor, this)); - this._hueStrip = new HueStrip(this._domNode, this.model, isStandaloneColorPicker); + this._hueStrip = new HueStrip(this._domNode, this.model, type); this._register(this._hueStrip); this._register(this._hueStrip.onDidChange(this.onDidHueChange, this)); this._register(this._hueStrip.onColorFlushed(this.flushColor, this)); - if (isStandaloneColorPicker) { + if (type === ColorPickerWidgetType.Standalone) { this._insertButton = this._register(new InsertButton(this._domNode)); this._domNode.classList.add('standalone-colorpicker'); } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerHeader.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerHeader.ts index 92cd6c65e68e1..8522f41e361e9 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerHeader.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerHeader.ts @@ -12,6 +12,7 @@ import { localize } from '../../../../../nls.js'; import { editorHoverBackground } from '../../../../../platform/theme/common/colorRegistry.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; import { CloseButton } from './colorPickerCloseButton.js'; +import { ColorPickerWidgetType } from '../colorPickerParticipantUtils.js'; const $ = dom.$; @@ -24,7 +25,7 @@ export class ColorPickerHeader extends Disposable { private readonly _closeButton: CloseButton | null = null; private backgroundColor: Color; - constructor(container: HTMLElement, private readonly model: ColorPickerModel, themeService: IThemeService, private showingStandaloneColorPicker: boolean = false) { + constructor(container: HTMLElement, private readonly model: ColorPickerModel, themeService: IThemeService, private type: ColorPickerWidgetType) { super(); this._domNode = $('.colorpicker-header'); @@ -59,7 +60,7 @@ export class ColorPickerHeader extends Disposable { this.onDidChangeColor(this.model.color); // When the color picker widget is a standalone color picker widget, then add a close button - if (this.showingStandaloneColorPicker) { + if (this.type === ColorPickerWidgetType.Standalone) { this._domNode.classList.add('standalone-colorpicker'); this._closeButton = this._register(new CloseButton(this._domNode)); } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerStrip.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerStrip.ts index ff72ddf8732ca..5702c8e4bcd0e 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerStrip.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerStrip.ts @@ -9,6 +9,7 @@ import { Color, RGBA } from '../../../../../base/common/color.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { ColorPickerModel } from '../colorPickerModel.js'; +import { ColorPickerWidgetType } from '../colorPickerParticipantUtils.js'; const $ = dom.$; @@ -25,9 +26,9 @@ export abstract class Strip extends Disposable { private readonly _onColorFlushed = new Emitter(); readonly onColorFlushed: Event = this._onColorFlushed.event; - constructor(container: HTMLElement, protected model: ColorPickerModel, showingStandaloneColorPicker: boolean = false) { + constructor(container: HTMLElement, protected model: ColorPickerModel, type: ColorPickerWidgetType) { super(); - if (showingStandaloneColorPicker) { + if (type === ColorPickerWidgetType.Standalone) { this.domNode = dom.append(container, $('.standalone-strip')); this.overlay = dom.append(this.domNode, $('.standalone-overlay')); } else { @@ -92,8 +93,8 @@ export abstract class Strip extends Disposable { export class OpacityStrip extends Strip { - constructor(container: HTMLElement, model: ColorPickerModel, showingStandaloneColorPicker: boolean = false) { - super(container, model, showingStandaloneColorPicker); + constructor(container: HTMLElement, model: ColorPickerModel, type: ColorPickerWidgetType) { + super(container, model, type); this.domNode.classList.add('opacity-strip'); this.onDidChangeColor(this.model.color); @@ -115,8 +116,8 @@ export class OpacityStrip extends Strip { export class HueStrip extends Strip { - constructor(container: HTMLElement, model: ColorPickerModel, showingStandaloneColorPicker: boolean = false) { - super(container, model, showingStandaloneColorPicker); + constructor(container: HTMLElement, model: ColorPickerModel, type: ColorPickerWidgetType) { + super(container, model, type); this.domNode.classList.add('hue-strip'); } diff --git a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts similarity index 58% rename from src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerWidget.ts rename to src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index 9ba946953f955..cc56c1bf12f3e 100644 --- a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import '../colorPicker.css'; -import { PixelRatio } from '../../../../../base/browser/pixelRatio.js'; -import * as dom from '../../../../../base/browser/dom.js'; -import { Widget } from '../../../../../base/browser/ui/widget.js'; -import { ColorPickerModel } from '../colorPickerModel.js'; -import { IEditorHoverColorPickerWidget } from '../../../hover/browser/hoverTypes.js'; -import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; -import { ColorPickerBody } from '../colorPickerParts/colorPickerBody.js'; -import { ColorPickerHeader } from '../colorPickerParts/colorPickerHeader.js'; +import './colorPicker.css'; +import { PixelRatio } from '../../../../base/browser/pixelRatio.js'; +import * as dom from '../../../../base/browser/dom.js'; +import { Widget } from '../../../../base/browser/ui/widget.js'; +import { ColorPickerModel } from './colorPickerModel.js'; +import { IEditorHoverColorPickerWidget } from '../../hover/browser/hoverTypes.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ColorPickerBody } from './colorPickerParts/colorPickerBody.js'; +import { ColorPickerHeader } from './colorPickerParts/colorPickerHeader.js'; +import { ColorPickerWidgetType } from './colorPickerParticipantUtils.js'; const $ = dom.$; @@ -23,7 +24,7 @@ export class ColorPickerWidget extends Widget implements IEditorHoverColorPicker body: ColorPickerBody; header: ColorPickerHeader; - constructor(container: Node, readonly model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService, standaloneColorPicker: boolean = false) { + constructor(container: Node, readonly model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService, type: ColorPickerWidgetType) { super(); this._register(PixelRatio.getInstance(dom.getWindow(container)).onDidChange(() => this.layout())); @@ -31,8 +32,8 @@ export class ColorPickerWidget extends Widget implements IEditorHoverColorPicker this._domNode = $('.colorpicker-widget'); container.appendChild(this._domNode); - this.header = this._register(new ColorPickerHeader(this._domNode, this.model, themeService, standaloneColorPicker)); - this.body = this._register(new ColorPickerBody(this._domNode, this.model, this.pixelRatio, standaloneColorPicker)); + this.header = this._register(new ColorPickerHeader(this._domNode, this.model, themeService, type)); + this.body = this._register(new ColorPickerBody(this._domNode, this.model, this.pixelRatio, type)); } getId(): string { diff --git a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts index d2e1179bea0b5..3b478cd397b50 100644 --- a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts +++ b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts @@ -11,11 +11,11 @@ import { IModelDecoration } from '../../../../common/model.js'; import { DocumentColorProvider } from '../../../../common/languages.js'; import { ColorDetector } from '../colorDetector.js'; import { ColorPickerModel } from '../colorPickerModel.js'; -import { ColorPickerWidget } from './hoverColorPickerWidget.js'; +import { ColorPickerWidget } from '../colorPickerWidget.js'; import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from '../../../hover/browser/hoverTypes.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; import * as nls from '../../../../../nls.js'; -import { BaseColor, createColorHover, updateColorPresentations, updateEditorModel } from '../colorPickerParticipantUtils.js'; +import { BaseColor, ColorPickerWidgetType, createColorHover, updateColorPresentations, updateEditorModel } from '../colorPickerParticipantUtils.js'; import { EditorOption } from '../../../../common/config/editorOptions.js'; import { Dimension } from '../../../../../base/browser/dom.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; @@ -105,7 +105,7 @@ export class HoverColorPickerParticipant implements IEditorHoverParticipant Date: Wed, 30 Oct 2024 14:01:24 +0100 Subject: [PATCH 084/555] Preventing status bar actions from overflowing (#232598) stopping status bar actions from overflowing --- src/vs/base/browser/ui/hover/hoverWidget.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/base/browser/ui/hover/hoverWidget.css b/src/vs/base/browser/ui/hover/hoverWidget.css index d69122654e579..f2963ced5c3c2 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.css +++ b/src/vs/base/browser/ui/hover/hoverWidget.css @@ -128,6 +128,9 @@ .monaco-hover .hover-row.status-bar .actions .action-container { margin-right: 16px; cursor: pointer; + overflow: hidden; + text-wrap: nowrap; + text-overflow: ellipsis; } .monaco-hover .hover-row.status-bar .actions .action-container .action .icon { From 10800de4546730cca2e5378570db308774b8ae99 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 30 Oct 2024 14:31:20 +0100 Subject: [PATCH 085/555] Possibly unintentional theme property warning (#232601) --- .../contrib/extensions/browser/extensionsWidgets.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 417c7bba2a767..0941ec92b5b65 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -837,10 +837,10 @@ export class ExtensionRecommendationWidget extends ExtensionWidget { } } -export const extensionRatingIconColor = registerColor('extensionIcon.starForeground', { light: '#DF6100', dark: '#FF8E00', hcDark: '#FF8E00', hcLight: textLinkForeground }, localize('extensionIconStarForeground', "The icon color for extension ratings."), true); -export const extensionVerifiedPublisherIconColor = registerColor('extensionIcon.verifiedForeground', textLinkForeground, localize('extensionIconVerifiedForeground', "The icon color for extension verified publisher."), true); -export const extensionPreReleaseIconColor = registerColor('extensionIcon.preReleaseForeground', { dark: '#1d9271', light: '#1d9271', hcDark: '#1d9271', hcLight: textLinkForeground }, localize('extensionPreReleaseForeground', "The icon color for pre-release extension."), true); -export const extensionSponsorIconColor = registerColor('extensionIcon.sponsorForeground', { light: '#B51E78', dark: '#D758B3', hcDark: null, hcLight: '#B51E78' }, localize('extensionIcon.sponsorForeground', "The icon color for extension sponsor."), true); +export const extensionRatingIconColor = registerColor('extensionIcon.starForeground', { light: '#DF6100', dark: '#FF8E00', hcDark: '#FF8E00', hcLight: textLinkForeground }, localize('extensionIconStarForeground', "The icon color for extension ratings."), false); +export const extensionVerifiedPublisherIconColor = registerColor('extensionIcon.verifiedForeground', textLinkForeground, localize('extensionIconVerifiedForeground', "The icon color for extension verified publisher."), false); +export const extensionPreReleaseIconColor = registerColor('extensionIcon.preReleaseForeground', { dark: '#1d9271', light: '#1d9271', hcDark: '#1d9271', hcLight: textLinkForeground }, localize('extensionPreReleaseForeground', "The icon color for pre-release extension."), false); +export const extensionSponsorIconColor = registerColor('extensionIcon.sponsorForeground', { light: '#B51E78', dark: '#D758B3', hcDark: null, hcLight: '#B51E78' }, localize('extensionIcon.sponsorForeground', "The icon color for extension sponsor."), false); registerThemingParticipant((theme, collector) => { const extensionRatingIcon = theme.getColor(extensionRatingIconColor); From a3e0ecec10afac201a8a042d187c459d33fa9d3b Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 30 Oct 2024 14:31:51 +0100 Subject: [PATCH 086/555] Adding custom hover on the editor hover actions to display the action label on hover (#232600) adding custom hover on the editor hover actions --- src/vs/base/browser/ui/hover/hoverWidget.ts | 7 +++++-- .../standaloneColorPickerWidget.ts | 4 +++- .../hover/browser/contentHoverRendered.ts | 18 +++++++++++------- .../hover/browser/contentHoverStatusBar.ts | 4 ++++ .../hover/browser/contentHoverWidgetWrapper.ts | 4 +++- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index 638af90bd8ea9..3da3b884a8855 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -53,7 +53,9 @@ export class HoverAction extends Disposable { public readonly actionLabel: string; public readonly actionKeybindingLabel: string | null; - private readonly actionContainer: HTMLElement; + public readonly actionRenderedLabel: string; + public readonly actionContainer: HTMLElement; + private readonly action: HTMLElement; private constructor(parent: HTMLElement, actionOptions: { label: string; iconClass?: string; run: (target: HTMLElement) => void; commandId: string }, keybindingLabel: string | null) { @@ -70,8 +72,9 @@ export class HoverAction extends Disposable { if (actionOptions.iconClass) { dom.append(this.action, $(`span.icon.${actionOptions.iconClass}`)); } + this.actionRenderedLabel = keybindingLabel ? `${actionOptions.label} (${keybindingLabel})` : actionOptions.label; const label = dom.append(this.action, $('span')); - label.textContent = keybindingLabel ? `${actionOptions.label} (${keybindingLabel})` : actionOptions.label; + label.textContent = this.actionRenderedLabel; this._store.add(new ClickAction(this.actionContainer, actionOptions.run)); this._store.add(new KeyDownAction(this.actionContainer, actionOptions.run, [KeyCode.Enter, KeyCode.Space])); diff --git a/src/vs/editor/contrib/colorPicker/browser/standaloneColorPicker/standaloneColorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/standaloneColorPicker/standaloneColorPickerWidget.ts index 1a00747687622..8fcb698b9403f 100644 --- a/src/vs/editor/contrib/colorPicker/browser/standaloneColorPicker/standaloneColorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/standaloneColorPicker/standaloneColorPickerWidget.ts @@ -23,6 +23,7 @@ import { IEditorWorkerService } from '../../../../common/services/editorWorker.j import { StandaloneColorPickerHover, StandaloneColorPickerParticipant } from './standaloneColorPickerParticipant.js'; import * as dom from '../../../../../base/browser/dom.js'; import { InsertButton } from '../colorPickerParts/colorPickerInsertButton.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; class StandaloneColorPickerResult { // The color picker result consists of: an array of color results and a boolean indicating if the color was found in the editor @@ -58,6 +59,7 @@ export class StandaloneColorPickerWidget extends Disposable implements IContentW @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, + @IHoverService private readonly _hoverService: IHoverService ) { super(); this._standaloneColorPickerVisible.set(true); @@ -166,7 +168,7 @@ export class StandaloneColorPickerWidget extends Disposable implements IContentW private _render(colorHover: StandaloneColorPickerHover, foundInEditor: boolean) { const fragment = document.createDocumentFragment(); - const statusBar = this._register(new EditorHoverStatusBar(this._keybindingService)); + const statusBar = this._register(new EditorHoverStatusBar(this._keybindingService, this._hoverService)); const context: IEditorHoverRenderContext = { fragment, diff --git a/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts b/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts index af9f9f7397bb5..c72ecfb5910bd 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts @@ -21,6 +21,7 @@ import { localize } from '../../../../nls.js'; import { InlayHintsHover } from '../../inlayHints/browser/inlayHintsHover.js'; import { BugIndicatingError } from '../../../../base/common/errors.js'; import { HoverAction } from '../../../../base/browser/ui/hover/hoverWidget.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; export class RenderedContentHover extends Disposable { @@ -41,7 +42,8 @@ export class RenderedContentHover extends Disposable { hoverResult: ContentHoverResult, participants: IEditorHoverParticipant[], context: IEditorHoverContext, - keybindingService: IKeybindingService + @IKeybindingService keybindingService: IKeybindingService, + @IHoverService hoverService: IHoverService ) { super(); const parts = hoverResult.hoverParts; @@ -49,8 +51,9 @@ export class RenderedContentHover extends Disposable { editor, participants, parts, + context, keybindingService, - context + hoverService )); const contentHoverComputerOptions = hoverResult.options; const anchor = contentHoverComputerOptions.anchor; @@ -225,13 +228,14 @@ class RenderedContentHoverParts extends Disposable { editor: ICodeEditor, participants: IEditorHoverParticipant[], hoverParts: IHoverPart[], - keybindingService: IKeybindingService, - context: IEditorHoverContext + context: IEditorHoverContext, + @IKeybindingService keybindingService: IKeybindingService, + @IHoverService hoverService: IHoverService ) { super(); this._context = context; this._fragment = document.createDocumentFragment(); - this._register(this._renderParts(participants, hoverParts, context, keybindingService)); + this._register(this._renderParts(participants, hoverParts, context, keybindingService, hoverService)); this._register(this._registerListenersOnRenderedParts()); this._register(this._createEditorDecorations(editor, hoverParts)); this._updateMarkdownAndColorParticipantInfo(participants); @@ -256,8 +260,8 @@ class RenderedContentHoverParts extends Disposable { }); } - private _renderParts(participants: IEditorHoverParticipant[], hoverParts: IHoverPart[], hoverContext: IEditorHoverContext, keybindingService: IKeybindingService): IDisposable { - const statusBar = new EditorHoverStatusBar(keybindingService); + private _renderParts(participants: IEditorHoverParticipant[], hoverParts: IHoverPart[], hoverContext: IEditorHoverContext, keybindingService: IKeybindingService, hoverService: IHoverService): IDisposable { + const statusBar = new EditorHoverStatusBar(keybindingService, hoverService); const hoverRenderingContext: IEditorHoverRenderContext = { fragment: this._fragment, statusBar, diff --git a/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts b/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts index 220b7597603e6..c799ff52c1180 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts @@ -7,6 +7,8 @@ import { HoverAction } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { IEditorHoverAction, IEditorHoverStatusBar } from './hoverTypes.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; const $ = dom.$; @@ -24,6 +26,7 @@ export class EditorHoverStatusBar extends Disposable implements IEditorHoverStat constructor( @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IHoverService private readonly _hoverService: IHoverService, ) { super(); this.hoverElement = $('div.hover-row.status-bar'); @@ -42,6 +45,7 @@ export class EditorHoverStatusBar extends Disposable implements IEditorHoverStat const keybindingLabel = keybinding ? keybinding.getLabel() : null; this._hasContent = true; const action = this._register(HoverAction.render(this.actionsElement, actionOptions, keybindingLabel)); + this._register(this._hoverService.setupManagedHover(getDefaultHoverDelegate('element'), action.actionContainer, action.actionRenderedLabel)); this.actions.push(action); return action; } diff --git a/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts b/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts index 2a80e26724172..5beccabb33b06 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts @@ -21,6 +21,7 @@ import { ContentHoverResult } from './contentHoverTypes.js'; import { Emitter } from '../../../../base/common/event.js'; import { RenderedContentHover } from './contentHoverRendered.js'; import { isMousePositionWithinElement } from './hoverUtils.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidget { @@ -38,6 +39,7 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge private readonly _editor: ICodeEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IHoverService private readonly _hoverService: IHoverService ) { super(); this._contentHoverWidget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor)); @@ -205,7 +207,7 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge private _showHover(hoverResult: ContentHoverResult): void { const context = this._getHoverContext(); - this._renderedContentHover = new RenderedContentHover(this._editor, hoverResult, this._participants, context, this._keybindingService); + this._renderedContentHover = new RenderedContentHover(this._editor, hoverResult, this._participants, context, this._keybindingService, this._hoverService); if (this._renderedContentHover.domNodeHasChildren) { this._contentHoverWidget.show(this._renderedContentHover); } else { From 0a2ad228869660ddb5b5c82c57d6cb641fde1408 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 06:50:12 -0700 Subject: [PATCH 087/555] Add delayed hover, groupId option and fix fade animation --- src/vs/base/browser/ui/hover/hover.ts | 24 ++++- src/vs/base/browser/ui/hover/hoverDelegate.ts | 2 +- .../base/browser/ui/hover/hoverDelegate2.ts | 1 + src/vs/base/browser/ui/hover/hoverWidget.css | 5 +- src/vs/base/browser/ui/hover/hoverWidget.ts | 3 +- .../browser/services/hoverService/hover.css | 4 - .../services/hoverService/hoverService.ts | 96 +++++++++++++++++-- .../services/hoverService/hoverWidget.ts | 16 ++-- .../hoverService/updatableHoverWidget.ts | 4 +- .../hover/browser/contentHoverWidget.ts | 2 +- .../contrib/hover/browser/glyphHoverWidget.ts | 2 +- .../quickinput/browser/quickInputTree.ts | 4 +- .../terminal/browser/terminalTabsList.ts | 6 +- 13 files changed, 137 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index c2500e19f5640..cfb9095d4111d 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -14,7 +14,8 @@ import type { IDisposable } from '../../../common/lifecycle.js'; */ export interface IHoverDelegate2 { /** - * Shows a hover, provided a hover with the same {@link options} object is not already visible. + * Shows a hover immediately, provided a hover with the same {@link options} object is not + * already visible. * * @param options A set of options defining the characteristics of the hover. * @param focus Whether to focus the hover (useful for keyboard accessibility). @@ -33,6 +34,10 @@ export interface IHoverDelegate2 { focus?: boolean ): IHoverWidget | undefined; + showDelayedHover( + options: IHoverOptions + ): IDelayedHoverWidget | IHoverWidget | undefined; + /** * Hides the hover if it was visible. This call will be ignored if the the hover is currently * "locked" via the alt/option key. @@ -79,6 +84,15 @@ export interface IHoverWidget extends IDisposable { readonly isDisposed: boolean; } +export interface IDelayedHoverWidget extends IDisposable { + /** + * Whether the hover widget has been disposed. + */ + readonly isDisposed: boolean; + + readonly wasShown: boolean; +} + export interface IHoverOptions { /** * The content to display in the primary section of the hover. The type of text determines the @@ -109,6 +123,14 @@ export interface IHoverOptions { */ id?: number | string; + // TODO: Move delay stuff into showHover? + /** + * An ID to associate with the hover that identifies a group of hovers. When a new hover target + * is moused over, if it's within a time threshold of the last hover target and they are within + * the same group the hover will be shown immediately. + */ + groupId?: number | string; + /** * A set of actions for the hover's "status bar". */ diff --git a/src/vs/base/browser/ui/hover/hoverDelegate.ts b/src/vs/base/browser/ui/hover/hoverDelegate.ts index 31ec93f727ae1..80cf086d8df14 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate.ts @@ -65,7 +65,7 @@ export interface IHoverDelegateOptions extends IManagedHoverOptions { } export interface IHoverDelegate { - showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined; + showHover(options: IHoverDelegateOptions, focus?: boolean): Promise | IHoverWidget | undefined; onDidHideHover?: () => void; delay: number; placement?: 'mouse' | 'element'; diff --git a/src/vs/base/browser/ui/hover/hoverDelegate2.ts b/src/vs/base/browser/ui/hover/hoverDelegate2.ts index 1d87b1721e43f..1bca5225365e2 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate2.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate2.ts @@ -7,6 +7,7 @@ import type { IHoverDelegate2 } from './hover.js'; let baseHoverDelegate: IHoverDelegate2 = { showHover: () => undefined, + showDelayedHover: () => undefined, hideHover: () => undefined, showAndFocusLastHover: () => undefined, setupManagedHover: () => null!, diff --git a/src/vs/base/browser/ui/hover/hoverWidget.css b/src/vs/base/browser/ui/hover/hoverWidget.css index d69122654e579..fda4a125b8321 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.css +++ b/src/vs/base/browser/ui/hover/hoverWidget.css @@ -10,11 +10,14 @@ user-select: text; -webkit-user-select: text; box-sizing: border-box; - animation: fadein 100ms linear; line-height: 1.5em; white-space: var(--vscode-hover-whiteSpace, normal); } +.monaco-hover.fade-in { + animation: fadein 100ms linear; +} + .monaco-hover.hidden { display: none; } diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index 638af90bd8ea9..a8a6f7e820d67 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -23,11 +23,12 @@ export class HoverWidget extends Disposable { public readonly contentsDomNode: HTMLElement; public readonly scrollbar: DomScrollableElement; - constructor() { + constructor(fadeIn: boolean) { super(); this.containerDomNode = document.createElement('div'); this.containerDomNode.className = 'monaco-hover'; + this.containerDomNode.classList.toggle('fade-in', !!fadeIn); this.containerDomNode.tabIndex = 0; this.containerDomNode.setAttribute('role', 'tooltip'); diff --git a/src/vs/editor/browser/services/hoverService/hover.css b/src/vs/editor/browser/services/hoverService/hover.css index 87ed1d3c84ec4..21f7221bf68ee 100644 --- a/src/vs/editor/browser/services/hoverService/hover.css +++ b/src/vs/editor/browser/services/hoverService/hover.css @@ -22,10 +22,6 @@ border-bottom: none; } -.monaco-workbench .workbench-hover:not(.skip-fade-in) { - animation: fadein 100ms linear; -} - .monaco-workbench .workbench-hover.compact { font-size: 12px; } diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 6e6c0567d80d3..6fa3ba407a807 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -20,10 +20,11 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; import { mainWindow } from '../../../../base/browser/window.js'; import { ContextViewHandler } from '../../../../platform/contextview/browser/contextViewService.js'; -import type { IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; +import type { IDelayedHoverWidget, IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; import type { IHoverDelegate, IHoverDelegateTarget } from '../../../../base/browser/ui/hover/hoverDelegate.js'; import { ManagedHoverWidget } from './updatableHoverWidget.js'; -import { TimeoutTimer } from '../../../../base/common/async.js'; +import { timeout, TimeoutTimer } from '../../../../base/common/async.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; export class HoverService extends Disposable implements IHoverService { declare readonly _serviceBrand: undefined; @@ -31,12 +32,14 @@ export class HoverService extends Disposable implements IHoverService { private _contextViewHandler: IContextViewProvider; private _currentHoverOptions: IHoverOptions | undefined; private _currentHover: HoverWidget | undefined; + private _currentDelayedHover: IDelayedHoverWidget | undefined; private _lastHoverOptions: IHoverOptions | undefined; private _lastFocusedElementBeforeOpen: HTMLElement | undefined; constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILayoutService private readonly _layoutService: ILayoutService, @@ -48,14 +51,18 @@ export class HoverService extends Disposable implements IHoverService { this._contextViewHandler = this._register(new ContextViewHandler(this._layoutService)); } - showHover(options: IHoverOptions, focus?: boolean, skipLastFocusedUpdate?: boolean): IHoverWidget | undefined { - if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { + showHover(options: IHoverOptions, focus?: boolean, skipLastFocusedUpdate?: boolean, dontShow?: boolean): IHoverWidget | undefined { + this._currentDelayedHover = undefined; + + console.log('HoverService.showHover'); + if (this._currentHover && this._currentHoverOptions?.persistence?.sticky) { return undefined; } - if (this._currentHover && this._currentHoverOptions?.persistence?.sticky) { + if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { return undefined; } this._currentHoverOptions = options; + console.log('set options to', this._currentHoverOptions); this._lastHoverOptions = options; const trapFocus = options.trapFocus || this._accessibilityService.isScreenReaderOptimized(); const activeElement = getActiveElement(); @@ -84,6 +91,7 @@ export class HoverService extends Disposable implements IHoverService { // Only clear the current options if it's the current hover, the current options help // reduce flickering when the same hover is shown multiple times if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { + console.trace('hover dispose, clear options', this._currentHoverOptions); this._currentHoverOptions = undefined; } hoverDisposables.dispose(); @@ -94,10 +102,12 @@ export class HoverService extends Disposable implements IHoverService { options.container = this._layoutService.getContainer(getWindow(targetElement)); } - this._contextViewHandler.showContextView( - new HoverContextViewDelegate(hover, focus), - options.container - ); + if (!dontShow) { + this._contextViewHandler.showContextView( + new HoverContextViewDelegate(hover, focus), + options.container + ); + } hover.onRequestLayout(() => this._contextViewHandler.layout(), undefined, hoverDisposables); if (options.persistence?.sticky) { hoverDisposables.add(addDisposableListener(getWindow(options.container).document, EventType.MOUSE_DOWN, e => { @@ -135,7 +145,74 @@ export class HoverService extends Disposable implements IHoverService { return hover; } + showDelayedHover( + options: IHoverOptions + ): IDelayedHoverWidget | IHoverWidget | undefined { + if (!this._currentDelayedHover || this._currentDelayedHover.wasShown) { + // Current hover is sticky, reject + if (this._currentHover && this._currentHoverOptions?.persistence?.sticky) { + return undefined; + } + + // Identity is the same, return current hover + if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { + return this._currentHover; + } + + console.log('showDelayedHover'); + console.log(' current options', this._currentHoverOptions); + console.log(' current group', this._currentHoverOptions?.groupId); + console.log(' new options', options); + console.log(' new group', options.groupId); + + // Check group identity, if it's the same skip the delay and show the hover immediately + if (this._currentHoverOptions?.groupId !== undefined && this._currentHoverOptions?.groupId === options.groupId) { + console.log('group matches!', this._currentHoverOptions?.groupId); + return this.showHover({ + ...options, + appearance: { + ...options.appearance, + skipFadeInAnimation: true + } + }); + } + } + + const hover = this.showHover(options, undefined, undefined, true); + if (!hover) { + return undefined; + } + + const delay = this._configurationService.getValue('workbench.hover.delay'); + let wasShown = false; + console.log(`queue showing delayed hover (${delay}ms)`); + timeout(delay).then(() => { + if (hover && !hover.isDisposed) { + wasShown = true; + this._contextViewHandler.showContextView( + new HoverContextViewDelegate(hover as any), + options.container + ); + } + }); + + const delayedHover: IDelayedHoverWidget = { + dispose() { + hover?.dispose(); + }, + get isDisposed() { + return hover.isDisposed ?? true; + }, + get wasShown() { + return wasShown; + } + }; + this._currentDelayedHover = delayedHover; + return delayedHover; + } + hideHover(): void { + console.log('hideHover'); if (this._currentHover?.isLocked || !this._currentHoverOptions) { return; } @@ -143,6 +220,7 @@ export class HoverService extends Disposable implements IHoverService { } private doHideHover(): void { + console.log('doHideHover'); this._currentHover = undefined; this._currentHoverOptions = undefined; this._contextViewHandler.hideContextView(); diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 62ea0e865c1e7..2c92e7b138783 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -111,14 +111,16 @@ export class HoverWidget extends Widget implements IHoverWidget { this._target = 'targetElements' in options.target ? options.target : new ElementHoverTarget(options.target); this._hoverPointer = options.appearance?.showPointer ? $('div.workbench-hover-pointer') : undefined; - this._hover = this._register(new BaseHoverWidget()); - this._hover.containerDomNode.classList.add('workbench-hover', 'fadeIn'); + this._hover = this._register(new BaseHoverWidget(!options.appearance?.skipFadeInAnimation)); + this._hover.containerDomNode.classList.add('workbench-hover'); if (options.appearance?.compact) { this._hover.containerDomNode.classList.add('workbench-hover', 'compact'); } - if (options.appearance?.skipFadeInAnimation) { - this._hover.containerDomNode.classList.add('skip-fade-in'); - } + // if (options.appearance?.skipFadeInAnimation) { + // this._hover.containerDomNode.classList.add('skip-fade-in'); + // } else { + // this._hover.containerDomNode.classList.add('fade-in'); + // } if (options.additionalClasses) { this._hover.containerDomNode.classList.add(...options.additionalClasses); } @@ -303,6 +305,7 @@ export class HoverWidget extends Widget implements IHoverWidget { } public render(container: HTMLElement): void { + console.log('render!'); container.appendChild(this._hoverContainer); const hoverFocused = this._hoverContainer.contains(this._hoverContainer.ownerDocument.activeElement); const accessibleViewHint = hoverFocused && getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel()); @@ -396,7 +399,7 @@ export class HoverWidget extends Widget implements IHoverWidget { this.setHoverPointerPosition(targetRect); } - + console.log('dimensions', this._x, this._y, this._hover.containerDomNode.clientWidth, this._hover.containerDomNode.clientHeight); this._hover.onContentsChanged(); } @@ -622,6 +625,7 @@ export class HoverWidget extends Widget implements IHoverWidget { } public override dispose(): void { + console.trace('HoverWidget.dispose'); if (!this._isDisposed) { this._onDispose.fire(); this._hoverContainer.remove(); diff --git a/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts b/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts index c5b656b99d363..542d00e8e837f 100644 --- a/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts @@ -64,7 +64,7 @@ export class ManagedHoverWidget implements IDisposable { this.show(resolvedContent, focus, options); } - private show(content: IManagedHoverResolvedContent, focus?: boolean, options?: IManagedHoverOptions): void { + private async show(content: IManagedHoverResolvedContent, focus?: boolean, options?: IManagedHoverOptions): Promise { const oldHoverWidget = this._hoverWidget; if (this.hasContent(content)) { @@ -84,7 +84,7 @@ export class ManagedHoverWidget implements IDisposable { }, }; - this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus); + this._hoverWidget = await this.hoverDelegate.showHover(hoverOptions, focus); } oldHoverWidget?.dispose(); } diff --git a/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts b/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts index 5dc3019098034..ad27c1544d58a 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts @@ -31,7 +31,7 @@ export class ContentHoverWidget extends ResizableContentWidget { private _minimumSize: dom.Dimension; private _contentWidth: number | undefined; - private readonly _hover: HoverWidget = this._register(new HoverWidget()); + private readonly _hover: HoverWidget = this._register(new HoverWidget(true)); private readonly _hoverVisibleKey: IContextKey; private readonly _hoverFocusedKey: IContextKey; diff --git a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts index 8432aa39f3a4f..f5b8fbb2fc5ff 100644 --- a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts @@ -45,7 +45,7 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov this._isVisible = false; this._messages = []; - this._hover = this._register(new HoverWidget()); + this._hover = this._register(new HoverWidget(true)); this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); this._markdownRenderer = this._register(new MarkdownRenderer({ editor: this._editor }, languageService, openerService)); diff --git a/src/vs/platform/quickinput/browser/quickInputTree.ts b/src/vs/platform/quickinput/browser/quickInputTree.ts index 3203be9e53570..eeee622609444 100644 --- a/src/vs/platform/quickinput/browser/quickInputTree.ts +++ b/src/vs/platform/quickinput/browser/quickInputTree.ts @@ -1544,7 +1544,7 @@ export class QuickInputTree extends Disposable { * Disposes of the hover and shows a new one for the given index if it has a tooltip. * @param element The element to show the hover for */ - private showHover(element: QuickPickItemElement): void { + private async showHover(element: QuickPickItemElement): Promise { if (this._lastHover && !this._lastHover.isDisposed) { this.hoverDelegate.onDidHideHover?.(); this._lastHover?.dispose(); @@ -1553,7 +1553,7 @@ export class QuickInputTree extends Disposable { if (!element.element || !element.saneTooltip) { return; } - this._lastHover = this.hoverDelegate.showHover({ + this._lastHover = await this.hoverDelegate.showHover({ content: element.saneTooltip, target: element.element, linkHandler: (url) => { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index e0795a92ef4c1..c567c82442f58 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -254,7 +254,6 @@ class TerminalTabsRenderer implements IListRenderer('workbench.hover.delay'), + delay: 0,//this._configurationService.getValue('workbench.hover.delay'), showHover: options => { - return this._hoverService.showHover({ + return this._hoverService.showDelayedHover({ ...options, + groupId: 'terminal-tabs-list', actions: context.hoverActions, target: element, persistence: { From 608fa0308d85f6980f8e5aec7c815f006b03db69 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 06:59:20 -0700 Subject: [PATCH 088/555] Move group ID into showDelayedHover options --- src/vs/base/browser/ui/hover/hover.ts | 11 ++------- .../services/hoverService/hoverService.ts | 23 +++++++++---------- .../terminal/browser/terminalTabsList.ts | 3 +-- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index cfb9095d4111d..d665018cdf4b9 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -35,7 +35,8 @@ export interface IHoverDelegate2 { ): IHoverWidget | undefined; showDelayedHover( - options: IHoverOptions + options: IHoverOptions, + groupId?: number | string, ): IDelayedHoverWidget | IHoverWidget | undefined; /** @@ -123,14 +124,6 @@ export interface IHoverOptions { */ id?: number | string; - // TODO: Move delay stuff into showHover? - /** - * An ID to associate with the hover that identifies a group of hovers. When a new hover target - * is moused over, if it's within a time threshold of the last hover target and they are within - * the same group the hover will be shown immediately. - */ - groupId?: number | string; - /** * A set of actions for the hover's "status bar". */ diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 6fa3ba407a807..9acdcdf0a70d6 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -33,6 +33,7 @@ export class HoverService extends Disposable implements IHoverService { private _currentHoverOptions: IHoverOptions | undefined; private _currentHover: HoverWidget | undefined; private _currentDelayedHover: IDelayedHoverWidget | undefined; + private _currentDelayedHoverGroupId: number | string | undefined; private _lastHoverOptions: IHoverOptions | undefined; private _lastFocusedElementBeforeOpen: HTMLElement | undefined; @@ -146,7 +147,8 @@ export class HoverService extends Disposable implements IHoverService { } showDelayedHover( - options: IHoverOptions + options: IHoverOptions, + groupId?: number | string, ): IDelayedHoverWidget | IHoverWidget | undefined { if (!this._currentDelayedHover || this._currentDelayedHover.wasShown) { // Current hover is sticky, reject @@ -159,15 +161,9 @@ export class HoverService extends Disposable implements IHoverService { return this._currentHover; } - console.log('showDelayedHover'); - console.log(' current options', this._currentHoverOptions); - console.log(' current group', this._currentHoverOptions?.groupId); - console.log(' new options', options); - console.log(' new group', options.groupId); - // Check group identity, if it's the same skip the delay and show the hover immediately - if (this._currentHoverOptions?.groupId !== undefined && this._currentHoverOptions?.groupId === options.groupId) { - console.log('group matches!', this._currentHoverOptions?.groupId); + if (this._currentDelayedHoverGroupId !== undefined && this._currentDelayedHoverGroupId === groupId) { + console.log('group matches!', groupId); return this.showHover({ ...options, appearance: { @@ -180,6 +176,8 @@ export class HoverService extends Disposable implements IHoverService { const hover = this.showHover(options, undefined, undefined, true); if (!hover) { + this._currentDelayedHover = undefined; + this._currentDelayedHoverGroupId = undefined; return undefined; } @@ -196,7 +194,7 @@ export class HoverService extends Disposable implements IHoverService { } }); - const delayedHover: IDelayedHoverWidget = { + this._currentDelayedHover = { dispose() { hover?.dispose(); }, @@ -207,8 +205,9 @@ export class HoverService extends Disposable implements IHoverService { return wasShown; } }; - this._currentDelayedHover = delayedHover; - return delayedHover; + this._currentDelayedHoverGroupId = groupId; + + return this._currentDelayedHover; } hideHover(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index c567c82442f58..47b0fe3f29bb8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -273,7 +273,6 @@ class TerminalTabsRenderer implements IListRenderer { return this._hoverService.showDelayedHover({ ...options, - groupId: 'terminal-tabs-list', actions: context.hoverActions, target: element, persistence: { @@ -285,7 +284,7 @@ class TerminalTabsRenderer implements IListRenderer Date: Wed, 30 Oct 2024 15:00:32 +0100 Subject: [PATCH 089/555] use workspace storage location instead of global storage location (#232596) --- .../vscode-selfhost-test-provider/src/extension.ts | 9 +++++---- .../vscode-selfhost-test-provider/src/failureTracker.ts | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts b/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts index 2732ef3b3f619..cbb8d50bf9954 100644 --- a/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts +++ b/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts @@ -86,10 +86,11 @@ export async function activate(context: vscode.ExtensionContext) { }, uri => ctrl.items.get(uri.toString().toLowerCase())); ctrl.relatedCodeProvider = graph; - context.subscriptions.push( - new FailureTracker(context, folder.uri.fsPath), - fileChangedEmitter.event(e => graph.didChange(e.uri, e.removed)), - ); + if (context.storageUri) { + context.subscriptions.push(new FailureTracker(context.storageUri.fsPath, folder.uri.fsPath)); + } + + context.subscriptions.push(fileChangedEmitter.event(e => graph.didChange(e.uri, e.removed))); }); const createRunHandler = ( diff --git a/.vscode/extensions/vscode-selfhost-test-provider/src/failureTracker.ts b/.vscode/extensions/vscode-selfhost-test-provider/src/failureTracker.ts index e04d4beede4c6..5bed5dd63e335 100644 --- a/.vscode/extensions/vscode-selfhost-test-provider/src/failureTracker.ts +++ b/.vscode/extensions/vscode-selfhost-test-provider/src/failureTracker.ts @@ -33,8 +33,8 @@ export class FailureTracker { private readonly logFile: string; private logs?: ITrackedRemediation[]; - constructor(context: vscode.ExtensionContext, private readonly rootDir: string) { - this.logFile = join(context.globalStorageUri.fsPath, '.build/vscode-test-failures.json'); + constructor(storageLocation: string, private readonly rootDir: string) { + this.logFile = join(storageLocation, '.build/vscode-test-failures.json'); mkdirSync(dirname(this.logFile), { recursive: true }); const oldLogFile = join(rootDir, '.build/vscode-test-failures.json'); From 2ed1f1d38b372176f164e8d5f9bee921b60b3cdb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 07:02:36 -0700 Subject: [PATCH 090/555] Force usage of groupId arg to make users consider it --- src/vs/base/browser/ui/hover/hover.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index d665018cdf4b9..ff97f7d503389 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -34,9 +34,17 @@ export interface IHoverDelegate2 { focus?: boolean ): IHoverWidget | undefined; + /** + * Shows a hover after a delay, or immediately if the {@link groupId} matches the currently + * shown hover. + * + * @param options The options of the hover. + * @param groupId The group ID of the hover. If the group ID is the same as the currently shown + * hover, the hover will be shown immediately, skipping the delay. + */ showDelayedHover( options: IHoverOptions, - groupId?: number | string, + groupId: number | string | undefined, ): IDelayedHoverWidget | IHoverWidget | undefined; /** From 1613ddd763ea0f90f0da6e623f96184f9aae1414 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 30 Oct 2024 15:07:25 +0100 Subject: [PATCH 091/555] Optimize tree view such that cross process calls are batched (#232588) * Optimize tree view such that cross process calls are batched Fixes #232263 --- src/vs/base/browser/ui/tree/abstractTree.ts | 6 +- .../api/browser/mainThreadTreeViews.ts | 55 ++++++--- .../workbench/api/common/extHost.protocol.ts | 12 +- .../workbench/api/common/extHostTreeViews.ts | 18 ++- .../api/test/browser/extHostTreeViews.test.ts | 85 +++++++------ .../test/browser/mainThreadTreeViews.test.ts | 4 +- .../workbench/browser/parts/views/treeView.ts | 115 +++++++++++++----- src/vs/workbench/common/views.ts | 1 + .../workbench/test/browser/treeview.test.ts | 62 ++++++++++ 9 files changed, 270 insertions(+), 88 deletions(-) create mode 100644 src/vs/workbench/test/browser/treeview.test.ts diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index d420f499b8b75..cacc72a9ea765 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -156,7 +156,7 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop< } } -function asListOptions(modelProvider: () => ITreeModel, options?: IAbstractTreeOptions): IListOptions> | undefined { +function asListOptions(modelProvider: () => ITreeModel, disposableStore: DisposableStore, options?: IAbstractTreeOptions): IListOptions> | undefined { return options && { ...options, identityProvider: options.identityProvider && { @@ -164,7 +164,7 @@ function asListOptions(modelProvider: () => ITreeModel implements IDisposable this.focus = new Trait(() => this.view.getFocusedElements()[0], _options.identityProvider); this.selection = new Trait(() => this.view.getSelectedElements()[0], _options.identityProvider); this.anchor = new Trait(() => this.view.getAnchorElement(), _options.identityProvider); - this.view = new TreeNodeList(_user, container, this.treeDelegate, this.renderers, this.focus, this.selection, this.anchor, { ...asListOptions(() => this.model, _options), tree: this, stickyScrollProvider: () => this.stickyScrollController }); + this.view = new TreeNodeList(_user, container, this.treeDelegate, this.renderers, this.focus, this.selection, this.anchor, { ...asListOptions(() => this.model, this.disposables, _options), tree: this, stickyScrollProvider: () => this.stickyScrollController }); this.setupModel(this.model); // model needs to be setup after the traits have been created diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 74089f182b0fd..c61530258bc98 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableMap, DisposableStore } from '../../../base/common/lifecycle.js'; import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, CheckboxUpdate } from '../common/extHost.protocol.js'; -import { ITreeViewDataProvider, ITreeItem, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem, ITreeViewDragAndDropController, IViewBadge, NoTreeViewError } from '../../common/views.js'; +import { ITreeItem, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem, ITreeViewDragAndDropController, IViewBadge, NoTreeViewError, ITreeViewDataProvider } from '../../common/views.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { distinct } from '../../../base/common/arrays.js'; import { INotificationService } from '../../../platform/notification/common/notification.js'; @@ -270,13 +270,21 @@ class TreeViewDataProvider implements ITreeViewDataProvider { this.hasResolve = this._proxy.$hasResolve(this.treeViewId); } - getChildren(treeItem?: ITreeItem): Promise { - if (!treeItem) { + async getChildren(treeItem?: ITreeItem): Promise { + const batches = await this.getChildrenBatch(treeItem ? [treeItem] : undefined); + return batches?.[0]; + } + + getChildrenBatch(treeItems?: ITreeItem[]): Promise { + if (!treeItems) { this.itemsMap.clear(); } - return this._proxy.$getChildren(this.treeViewId, treeItem ? treeItem.handle : undefined) + return this._proxy.$getChildren(this.treeViewId, treeItems ? treeItems.map(item => item.handle) : undefined) .then( - children => this.postGetChildren(children), + children => { + const convertedChildren = this.convertTransferChildren(treeItems ?? [], children); + return this.postGetChildren(convertedChildren); + }, err => { // It can happen that a tree view is disposed right as `getChildren` is called. This results in an error because the data provider gets removed. // The tree will shortly get cleaned up in this case. We just need to handle the error here. @@ -287,6 +295,17 @@ class TreeViewDataProvider implements ITreeViewDataProvider { }); } + private convertTransferChildren(parents: ITreeItem[], children: (number | ITreeItem)[][] | undefined) { + const convertedChildren: ITreeItem[][] = Array(parents.length); + if (children) { + for (const childGroup of children) { + const childGroupIndex = childGroup[0] as number; + convertedChildren[childGroupIndex] = childGroup.slice(1) as ITreeItem[]; + } + } + return convertedChildren; + } + getItemsToRefresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): { items: ITreeItem[]; checkboxes: ITreeItem[] } { const itemsToRefresh: ITreeItem[] = []; const checkboxesToRefresh: ITreeItem[] = []; @@ -325,22 +344,26 @@ class TreeViewDataProvider implements ITreeViewDataProvider { return this.itemsMap.size === 0; } - private async postGetChildren(elements: ITreeItem[] | undefined): Promise { - if (elements === undefined) { + private async postGetChildren(elementGroups: ITreeItem[][] | undefined): Promise { + if (elementGroups === undefined) { return undefined; } - const result: ResolvableTreeItem[] = []; + const resultGroups: ResolvableTreeItem[][] = []; const hasResolve = await this.hasResolve; - if (elements) { - for (const element of elements) { - const resolvable = new ResolvableTreeItem(element, hasResolve ? (token) => { - return this._proxy.$resolve(this.treeViewId, element.handle, token); - } : undefined); - this.itemsMap.set(element.handle, resolvable); - result.push(resolvable); + if (elementGroups) { + for (const elements of elementGroups) { + const result: ResolvableTreeItem[] = []; + resultGroups.push(result); + for (const element of elements) { + const resolvable = new ResolvableTreeItem(element, hasResolve ? (token) => { + return this._proxy.$resolve(this.treeViewId, element.handle, token); + } : undefined); + this.itemsMap.set(element.handle, resolvable); + result.push(resolvable); + } } } - return result; + return resultGroups; } private updateTreeItem(current: ITreeItem, treeItem: ITreeItem): void { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 02b40ac0cbf27..0369719ee2666 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1862,7 +1862,17 @@ export interface CheckboxUpdate { } export interface ExtHostTreeViewsShape { - $getChildren(treeViewId: string, treeItemHandle?: string): Promise; + /** + * To reduce what is sent on the wire: + * w + * x + * y + * z + * + * for [x,y] returns + * [[1,z]], where the inner array is [original index, ...children] + */ + $getChildren(treeViewId: string, treeItemHandles?: string[]): Promise<(number | ITreeItem)[][] | undefined>; $handleDrop(destinationViewId: string, requestId: number, treeDataTransfer: DataTransferDTO, targetHandle: string | undefined, token: CancellationToken, operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise; $handleDrag(sourceViewId: string, sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise; $setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index d3c2aae3c12e6..4179deb5ad3d2 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -156,12 +156,26 @@ export class ExtHostTreeViews extends Disposable implements ExtHostTreeViewsShap return view as vscode.TreeView; } - $getChildren(treeViewId: string, treeItemHandle?: string): Promise { + async $getChildren(treeViewId: string, treeItemHandles?: string[]): Promise<(number | ITreeItem)[][] | undefined> { const treeView = this.treeViews.get(treeViewId); if (!treeView) { return Promise.reject(new NoTreeViewError(treeViewId)); } - return treeView.getChildren(treeItemHandle); + if (!treeItemHandles) { + const children = await treeView.getChildren(); + return children ? [[0, ...children]] : undefined; + } + // Keep order of treeItemHandles in case extension trees already depend on this + const result = []; + for (let i = 0; i < treeItemHandles.length; i++) { + const treeItemHandle = treeItemHandles[i]; + const children = await treeView.getChildren(treeItemHandle); + if (children) { + result.push([i, ...children]); + } + + } + return result; } async $handleDrop(destinationViewId: string, requestId: number, treeDataTransferDTO: DataTransferDTO, targetItemHandle: string | undefined, token: CancellationToken, diff --git a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts index d00b0f7c98865..997f0b33ed81c 100644 --- a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts @@ -20,6 +20,16 @@ import { runWithFakedTimers } from '../../../../base/test/common/timeTravelSched import { IExtHostTelemetry } from '../../common/extHostTelemetry.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +function unBatchChildren(result: (number | ITreeItem)[][] | undefined): ITreeItem[] | undefined { + if (!result || result.length === 0) { + return undefined; + } + if (result.length > 1) { + throw new Error('Unexpected result length, all tests are unbatched.'); + } + return result[0].slice(1) as ITreeItem[]; +} + suite('ExtHostTreeView', function () { const store = ensureNoDisposablesAreLeakedInTestSuite(); @@ -96,25 +106,25 @@ suite('ExtHostTreeView', function () { test('construct node tree', () => { return testObject.$getChildren('testNodeTreeProvider') .then(elements => { - const actuals = elements?.map(e => e.handle); + const actuals = unBatchChildren(elements)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['0/0:a', '0/0:b']); return Promise.all([ - testObject.$getChildren('testNodeTreeProvider', '0/0:a') + testObject.$getChildren('testNodeTreeProvider', ['0/0:a']) .then(children => { - const actuals = children?.map(e => e.handle); + const actuals = unBatchChildren(children)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['0/0:a/0:aa', '0/0:a/0:ab']); return Promise.all([ - testObject.$getChildren('testNodeTreeProvider', '0/0:a/0:aa').then(children => assert.strictEqual(children?.length, 0)), - testObject.$getChildren('testNodeTreeProvider', '0/0:a/0:ab').then(children => assert.strictEqual(children?.length, 0)) + testObject.$getChildren('testNodeTreeProvider', ['0/0:a/0:aa']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)), + testObject.$getChildren('testNodeTreeProvider', ['0/0:a/0:ab']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)) ]); }), - testObject.$getChildren('testNodeTreeProvider', '0/0:b') + testObject.$getChildren('testNodeTreeProvider', ['0/0:b']) .then(children => { - const actuals = children?.map(e => e.handle); + const actuals = unBatchChildren(children)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['0/0:b/0:ba', '0/0:b/0:bb']); return Promise.all([ - testObject.$getChildren('testNodeTreeProvider', '0/0:b/0:ba').then(children => assert.strictEqual(children?.length, 0)), - testObject.$getChildren('testNodeTreeProvider', '0/0:b/0:bb').then(children => assert.strictEqual(children?.length, 0)) + testObject.$getChildren('testNodeTreeProvider', ['0/0:b/0:ba']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)), + testObject.$getChildren('testNodeTreeProvider', ['0/0:b/0:bb']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)) ]); }) ]); @@ -124,25 +134,25 @@ suite('ExtHostTreeView', function () { test('construct id tree', () => { return testObject.$getChildren('testNodeWithIdTreeProvider') .then(elements => { - const actuals = elements?.map(e => e.handle); + const actuals = unBatchChildren(elements)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['1/a', '1/b']); return Promise.all([ - testObject.$getChildren('testNodeWithIdTreeProvider', '1/a') + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/a']) .then(children => { - const actuals = children?.map(e => e.handle); + const actuals = unBatchChildren(children)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['1/aa', '1/ab']); return Promise.all([ - testObject.$getChildren('testNodeWithIdTreeProvider', '1/aa').then(children => assert.strictEqual(children?.length, 0)), - testObject.$getChildren('testNodeWithIdTreeProvider', '1/ab').then(children => assert.strictEqual(children?.length, 0)) + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/aa']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)), + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/ab']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)) ]); }), - testObject.$getChildren('testNodeWithIdTreeProvider', '1/b') + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/b']) .then(children => { - const actuals = children?.map(e => e.handle); + const actuals = unBatchChildren(children)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['1/ba', '1/bb']); return Promise.all([ - testObject.$getChildren('testNodeWithIdTreeProvider', '1/ba').then(children => assert.strictEqual(children?.length, 0)), - testObject.$getChildren('testNodeWithIdTreeProvider', '1/bb').then(children => assert.strictEqual(children?.length, 0)) + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/ba']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)), + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/bb']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)) ]); }) ]); @@ -152,7 +162,7 @@ suite('ExtHostTreeView', function () { test('construct highlights tree', () => { return testObject.$getChildren('testNodeWithHighlightsTreeProvider') .then(elements => { - assert.deepStrictEqual(removeUnsetKeys(elements), [{ + assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(elements)), [{ handle: '1/a', label: { label: 'a', highlights: [[0, 2], [3, 5]] }, collapsibleState: TreeItemCollapsibleState.Collapsed @@ -162,9 +172,9 @@ suite('ExtHostTreeView', function () { collapsibleState: TreeItemCollapsibleState.Collapsed }]); return Promise.all([ - testObject.$getChildren('testNodeWithHighlightsTreeProvider', '1/a') + testObject.$getChildren('testNodeWithHighlightsTreeProvider', ['1/a']) .then(children => { - assert.deepStrictEqual(removeUnsetKeys(children), [{ + assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(children)), [{ handle: '1/aa', parentHandle: '1/a', label: { label: 'aa', highlights: [[0, 2], [3, 5]] }, @@ -176,9 +186,9 @@ suite('ExtHostTreeView', function () { collapsibleState: TreeItemCollapsibleState.None }]); }), - testObject.$getChildren('testNodeWithHighlightsTreeProvider', '1/b') + testObject.$getChildren('testNodeWithHighlightsTreeProvider', ['1/b']) .then(children => { - assert.deepStrictEqual(removeUnsetKeys(children), [{ + assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(children)), [{ handle: '1/ba', parentHandle: '1/b', label: { label: 'ba', highlights: [[0, 2], [3, 5]] }, @@ -206,10 +216,10 @@ suite('ExtHostTreeView', function () { store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeWithIdTreeProvider') .then(elements => { - const actuals = elements?.map(e => e.handle); + const actuals = unBatchChildren(elements)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['1/a', '1/b']); - return testObject.$getChildren('testNodeWithIdTreeProvider', '1/a') - .then(() => testObject.$getChildren('testNodeWithIdTreeProvider', '1/b')) + return testObject.$getChildren('testNodeWithIdTreeProvider', ['1/a']) + .then(() => testObject.$getChildren('testNodeWithIdTreeProvider', ['1/b'])) .then(() => assert.fail('Should fail with duplicate id')) .catch(() => caughtExpectedError = true) .finally(() => caughtExpectedError ? done() : assert.fail('Expected duplicate id error not thrown.')); @@ -406,7 +416,7 @@ suite('ExtHostTreeView', function () { store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeTreeProvider') .then(elements => { - assert.deepStrictEqual(elements?.map(e => e.handle), ['0/0:a//0:b']); + assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:a//0:b']); done(); }); })); @@ -448,11 +458,11 @@ suite('ExtHostTreeView', function () { store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeTreeProvider') .then(elements => { - const actuals = elements?.map(e => e.handle); + const actuals = unBatchChildren(elements)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['0/0:a', '0/0:b', '0/1:a', '0/0:d', '0/1:b', '0/0:f', '0/2:a']); - return testObject.$getChildren('testNodeTreeProvider', '0/1:b') + return testObject.$getChildren('testNodeTreeProvider', ['0/1:b']) .then(elements => { - const actuals = elements?.map(e => e.handle); + const actuals = unBatchChildren(elements)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['0/1:b/0:h', '0/1:b/1:h', '0/1:b/0:j', '0/1:b/1:j', '0/1:b/2:h']); done(); }); @@ -470,7 +480,7 @@ suite('ExtHostTreeView', function () { store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeTreeProvider') .then(elements => { - assert.deepStrictEqual(elements?.map(e => e.handle), ['0/0:c']); + assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:c']); done(); }); })); @@ -485,7 +495,7 @@ suite('ExtHostTreeView', function () { return testObject.$getChildren('testNodeTreeProvider') .then(elements => { - assert.deepStrictEqual(elements?.map(e => e.handle), ['0/0:a', '0/0:b']); + assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:a', '0/0:b']); }); }); @@ -537,7 +547,7 @@ suite('ExtHostTreeView', function () { parentChain: [{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }] }; return testObject.$getChildren('treeDataProvider') - .then(() => testObject.$getChildren('treeDataProvider', '0/0:a')) + .then(() => testObject.$getChildren('treeDataProvider', ['0/0:a'])) .then(() => treeView.reveal({ key: 'aa' }) .then(() => { assert.ok(revealTarget.calledOnce); @@ -650,8 +660,13 @@ suite('ExtHostTreeView', function () { }); function loadCompleteTree(treeId: string, element?: string): Promise { - return testObject.$getChildren(treeId, element) - .then(elements => elements?.map(e => loadCompleteTree(treeId, e.handle))) + return testObject.$getChildren(treeId, element ? [element] : undefined) + .then(elements => { + if (!elements || elements?.length === 0) { + return null; + } + return elements[0].slice(1).map(e => loadCompleteTree(treeId, (e as ITreeItem).handle)); + }) .then(() => null); } diff --git a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts index ee16dbe37d741..152b128f35ad4 100644 --- a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts @@ -32,8 +32,8 @@ suite('MainThreadHostTreeView', function () { } class MockExtHostTreeViewsShape extends mock() { - override async $getChildren(treeViewId: string, treeItemHandle?: string): Promise { - return [{ handle: 'testItem1', collapsibleState: TreeItemCollapsibleState.Expanded, customProp: customValue }]; + override async $getChildren(treeViewId: string, treeItemHandle?: string[]): Promise<(number | ITreeItem)[][]> { + return [[0, { handle: 'testItem1', collapsibleState: TreeItemCollapsibleState.Expanded, customProp: customValue }]]; } override async $hasResolve(): Promise { diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 2d65e66e01206..28a0846356f3e 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -367,34 +367,60 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { return this._isEmpty; } - async getChildren(node?: ITreeItem): Promise { - let children: ITreeItem[]; + async getChildren(element?: ITreeItem): Promise { + const batches = await this.getChildrenBatch(element ? [element] : undefined); + return batches?.[0]; + } + + private updateEmptyState(nodes: ITreeItem[], childrenGroups: ITreeItem[][]): void { + if ((nodes.length === 1) && (nodes[0] instanceof Root)) { + const oldEmpty = this._isEmpty; + this._isEmpty = childrenGroups[0].length === 0; + if (oldEmpty !== this._isEmpty) { + this._onDidChangeEmpty.fire(); + } + } + } + + private findCheckboxesUpdated(nodes: ITreeItem[], childrenGroups: ITreeItem[][]): ITreeItem[] { const checkboxesUpdated: ITreeItem[] = []; - if (node && node.children) { - children = node.children; - } else { - node = node ?? self.root; - node.children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node)); - children = node.children ?? []; - children.forEach(child => { + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + const children = childrenGroups[i]; + for (const child of children) { child.parent = node; if (!self.manuallyManageCheckboxes && (node?.checkbox?.isChecked === true) && (child.checkbox?.isChecked === false)) { child.checkbox.isChecked = true; checkboxesUpdated.push(child); } - }); + } } - if (node instanceof Root) { - const oldEmpty = this._isEmpty; - this._isEmpty = children.length === 0; - if (oldEmpty !== this._isEmpty) { - this._onDidChangeEmpty.fire(); + return checkboxesUpdated; + } + + async getChildrenBatch(nodes?: ITreeItem[]): Promise { + let childrenGroups: ITreeItem[][]; + let checkboxesUpdated: ITreeItem[] = []; + if (nodes && nodes.every((node): node is Required => !!node.children)) { + childrenGroups = nodes.map(node => node.children); + } else { + nodes = nodes ?? [self.root]; + const batchedChildren = await (nodes.length === 1 && nodes[0] instanceof Root ? doGetChildrenOrBatch(dataProvider, undefined) : doGetChildrenOrBatch(dataProvider, nodes)); + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + node.children = batchedChildren ? batchedChildren[i] : undefined; } + childrenGroups = batchedChildren ?? []; + checkboxesUpdated = this.findCheckboxesUpdated(nodes, childrenGroups); } + + this.updateEmptyState(nodes, childrenGroups); + if (checkboxesUpdated.length > 0) { self._onDidChangeCheckboxState.fire(checkboxesUpdated); } - return children; + return childrenGroups; } }; if (this._dataProvider.onDidChangeEmpty) { @@ -662,9 +688,9 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { const treeMenus = this.treeDisposables.add(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this.treeDisposables.add(this.instantiationService.createInstance(ResourceLabels, this)); const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); - const aligner = new Aligner(this.themeService); + const aligner = this.treeDisposables.add(new Aligner(this.themeService)); const checkboxStateHandler = this.treeDisposables.add(new CheckboxStateHandler()); - const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler, () => this.manuallyManageCheckboxes); + const renderer = this.treeDisposables.add(this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler, () => this.manuallyManageCheckboxes)); this.treeDisposables.add(renderer.onDidChangeCheckboxState(e => this._onDidChangeCheckboxState.fire(e))); const widgetAriaLabel = this._title; @@ -724,7 +750,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { this.treeDisposables.add(this.tree); treeMenus.setContextKeyService(this.tree.contextKeyService); aligner.tree = this.tree; - const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection()); + const actionRunner = this.treeDisposables.add(new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection())); renderer.actionRunner = actionRunner; this.tree.contextKeyService.createKey(this.id, true); @@ -1138,6 +1164,18 @@ class TreeViewDelegate implements IListVirtualDelegate { } } +async function doGetChildrenOrBatch(dataProvider: ITreeViewDataProvider, nodes: ITreeItem[] | undefined): Promise { + if (dataProvider.getChildrenBatch) { + return dataProvider.getChildrenBatch(nodes); + } else { + if (nodes) { + return Promise.all(nodes.map(node => dataProvider.getChildren(node).then(children => children ?? []))); + } else { + return [await dataProvider.getChildren()].filter(children => children !== undefined); + } + } +} + class TreeDataSource implements IAsyncDataSource { constructor( @@ -1150,18 +1188,37 @@ class TreeDataSource implements IAsyncDataSource { return !!this.treeView.dataProvider && (element.collapsibleState !== TreeItemCollapsibleState.None); } + private batch: ITreeItem[] | undefined; + private batchPromise: Promise | undefined; async getChildren(element: ITreeItem): Promise { - let result: ITreeItem[] = []; - if (this.treeView.dataProvider) { - try { - result = (await this.withProgress(this.treeView.dataProvider.getChildren(element))) ?? []; - } catch (e) { - if (!(e.message).startsWith('Bad progress location:')) { - throw e; - } - } + const dataProvider = this.treeView.dataProvider; + if (!dataProvider) { + return []; } - return result; + if (this.batch === undefined) { + this.batch = [element]; + this.batchPromise = undefined; + } else { + this.batch.push(element); + } + const indexInBatch = this.batch.length - 1; + return new Promise((resolve, reject) => { + setTimeout(async () => { + const batch = this.batch; + this.batch = undefined; + if (!this.batchPromise) { + this.batchPromise = this.withProgress(doGetChildrenOrBatch(dataProvider, batch)); + } + try { + const result = await this.batchPromise; + resolve(result ? result[indexInBatch] : []); + } catch (e) { + if (!(e.message).startsWith('Bad progress location:')) { + reject(e); + } + } + }, 0); + }); } } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 5f6069ef05787..37e1b8f620c75 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -847,6 +847,7 @@ export interface ITreeViewDataProvider { readonly isTreeEmpty?: boolean; onDidChangeEmpty?: Event; getChildren(element?: ITreeItem): Promise; + getChildrenBatch?(element?: ITreeItem[]): Promise; } export interface ITreeViewDragAndDropController { diff --git a/src/vs/workbench/test/browser/treeview.test.ts b/src/vs/workbench/test/browser/treeview.test.ts new file mode 100644 index 0000000000000..1bc421c614b13 --- /dev/null +++ b/src/vs/workbench/test/browser/treeview.test.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { TreeView } from '../../browser/parts/views/treeView.js'; +import { workbenchInstantiationService } from './workbenchTestServices.js'; +import { TestInstantiationService } from '../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { ITreeItem, IViewDescriptorService, TreeItemCollapsibleState } from '../../common/views.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js'; +import { ViewDescriptorService } from '../../services/views/browser/viewDescriptorService.js'; + +suite('TreeView', function () { + + let treeView: TreeView; + let largestBatchSize: number = 0; + + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + setup(async () => { + largestBatchSize = 0; + const instantiationService: TestInstantiationService = workbenchInstantiationService(undefined, disposables); + const viewDescriptorService = disposables.add(instantiationService.createInstance(ViewDescriptorService)); + instantiationService.stub(IViewDescriptorService, viewDescriptorService); + treeView = disposables.add(instantiationService.createInstance(TreeView, 'testTree', 'Test Title')); + const getChildrenOfItem = async (element?: ITreeItem): Promise => { + if (element) { + return undefined; + } else { + const rootChildren: ITreeItem[] = []; + for (let i = 0; i < 100; i++) { + rootChildren.push({ handle: `item_${i}`, collapsibleState: TreeItemCollapsibleState.Expanded }); + } + return rootChildren; + } + }; + + treeView.dataProvider = { + getChildren: getChildrenOfItem, + getChildrenBatch: async (elements?: ITreeItem[]): Promise => { + if (elements && elements.length > largestBatchSize) { + largestBatchSize = elements.length; + } + if (elements) { + return Array(elements.length).fill([]); + } else { + return [(await getChildrenOfItem()) ?? []]; + } + } + }; + }); + + test('children are batched', async () => { + assert.strictEqual(largestBatchSize, 0); + treeView.setVisibility(true); + await treeView.refresh(); + assert.strictEqual(largestBatchSize, 100); + }); + + +}); From 2c2f43d7cf8f7a91dbab3381a1029000974bc53a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 30 Oct 2024 15:09:06 +0100 Subject: [PATCH 092/555] testresolver: apply learnings from wsl download fix (#232610) --- .../vscode-test-resolver/src/download.ts | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/extensions/vscode-test-resolver/src/download.ts b/extensions/vscode-test-resolver/src/download.ts index fa001b5a17809..a351aa775cc39 100644 --- a/extensions/vscode-test-resolver/src/download.ts +++ b/extensions/vscode-test-resolver/src/download.ts @@ -36,33 +36,31 @@ async function downloadVSCodeServerArchive(updateUrl: string, commit: string, qu https.get(requestOptions, res => { if (res.statusCode !== 302) { reject('Failed to get VS Code server archive location'); + res.resume(); // read the rest of the response data and discard it + return; } const archiveUrl = res.headers.location; if (!archiveUrl) { reject('Failed to get VS Code server archive location'); + res.resume(); // read the rest of the response data and discard it return; } const archiveRequestOptions: https.RequestOptions = parseUrl(archiveUrl); - if (archiveUrl.endsWith('.zip')) { - const archivePath = path.resolve(destDir, `vscode-server-${commit}.zip`); - const outStream = fs.createWriteStream(archivePath); - outStream.on('close', () => { - resolve(archivePath); - }); - https.get(archiveRequestOptions, res => { - res.pipe(outStream); - }); - } else { - const zipPath = path.resolve(destDir, `vscode-server-${commit}.tgz`); - const outStream = fs.createWriteStream(zipPath); - https.get(archiveRequestOptions, res => { - res.pipe(outStream); + const archivePath = path.resolve(destDir, `vscode-server-${commit}.${archiveUrl.endsWith('.zip') ? 'zip' : 'tgz'}`); + const outStream = fs.createWriteStream(archivePath); + outStream.on('finish', () => { + resolve(archivePath); + }); + outStream.on('error', err => { + reject(err); + }); + https.get(archiveRequestOptions, res => { + res.pipe(outStream); + res.on('error', err => { + reject(err); }); - outStream.on('close', () => { - resolve(zipPath); - }); - } + }); }); }); } From d04d50e0f735821eeb153ab34f7a239750ac01ea Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 07:19:00 -0700 Subject: [PATCH 093/555] Reduce duplication in hover service --- .../services/hoverService/hoverService.ts | 143 +++++++++--------- .../terminal/browser/terminalTabsList.ts | 2 +- 2 files changed, 76 insertions(+), 69 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 9acdcdf0a70d6..7257c7b64e97e 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -53,6 +53,76 @@ export class HoverService extends Disposable implements IHoverService { } showHover(options: IHoverOptions, focus?: boolean, skipLastFocusedUpdate?: boolean, dontShow?: boolean): IHoverWidget | undefined { + const hover = this._createHover(options, skipLastFocusedUpdate); + if (!hover) { + return undefined; + } + this._showHover(hover, options, focus); + return hover; + } + + showDelayedHover( + options: IHoverOptions, + groupId?: number | string, + ): IDelayedHoverWidget | IHoverWidget | undefined { + if (!this._currentDelayedHover || this._currentDelayedHover.wasShown) { + // Current hover is sticky, reject + if (this._currentHover && this._currentHoverOptions?.persistence?.sticky) { + return undefined; + } + + // Identity is the same, return current hover + if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { + return this._currentHover; + } + + // Check group identity, if it's the same skip the delay and show the hover immediately + if (this._currentHover && !this._currentHover.isDisposed && this._currentDelayedHoverGroupId !== undefined && this._currentDelayedHoverGroupId === groupId) { + console.log('group matches!', groupId); + return this.showHover({ + ...options, + appearance: { + ...options.appearance, + skipFadeInAnimation: true + } + }); + } + } + + const hover = this._createHover(options, undefined); + if (!hover) { + this._currentDelayedHover = undefined; + this._currentDelayedHoverGroupId = undefined; + return undefined; + } + + const delay = this._configurationService.getValue('workbench.hover.delay'); + let wasShown = false; + console.log(`queue showing delayed hover (${delay}ms)`); + timeout(delay).then(() => { + if (hover && !hover.isDisposed) { + wasShown = true; + this._showHover(hover, options); + } + }); + + this._currentDelayedHover = { + dispose() { + hover?.dispose(); + }, + get isDisposed() { + return hover.isDisposed ?? true; + }, + get wasShown() { + return wasShown; + } + }; + this._currentDelayedHoverGroupId = groupId; + + return this._currentDelayedHover; + } + + private _createHover(options: IHoverOptions, skipLastFocusedUpdate?: boolean): HoverWidget | undefined { this._currentDelayedHover = undefined; console.log('HoverService.showHover'); @@ -103,12 +173,6 @@ export class HoverService extends Disposable implements IHoverService { options.container = this._layoutService.getContainer(getWindow(targetElement)); } - if (!dontShow) { - this._contextViewHandler.showContextView( - new HoverContextViewDelegate(hover, focus), - options.container - ); - } hover.onRequestLayout(() => this._contextViewHandler.layout(), undefined, hoverDisposables); if (options.persistence?.sticky) { hoverDisposables.add(addDisposableListener(getWindow(options.container).document, EventType.MOUSE_DOWN, e => { @@ -146,68 +210,11 @@ export class HoverService extends Disposable implements IHoverService { return hover; } - showDelayedHover( - options: IHoverOptions, - groupId?: number | string, - ): IDelayedHoverWidget | IHoverWidget | undefined { - if (!this._currentDelayedHover || this._currentDelayedHover.wasShown) { - // Current hover is sticky, reject - if (this._currentHover && this._currentHoverOptions?.persistence?.sticky) { - return undefined; - } - - // Identity is the same, return current hover - if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { - return this._currentHover; - } - - // Check group identity, if it's the same skip the delay and show the hover immediately - if (this._currentDelayedHoverGroupId !== undefined && this._currentDelayedHoverGroupId === groupId) { - console.log('group matches!', groupId); - return this.showHover({ - ...options, - appearance: { - ...options.appearance, - skipFadeInAnimation: true - } - }); - } - } - - const hover = this.showHover(options, undefined, undefined, true); - if (!hover) { - this._currentDelayedHover = undefined; - this._currentDelayedHoverGroupId = undefined; - return undefined; - } - - const delay = this._configurationService.getValue('workbench.hover.delay'); - let wasShown = false; - console.log(`queue showing delayed hover (${delay}ms)`); - timeout(delay).then(() => { - if (hover && !hover.isDisposed) { - wasShown = true; - this._contextViewHandler.showContextView( - new HoverContextViewDelegate(hover as any), - options.container - ); - } - }); - - this._currentDelayedHover = { - dispose() { - hover?.dispose(); - }, - get isDisposed() { - return hover.isDisposed ?? true; - }, - get wasShown() { - return wasShown; - } - }; - this._currentDelayedHoverGroupId = groupId; - - return this._currentDelayedHover; + private _showHover(hover: HoverWidget, options: IHoverOptions, focus?: boolean) { + this._contextViewHandler.showContextView( + new HoverContextViewDelegate(hover, focus), + options.container + ); } hideHover(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 47b0fe3f29bb8..eb23deed2dea6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -269,7 +269,7 @@ class TerminalTabsRenderer implements IListRenderer('workbench.hover.delay'), + delay: 0, showHover: options => { return this._hoverService.showDelayedHover({ ...options, From d9743c5de4d4c9fdd72b8f851a2696ca3cc302d4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 07:19:39 -0700 Subject: [PATCH 094/555] Remove logs --- .../editor/browser/services/hoverService/hoverService.ts | 7 ------- src/vs/editor/browser/services/hoverService/hoverWidget.ts | 3 --- 2 files changed, 10 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 7257c7b64e97e..13019e091da53 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -78,7 +78,6 @@ export class HoverService extends Disposable implements IHoverService { // Check group identity, if it's the same skip the delay and show the hover immediately if (this._currentHover && !this._currentHover.isDisposed && this._currentDelayedHoverGroupId !== undefined && this._currentDelayedHoverGroupId === groupId) { - console.log('group matches!', groupId); return this.showHover({ ...options, appearance: { @@ -98,7 +97,6 @@ export class HoverService extends Disposable implements IHoverService { const delay = this._configurationService.getValue('workbench.hover.delay'); let wasShown = false; - console.log(`queue showing delayed hover (${delay}ms)`); timeout(delay).then(() => { if (hover && !hover.isDisposed) { wasShown = true; @@ -125,7 +123,6 @@ export class HoverService extends Disposable implements IHoverService { private _createHover(options: IHoverOptions, skipLastFocusedUpdate?: boolean): HoverWidget | undefined { this._currentDelayedHover = undefined; - console.log('HoverService.showHover'); if (this._currentHover && this._currentHoverOptions?.persistence?.sticky) { return undefined; } @@ -133,7 +130,6 @@ export class HoverService extends Disposable implements IHoverService { return undefined; } this._currentHoverOptions = options; - console.log('set options to', this._currentHoverOptions); this._lastHoverOptions = options; const trapFocus = options.trapFocus || this._accessibilityService.isScreenReaderOptimized(); const activeElement = getActiveElement(); @@ -162,7 +158,6 @@ export class HoverService extends Disposable implements IHoverService { // Only clear the current options if it's the current hover, the current options help // reduce flickering when the same hover is shown multiple times if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { - console.trace('hover dispose, clear options', this._currentHoverOptions); this._currentHoverOptions = undefined; } hoverDisposables.dispose(); @@ -218,7 +213,6 @@ export class HoverService extends Disposable implements IHoverService { } hideHover(): void { - console.log('hideHover'); if (this._currentHover?.isLocked || !this._currentHoverOptions) { return; } @@ -226,7 +220,6 @@ export class HoverService extends Disposable implements IHoverService { } private doHideHover(): void { - console.log('doHideHover'); this._currentHover = undefined; this._currentHoverOptions = undefined; this._contextViewHandler.hideContextView(); diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 2c92e7b138783..cf2be96374cc4 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -305,7 +305,6 @@ export class HoverWidget extends Widget implements IHoverWidget { } public render(container: HTMLElement): void { - console.log('render!'); container.appendChild(this._hoverContainer); const hoverFocused = this._hoverContainer.contains(this._hoverContainer.ownerDocument.activeElement); const accessibleViewHint = hoverFocused && getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel()); @@ -399,7 +398,6 @@ export class HoverWidget extends Widget implements IHoverWidget { this.setHoverPointerPosition(targetRect); } - console.log('dimensions', this._x, this._y, this._hover.containerDomNode.clientWidth, this._hover.containerDomNode.clientHeight); this._hover.onContentsChanged(); } @@ -625,7 +623,6 @@ export class HoverWidget extends Widget implements IHoverWidget { } public override dispose(): void { - console.trace('HoverWidget.dispose'); if (!this._isDisposed) { this._onDispose.fire(); this._hoverContainer.remove(); From 11cacd5cffd858c331f1e447b972a9ee38d3fe32 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 07:34:47 -0700 Subject: [PATCH 095/555] Move CompositeBarActionViewItem to showDelayedHover --- .../services/hoverService/hoverService.ts | 1 - .../browser/parts/compositeBarActions.ts | 80 +++++-------------- 2 files changed, 18 insertions(+), 63 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 13019e091da53..e0de43f1606ee 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -271,7 +271,6 @@ export class HoverService extends Disposable implements IHoverService { // TODO: Investigate performance of this function. There seems to be a lot of content created // and thrown away on start up setupManagedHover(hoverDelegate: IHoverDelegate, targetElement: HTMLElement, content: IManagedHoverContentOrFactory, options?: IManagedHoverOptions | undefined): IManagedHover { - targetElement.setAttribute('custom-hover', 'true'); if (targetElement.title !== '') { diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index deb1385838729..dc4cb88481b57 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -21,12 +21,10 @@ import { BaseActionViewItem, IActionViewItemOptions } from '../../../base/browse import { Codicon } from '../../../base/common/codicons.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { IHoverService } from '../../../platform/hover/browser/hover.js'; -import { RunOnceScheduler } from '../../../base/common/async.js'; import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; import { HoverPosition } from '../../../base/browser/ui/hover/hoverWidget.js'; import { URI } from '../../../base/common/uri.js'; import { badgeBackground, badgeForeground, contrastBorder } from '../../../platform/theme/common/colorRegistry.js'; -import type { IHoverWidget } from '../../../base/browser/ui/hover/hover.js'; import { Action2, IAction2Options } from '../../../platform/actions/common/actions.js'; import { ViewContainerLocation } from '../../common/views.js'; import { IPaneCompositePartService } from '../../services/panecomposite/browser/panecomposite.js'; @@ -154,8 +152,6 @@ export interface ICompositeBarActionViewItemOptions extends IActionViewItemOptio export class CompositeBarActionViewItem extends BaseActionViewItem { - private static hoverLeaveTime = 0; - protected container!: HTMLElement; protected label!: HTMLElement; protected badge!: HTMLElement; @@ -166,10 +162,6 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { private mouseUpTimeout: any; private keybindingLabel: string | undefined | null; - private readonly hoverDisposables = this._register(new DisposableStore()); - private lastHover: IHoverWidget | undefined; - private readonly showHoverScheduler = new RunOnceScheduler(() => this.showHover(), 0); - constructor( action: CompositeBarAction, options: ICompositeBarActionViewItemOptions, @@ -187,7 +179,6 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { this._register(action.onDidChangeCompositeBarActionItem(() => this.update())); this._register(Event.filter(keybindingService.onDidUpdateKeybindings, () => this.keybindingLabel !== this.computeKeybindingLabel())(() => this.updateTitle())); this._register(action.onDidChangeActivity(() => this.updateActivity())); - this._register(toDisposable(() => this.showHoverScheduler.cancel())); } protected get compositeBarActionItem(): ICompositeBarActionItem { @@ -266,6 +257,23 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { }, 800); // delayed to prevent focus feedback from showing on mouse up })); + this._register(addDisposableListener(this.container, EventType.MOUSE_OVER, () => { + this.hoverService.showDelayedHover({ + target: this.container, + content: this.computeTitle(), + position: { + hoverPosition: this.options.hoverOptions.position(), + }, + persistence: { + hideOnKeyDown: true, + }, + appearance: { + showPointer: true, + compact: true, + } + }, 'composite-bar-actions'); + }, true)); + // Label this.label = append(container, $('a')); @@ -280,7 +288,7 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { this.update(); this.updateStyles(); - this.updateHover(); + this.updateTitle(); } private onThemeChange(theme: IColorTheme): void { @@ -411,58 +419,6 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { return keybinding?.getLabel(); } - private updateHover(): void { - this.hoverDisposables.clear(); - - this.updateTitle(); - - this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_OVER, () => { - if (!this.showHoverScheduler.isScheduled()) { - if (Date.now() - CompositeBarActionViewItem.hoverLeaveTime < 200) { - this.showHover(true); - } else { - this.showHoverScheduler.schedule(this.configurationService.getValue('workbench.hover.delay')); - } - } - }, true)); - - this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_LEAVE, e => { - if (e.target === this.container) { - CompositeBarActionViewItem.hoverLeaveTime = Date.now(); - this.hoverService.hideHover(); - this.showHoverScheduler.cancel(); - } - }, true)); - - this.hoverDisposables.add(toDisposable(() => { - this.hoverService.hideHover(); - this.showHoverScheduler.cancel(); - })); - } - - showHover(skipFadeInAnimation: boolean = false): void { - if (this.lastHover && !this.lastHover.isDisposed) { - return; - } - - const hoverPosition = this.options.hoverOptions.position(); - this.lastHover = this.hoverService.showHover({ - target: this.container, - content: this.computeTitle(), - position: { - hoverPosition, - }, - persistence: { - hideOnKeyDown: true, - }, - appearance: { - showPointer: true, - compact: true, - skipFadeInAnimation, - } - }); - } - override dispose(): void { super.dispose(); From 3c3bdf39c21d940eb664128498d5538d28b5b522 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 07:43:18 -0700 Subject: [PATCH 096/555] Create simple setupDelayedHover wrapper --- src/vs/base/browser/ui/hover/hover.ts | 14 +++++++++ .../services/hoverService/hoverService.ts | 10 +++++++ .../browser/parts/compositeBarActions.ts | 30 +++++++++---------- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index ff97f7d503389..d0f70558169d2 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -47,6 +47,20 @@ export interface IHoverDelegate2 { groupId: number | string | undefined, ): IDelayedHoverWidget | IHoverWidget | undefined; + /** + * A simple wrapper around showDelayedHover that includes listening to the mouseover event of + * the {@link target} element. + * @param target The target element to listener for mouseover events on. + * @param options The options of the hover. + * @param groupId The group ID of the hover. If the group ID is the same as the currently shown + * hover, the hover will be shown immediately, skipping the delay. + */ + setupDelayedHover( + target: HTMLElement, + options: (() => IHoverOptions) | IHoverOptions, + groupId: number | string | undefined, + ): IDisposable; + /** * Hides the hover if it was visible. This call will be ignored if the the hover is currently * "locked" via the alt/option key. diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index e0de43f1606ee..fb24a796435d3 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -120,6 +120,16 @@ export class HoverService extends Disposable implements IHoverService { return this._currentDelayedHover; } + setupDelayedHover( + target: HTMLElement, + options: (() => IHoverOptions) | IHoverOptions, + groupId: number | string | undefined, + ): IDisposable { + return addDisposableListener(target, EventType.MOUSE_OVER, () => { + this.showDelayedHover(typeof options === 'function' ? options() : options, groupId); + }); + } + private _createHover(options: IHoverOptions, skipLastFocusedUpdate?: boolean): HoverWidget | undefined { this._currentDelayedHover = undefined; diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index dc4cb88481b57..7bcc8ccd5f8bf 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -257,22 +257,20 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { }, 800); // delayed to prevent focus feedback from showing on mouse up })); - this._register(addDisposableListener(this.container, EventType.MOUSE_OVER, () => { - this.hoverService.showDelayedHover({ - target: this.container, - content: this.computeTitle(), - position: { - hoverPosition: this.options.hoverOptions.position(), - }, - persistence: { - hideOnKeyDown: true, - }, - appearance: { - showPointer: true, - compact: true, - } - }, 'composite-bar-actions'); - }, true)); + this._register(this.hoverService.setupDelayedHover(this.container, () => ({ + target: this.container, + content: this.computeTitle(), + position: { + hoverPosition: this.options.hoverOptions.position(), + }, + persistence: { + hideOnKeyDown: true, + }, + appearance: { + showPointer: true, + compact: true, + } + }), 'composite-bar-actions')); // Label this.label = append(container, $('a')); From 4b41a60e1ed9cd9200fdd88ead3dd03f006ab80a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 07:48:09 -0700 Subject: [PATCH 097/555] Fix compile --- src/vs/base/browser/ui/hover/hoverDelegate2.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/base/browser/ui/hover/hoverDelegate2.ts b/src/vs/base/browser/ui/hover/hoverDelegate2.ts index 1bca5225365e2..3027fdc54460a 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate2.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate2.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Disposable } from '../../../common/lifecycle.js'; import type { IHoverDelegate2 } from './hover.js'; let baseHoverDelegate: IHoverDelegate2 = { showHover: () => undefined, showDelayedHover: () => undefined, + setupDelayedHover: () => Disposable.None, hideHover: () => undefined, showAndFocusLastHover: () => undefined, setupManagedHover: () => null!, From bbd39b925d1e6a0b00b93af35c255cb92f060f07 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 08:03:45 -0700 Subject: [PATCH 098/555] Move DecorationAddon to use setupDelayedHover --- .../terminal/browser/xterm/decorationAddon.ts | 23 ++-- .../browser/xterm/decorationStyles.ts | 113 ++++++------------ 2 files changed, 52 insertions(+), 84 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index bb7d902d802c5..8158b269e2395 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -23,9 +23,11 @@ import { CommandInvalidationReason, ICommandDetectionCapability, IMarkProperties import { TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; import { terminalDecorationError, terminalDecorationIncomplete, terminalDecorationMark, terminalDecorationSuccess } from '../terminalIcons.js'; -import { DecorationSelector, TerminalDecorationHoverManager, updateLayout } from './decorationStyles.js'; +import { DecorationSelector, getTerminalDecorationHoverContent, updateLayout } from './decorationStyles.js'; import { TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_ERROR_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_SUCCESS_BACKGROUND_COLOR } from '../../common/terminalColorRegistry.js'; import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import { MarkdownString } from '../../../../../base/common/htmlContent.js'; interface IDisposableDecoration { decoration: IDecoration; disposables: IDisposable[]; exitCode?: number; markProperties?: IMarkProperties } @@ -36,7 +38,6 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { private _placeholderDecoration: IDecoration | undefined; private _showGutterDecorations?: boolean; private _showOverviewRulerDecorations?: boolean; - private _terminalDecorationHoverManager: TerminalDecorationHoverManager; private readonly _onDidRequestRunCommand = this._register(new Emitter<{ command: ITerminalCommand; noNewLine?: boolean }>()); readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event; @@ -55,7 +56,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { @ICommandService private readonly _commandService: ICommandService, @IInstantiationService instantiationService: IInstantiationService, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, - @INotificationService private readonly _notificationService: INotificationService + @INotificationService private readonly _notificationService: INotificationService, + @IHoverService private readonly _hoverService: IHoverService ) { super(); this._register(toDisposable(() => this._dispose())); @@ -74,7 +76,6 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { this._register(this._capabilities.onDidAddCapabilityType(c => this._createCapabilityDisposables(c))); this._register(this._capabilities.onDidRemoveCapabilityType(c => this._removeCapabilityDisposables(c))); this._register(lifecycleService.onWillShutdown(() => this._disposeAllDecorations())); - this._terminalDecorationHoverManager = this._register(instantiationService.createInstance(TerminalDecorationHoverManager)); } private _removeCapabilityDisposables(c: TerminalCapability): void { @@ -181,7 +182,6 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { } private _dispose(): void { - this._terminalDecorationHoverManager.dispose(); for (const disposable of this._capabilityDisposables.values()) { dispose(disposable); } @@ -315,9 +315,16 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { if (command?.exitCode === undefined && !command?.markProperties) { return []; } else if (command?.markProperties || markProperties) { - return [this._terminalDecorationHoverManager.createHover(element, command || markProperties, markProperties?.hoverMessage)]; + return [this._createHover(element, command || markProperties, markProperties?.hoverMessage)]; } - return [...this._createContextMenu(element, command), this._terminalDecorationHoverManager.createHover(element, command)]; + return [...this._createContextMenu(element, command), this._createHover(element, command)]; + } + + private _createHover(element: HTMLElement, command: ITerminalCommand | undefined, hoverMessage?: string) { + return this._hoverService.setupDelayedHover(element, () => ({ + target: element, + content: new MarkdownString(getTerminalDecorationHoverContent(command, hoverMessage)) + }), undefined); } private _updateClasses(element?: HTMLElement, exitCode?: number, markProperties?: IMarkProperties): void { @@ -359,13 +366,11 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { }), dom.addDisposableListener(element, dom.EventType.CLICK, async (e) => { e.stopImmediatePropagation(); - this._terminalDecorationHoverManager.hideHover(); const actions = await this._getCommandActions(command); this._contextMenuService.showContextMenu({ getAnchor: () => element, getActions: () => actions }); }), dom.addDisposableListener(element, dom.EventType.CONTEXT_MENU, async (e) => { e.stopImmediatePropagation(); - this._terminalDecorationHoverManager.hideHover(); const actions = this._getContextMenuActions(); this._contextMenuService.showContextMenu({ getAnchor: () => element, getActions: () => actions }); }), diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts index 044987585d1fa..f19b9cb716209 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationStyles.ts @@ -3,17 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from '../../../../../base/browser/dom.js'; -import { Delayer } from '../../../../../base/common/async.js'; import { fromNow, getDurationString } from '../../../../../base/common/date.js'; -import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { combinedDisposable, Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; import { ITerminalCommand } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; import { TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; -import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; const enum DecorationStyles { DefaultDimension = 16, @@ -31,77 +25,46 @@ export const enum DecorationSelector { OverviewRuler = '.xterm-decoration-overview-ruler', } -export class TerminalDecorationHoverManager extends Disposable { - private _hoverDelayer: Delayer; - private _contextMenuVisible: boolean = false; - - constructor(@IHoverService private readonly _hoverService: IHoverService, - @IConfigurationService configurationService: IConfigurationService, - @IContextMenuService contextMenuService: IContextMenuService) { - super(); - this._register(contextMenuService.onDidShowContextMenu(() => this._contextMenuVisible = true)); - this._register(contextMenuService.onDidHideContextMenu(() => this._contextMenuVisible = false)); - this._hoverDelayer = this._register(new Delayer(configurationService.getValue('workbench.hover.delay'))); - } - - public hideHover() { - this._hoverDelayer.cancel(); - this._hoverService.hideHover(); - } - - createHover(element: HTMLElement, command: ITerminalCommand | undefined, hoverMessage?: string): IDisposable { - return combinedDisposable( - dom.addDisposableListener(element, dom.EventType.MOUSE_ENTER, () => { - if (this._contextMenuVisible) { - return; +export function getTerminalDecorationHoverContent(command: ITerminalCommand | undefined, hoverMessage?: string): string { + let hoverContent = `${localize('terminalPromptContextMenu', "Show Command Actions")}`; + hoverContent += '\n\n---\n\n'; + if (!command) { + if (hoverMessage) { + hoverContent = hoverMessage; + } else { + return ''; + } + } else if (command.markProperties || hoverMessage) { + if (command.markProperties?.hoverMessage || hoverMessage) { + hoverContent = command.markProperties?.hoverMessage || hoverMessage || ''; + } else { + return ''; + } + } else { + if (command.duration) { + const durationText = getDurationString(command.duration); + if (command.exitCode) { + if (command.exitCode === -1) { + hoverContent += localize('terminalPromptCommandFailed.duration', 'Command executed {0}, took {1} and failed', fromNow(command.timestamp, true), durationText); + } else { + hoverContent += localize('terminalPromptCommandFailedWithExitCode.duration', 'Command executed {0}, took {1} and failed (Exit Code {2})', fromNow(command.timestamp, true), durationText, command.exitCode); + } + } else { + hoverContent += localize('terminalPromptCommandSuccess.duration', 'Command executed {0} and took {1}', fromNow(command.timestamp, true), durationText); + } + } else { + if (command.exitCode) { + if (command.exitCode === -1) { + hoverContent += localize('terminalPromptCommandFailed', 'Command executed {0} and failed', fromNow(command.timestamp, true)); + } else { + hoverContent += localize('terminalPromptCommandFailedWithExitCode', 'Command executed {0} and failed (Exit Code {1})', fromNow(command.timestamp, true), command.exitCode); } - this._hoverDelayer.trigger(() => { - let hoverContent = `${localize('terminalPromptContextMenu', "Show Command Actions")}`; - hoverContent += '\n\n---\n\n'; - if (!command) { - if (hoverMessage) { - hoverContent = hoverMessage; - } else { - return; - } - } else if (command.markProperties || hoverMessage) { - if (command.markProperties?.hoverMessage || hoverMessage) { - hoverContent = command.markProperties?.hoverMessage || hoverMessage || ''; - } else { - return; - } - } else { - if (command.duration) { - const durationText = getDurationString(command.duration); - if (command.exitCode) { - if (command.exitCode === -1) { - hoverContent += localize('terminalPromptCommandFailed.duration', 'Command executed {0}, took {1} and failed', fromNow(command.timestamp, true), durationText); - } else { - hoverContent += localize('terminalPromptCommandFailedWithExitCode.duration', 'Command executed {0}, took {1} and failed (Exit Code {2})', fromNow(command.timestamp, true), durationText, command.exitCode); - } - } else { - hoverContent += localize('terminalPromptCommandSuccess.duration', 'Command executed {0} and took {1}', fromNow(command.timestamp, true), durationText); - } - } else { - if (command.exitCode) { - if (command.exitCode === -1) { - hoverContent += localize('terminalPromptCommandFailed', 'Command executed {0} and failed', fromNow(command.timestamp, true)); - } else { - hoverContent += localize('terminalPromptCommandFailedWithExitCode', 'Command executed {0} and failed (Exit Code {1})', fromNow(command.timestamp, true), command.exitCode); - } - } else { - hoverContent += localize('terminalPromptCommandSuccess', 'Command executed {0}', fromNow(command.timestamp, true)); - } - } - } - this._hoverService.showHover({ content: new MarkdownString(hoverContent), target: element }); - }); - }), - dom.addDisposableListener(element, dom.EventType.MOUSE_LEAVE, () => this.hideHover()), - dom.addDisposableListener(element, dom.EventType.MOUSE_OUT, () => this.hideHover()) - ); + } else { + hoverContent += localize('terminalPromptCommandSuccess', 'Command executed {0}', fromNow(command.timestamp, true)); + } + } } - + return hoverContent; } export function updateLayout(configurationService: IConfigurationService, element?: HTMLElement): void { From 7c63cf09632626a0736047f8d1bc67bbc2172fdb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 08:05:15 -0700 Subject: [PATCH 099/555] Remove hideOnHover from terminal tabs call This essentially didn't do anything as terminals almost always have hover actions --- src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index eb23deed2dea6..5c1cabe8b8892 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -275,9 +275,6 @@ class TerminalTabsRenderer implements IListRenderer Date: Wed, 30 Oct 2024 16:14:03 +0100 Subject: [PATCH 100/555] Do not hide hover when color decorator clicked (#232616) do not hide hover when color decorator clicked --- .../hoverColorPickerContribution.ts | 36 +++++++++---------- .../hover/browser/contentHoverController.ts | 3 +- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts index 46f7660dd2eee..cc302544fbca0 100644 --- a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts +++ b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from '../../../../../base/common/lifecycle.js'; -import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../browser/editorBrowser.js'; +import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent, MouseTargetType } from '../../../../browser/editorBrowser.js'; import { EditorOption } from '../../../../common/config/editorOptions.js'; import { Range } from '../../../../common/core/range.js'; import { IEditorContribution } from '../../../../common/editorCommon.js'; @@ -34,32 +34,28 @@ export class HoverColorPickerContribution extends Disposable implements IEditorC if (colorDecoratorsActivatedOn !== 'click' && colorDecoratorsActivatedOn !== 'clickAndHover') { return; } - - const target = mouseEvent.target; - - if (target.type !== MouseTargetType.CONTENT_TEXT) { + if (!isOnColorDecorator(mouseEvent)) { return; } - - if (!target.detail.injectedText) { + const hoverController = this._editor.getContribution(ContentHoverController.ID); + if (!hoverController) { return; } - - if (target.detail.injectedText.options.attachedData !== ColorDecorationInjectedTextMarker) { + if (hoverController.isColorPickerVisible) { return; } - - if (!target.range) { + const targetRange = mouseEvent.target.range; + if (!targetRange) { return; } - - const hoverController = this._editor.getContribution(ContentHoverController.ID); - if (!hoverController) { - return; - } - if (!hoverController.isColorPickerVisible) { - const range = new Range(target.range.startLineNumber, target.range.startColumn + 1, target.range.endLineNumber, target.range.endColumn + 1); - hoverController.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Mouse, false, true); - } + const range = new Range(targetRange.startLineNumber, targetRange.startColumn + 1, targetRange.endLineNumber, targetRange.endColumn + 1); + hoverController.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Mouse, false, true); } } + +export function isOnColorDecorator(mouseEvent: IPartialEditorMouseEvent): boolean { + const target = mouseEvent.target; + return !!target + && target.type === MouseTargetType.CONTENT_TEXT + && target.detail.injectedText?.options.attachedData === ColorDecorationInjectedTextMarker; +} diff --git a/src/vs/editor/contrib/hover/browser/contentHoverController.ts b/src/vs/editor/contrib/hover/browser/contentHoverController.ts index ba56f278b99a2..9f90495950e7a 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverController.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverController.ts @@ -23,6 +23,7 @@ import { isMousePositionWithinElement } from './hoverUtils.js'; import { ContentHoverWidgetWrapper } from './contentHoverWidgetWrapper.js'; import './hover.css'; import { Emitter } from '../../../../base/common/event.js'; +import { isOnColorDecorator } from '../../colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.js'; // sticky hover widget which doesn't disappear on focus out and such const _sticky = false @@ -142,7 +143,7 @@ export class ContentHoverController extends Disposable implements IEditorContrib } private _shouldNotHideCurrentHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { - return this._isMouseOnContentHoverWidget(mouseEvent) || this._isContentWidgetResizing(); + return this._isMouseOnContentHoverWidget(mouseEvent) || this._isContentWidgetResizing() || isOnColorDecorator(mouseEvent); } private _isMouseOnContentHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { From 756f4313e279815f5d2420ff88af880c78a86a3d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 30 Oct 2024 16:14:55 +0100 Subject: [PATCH 101/555] Fixes https://github.com/microsoft/vscode-copilot/issues/9919 (#232617) --- .../browser/controller/inlineCompletionsController.ts | 5 ----- .../browser/model/inlineCompletionsModel.ts | 4 ---- 2 files changed, 9 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 2a8aff67a9d19..c71e8b01a6a17 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -91,10 +91,6 @@ export class InlineCompletionsController extends Disposable { return cursorPos.column <= indentMaxColumn; }); - private readonly _shouldHideInlineEdit = derived(this, reader => { - return this._cursorIsInIndentation.read(reader); - }); - private readonly optionPreview = this._editorObs.getOption(EditorOption.suggest).map(v => v.preview); private readonly optionPreviewMode = this._editorObs.getOption(EditorOption.suggest).map(v => v.previewMode); private readonly optionMode = this._editorObs.getOption(EditorOption.inlineSuggest).map(v => v.mode); @@ -117,7 +113,6 @@ export class InlineCompletionsController extends Disposable { this.optionMode, this._enabled, this.optionInlineEditsEnabled, - this._shouldHideInlineEdit, this.editor, ); return model; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 3b12a5919a640..d09375176e42a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -59,7 +59,6 @@ export class InlineCompletionsModel extends Disposable { private readonly _inlineSuggestMode: IObservable<'prefix' | 'subword' | 'subwordSmart'>, private readonly _enabled: IObservable, private readonly _inlineEditsEnabled: IObservable, - private readonly _shouldHideInlineEdit: IObservable, private readonly _editor: ICodeEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ICommandService private readonly _commandService: ICommandService, @@ -292,9 +291,6 @@ export class InlineCompletionsModel extends Disposable { const cursorPos = this._primaryPosition.read(reader); const cursorAtInlineEdit = LineRange.fromRangeInclusive(edit.range).addMargin(1, 1).contains(cursorPos.lineNumber); - const shouldShow = this._alwaysShowInlineEdit.read(reader); - if (!shouldShow && item.inlineEditCompletion.request.context.triggerKind === InlineCompletionTriggerKind.Automatic && this._shouldHideInlineEdit.read(reader) && !cursorAtInlineEdit) { return undefined; } - const cursorDist = LineRange.fromRange(edit.range).distanceToLine(this._primaryPosition.read(reader).lineNumber); const disableCollapsing = true; const currentItemIsCollapsed = !disableCollapsing && (cursorDist > 1 && this._collapsedInlineEditId.read(reader) === item.inlineEditCompletion.semanticId); From 5a144d30835557ab69aa37d875cc296d27be6a1d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 08:24:35 -0700 Subject: [PATCH 102/555] Don't require target to be specified again when using setupDelayedHover --- src/vs/base/browser/ui/hover/hover.ts | 3 ++- .../editor/browser/services/hoverService/hoverService.ts | 7 +++++-- src/vs/workbench/browser/parts/compositeBarActions.ts | 1 - .../contrib/terminal/browser/xterm/decorationAddon.ts | 1 - 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index d0f70558169d2..8fd9003bee672 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -57,7 +57,8 @@ export interface IHoverDelegate2 { */ setupDelayedHover( target: HTMLElement, - options: (() => IHoverOptions) | IHoverOptions, + // TODO: Support using a simple string (content) as options? + options: (() => Omit) | Omit, groupId: number | string | undefined, ): IDisposable; diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index fb24a796435d3..2312b5c821c81 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -122,11 +122,14 @@ export class HoverService extends Disposable implements IHoverService { setupDelayedHover( target: HTMLElement, - options: (() => IHoverOptions) | IHoverOptions, + options: (() => Omit) | Omit, groupId: number | string | undefined, ): IDisposable { return addDisposableListener(target, EventType.MOUSE_OVER, () => { - this.showDelayedHover(typeof options === 'function' ? options() : options, groupId); + this.showDelayedHover({ + ...typeof options === 'function' ? options() : options, + target + }, groupId); }); } diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 7bcc8ccd5f8bf..944d7d2d04dc9 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -258,7 +258,6 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { })); this._register(this.hoverService.setupDelayedHover(this.container, () => ({ - target: this.container, content: this.computeTitle(), position: { hoverPosition: this.options.hoverOptions.position(), diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 8158b269e2395..8d7bd48bbc484 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -322,7 +322,6 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { private _createHover(element: HTMLElement, command: ITerminalCommand | undefined, hoverMessage?: string) { return this._hoverService.setupDelayedHover(element, () => ({ - target: element, content: new MarkdownString(getTerminalDecorationHoverContent(command, hoverMessage)) }), undefined); } From 8b88015ddb0600934089853b81e49095de10d486 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 08:40:30 -0700 Subject: [PATCH 103/555] Support HoverPosition.Mouse --- src/vs/base/browser/ui/hover/hover.ts | 2 +- src/vs/base/browser/ui/hover/hoverDelegate.ts | 2 ++ src/vs/base/browser/ui/hover/hoverWidget.ts | 6 +++++- .../services/hoverService/hoverService.ts | 20 ++++++++++++++++--- .../services/hoverService/hoverWidget.ts | 1 - 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 8fd9003bee672..59b992091eda0 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -280,7 +280,7 @@ export interface IHoverAction { /** * A target for a hover. */ -export interface IHoverTarget extends IDisposable { +export interface IHoverTarget { /** * A set of target elements used to position the hover. If multiple elements are used the hover * will try to not overlap any target element. An example use case for this is show a hover for diff --git a/src/vs/base/browser/ui/hover/hoverDelegate.ts b/src/vs/base/browser/ui/hover/hoverDelegate.ts index 80cf086d8df14..534636cf8e7c4 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate.ts @@ -41,6 +41,8 @@ export interface IHoverDelegateOptions extends IManagedHoverOptions { * Position of the hover. The default is to show above the target. This option will be ignored * if there is not enough room to layout the hover in the specified position, unless the * forcePosition option is set. + * + * The value 'mouse' */ hoverPosition?: HoverPosition; }; diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index a8a6f7e820d67..67f6b052732f2 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -14,7 +14,11 @@ import { localize } from '../../../../nls.js'; const $ = dom.$; export const enum HoverPosition { - LEFT, RIGHT, BELOW, ABOVE + LEFT, + RIGHT, + BELOW, + ABOVE, + MOUSE, } export class HoverWidget extends Disposable { diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 2312b5c821c81..8566ba20c5a3a 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -25,6 +25,7 @@ import type { IHoverDelegate, IHoverDelegateTarget } from '../../../../base/brow import { ManagedHoverWidget } from './updatableHoverWidget.js'; import { timeout, TimeoutTimer } from '../../../../base/common/async.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; export class HoverService extends Disposable implements IHoverService { declare readonly _serviceBrand: undefined; @@ -125,11 +126,24 @@ export class HoverService extends Disposable implements IHoverService { options: (() => Omit) | Omit, groupId: number | string | undefined, ): IDisposable { - return addDisposableListener(target, EventType.MOUSE_OVER, () => { - this.showDelayedHover({ + return addDisposableListener(target, EventType.MOUSE_OVER, e => { + const resolvedOptions: IHoverOptions = { ...typeof options === 'function' ? options() : options, target - }, groupId); + }; + if (resolvedOptions.position?.hoverPosition === HoverPosition.MOUSE) { + resolvedOptions.target = { + targetElements: [target], + x: e.x + 10 + }; + } + + // TODO: setupManagedHover forces pointer on element hovers, we don't want to force it though + // else { // element + // resolvedOptions.appearance ||= {}; + // resolvedOptions.appearance.showPointer = true; + // } + this.showDelayedHover(resolvedOptions, groupId); }); } diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index cf2be96374cc4..c8a5551e7137c 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -627,7 +627,6 @@ export class HoverWidget extends Widget implements IHoverWidget { this._onDispose.fire(); this._hoverContainer.remove(); this._messageListeners.dispose(); - this._target.dispose(); super.dispose(); } this._isDisposed = true; From aa3abe00018e69adc908b5dfbe9258c8fbe63540 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 30 Oct 2024 16:47:54 +0100 Subject: [PATCH 104/555] Fixes https://github.com/microsoft/vscode-copilot/issues/9962 (#232619) --- .../browser/controller/commands.ts | 8 +--- .../controller/inlineCompletionContextKeys.ts | 3 +- .../controller/inlineCompletionsController.ts | 2 + .../browser/model/inlineCompletionsModel.ts | 41 +++++++++++++++++++ 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts index c78915c91f04a..2957f3b1bf3f2 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts @@ -171,9 +171,7 @@ export class AcceptInlineCompletion extends EditorAction { SuggestContext.Visible.toNegated(), EditorContextKeys.hoverFocused.toNegated(), - //InlineCompletionContextKeys.cursorInIndentation.toNegated(), - InlineCompletionContextKeys.hasSelection.toNegated(), - InlineCompletionContextKeys.cursorAtInlineEdit, + InlineCompletionContextKeys.tabShouldAcceptInlineEdit, ) ), }, @@ -220,12 +218,10 @@ export class JumpToNextInlineEdit extends EditorAction { weight: 201, kbExpr: ContextKeyExpr.and( InlineCompletionContextKeys.inlineEditVisible, - //InlineCompletionContextKeys.cursorInIndentation.toNegated(), - InlineCompletionContextKeys.hasSelection.toNegated(), EditorContextKeys.tabMovesFocus.toNegated(), SuggestContext.Visible.toNegated(), EditorContextKeys.hoverFocused.toNegated(), - InlineCompletionContextKeys.cursorAtInlineEdit.toNegated(), + InlineCompletionContextKeys.tabShouldJumpToInlineEdit, ), } }); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts index 5b0407d8afc31..db7027aa6bd7c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts @@ -23,7 +23,8 @@ export class InlineCompletionContextKeys extends Disposable { public static readonly hasSelection = new RawContextKey('editor.hasSelection', false, localize('editor.hasSelection', "Whether the editor has a selection")); public static readonly cursorAtInlineEdit = new RawContextKey('cursorAtInlineEdit', false, localize('cursorAtInlineEdit', "Whether the cursor is at an inline edit")); public static readonly inlineEditVisible = new RawContextKey('inlineEditIsVisible', false, localize('inlineEditVisible', "Whether an inline edit is visible")); - + public static readonly tabShouldJumpToInlineEdit = new RawContextKey('tabShouldJumpToInlineEdit', false, localize('tabShouldJumpToInlineEdit', "Whether tab should jump to an inline edit.")); + public static readonly tabShouldAcceptInlineEdit = new RawContextKey('tabShouldAcceptInlineEdit', false, localize('tabShouldAcceptInlineEdit', "Whether tab should accept the inline edit.")); public readonly inlineCompletionVisible = InlineCompletionContextKeys.inlineSuggestionVisible.bindTo(this.contextKeyService); public readonly inlineCompletionSuggestsIndentation = InlineCompletionContextKeys.inlineSuggestionHasIndentation.bindTo(this.contextKeyService); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index c71e8b01a6a17..ca4c3ee19b133 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -299,6 +299,8 @@ export class InlineCompletionsController extends Disposable { const s = m?.state?.read(reader); return s?.kind === 'inlineEdit' && s.cursorAtInlineEdit; }))); + this._register(contextKeySvcObs.bind(InlineCompletionContextKeys.tabShouldAcceptInlineEdit, this.model.map((m, r) => !!m?.tabShouldAcceptInlineEdit.read(r)))); + this._register(contextKeySvcObs.bind(InlineCompletionContextKeys.tabShouldJumpToInlineEdit, this.model.map((m, r) => !!m?.tabShouldJumpToInlineEdit.read(r)))); } public playAccessibilitySignal(tx: ITransaction) { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index d09375176e42a..bb6eedc415cd6 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -13,6 +13,7 @@ import { isDefined } from '../../../../../base/common/types.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor } from '../../../../browser/editorBrowser.js'; +import { observableCodeEditor } from '../../../../browser/observableCodeEditor.js'; import { EditOperation } from '../../../../common/core/editOperation.js'; import { LineRange } from '../../../../common/core/lineRange.js'; import { Position } from '../../../../common/core/position.js'; @@ -48,6 +49,8 @@ export class InlineCompletionsModel extends Disposable { private _isAcceptingPartially = false; public get isAcceptingPartially() { return this._isAcceptingPartially; } + private readonly _editorObs = observableCodeEditor(this._editor); + constructor( public readonly textModel: ITextModel, public readonly selectedSuggestItem: IObservable, @@ -403,6 +406,44 @@ export class InlineCompletionsModel extends Disposable { return v?.primaryGhostText; }); + private readonly _tabShouldIndent = derived(this, reader => { + function isMultiLine(range: Range): boolean { + return range.startLineNumber !== range.endLineNumber; + } + + function getNonIndentationRange(model: ITextModel, lineNumber: number): Range { + const columnStart = model.getLineIndentColumn(lineNumber); + const lastNonWsColumn = model.getLineLastNonWhitespaceColumn(lineNumber); + const columnEnd = Math.max(lastNonWsColumn, columnStart); + return new Range(lineNumber, columnStart, lineNumber, columnEnd); + } + + const selections = this._editorObs.selections.read(reader); + return selections?.some(s => { + if (s.isEmpty()) { + return this.textModel.getLineLength(s.startLineNumber) === 0; + } else { + return isMultiLine(s) || s.containsRange(getNonIndentationRange(this.textModel, s.startLineNumber)); + } + }); + }); + + public readonly tabShouldJumpToInlineEdit = derived(this, reader => { + if (this._tabShouldIndent.read(reader)) { return false; } + + const s = this.inlineEditState.read(reader); + if (!s) { return false; } + return !s.cursorAtInlineEdit; + }); + + public readonly tabShouldAcceptInlineEdit = derived(this, reader => { + if (this._tabShouldIndent.read(reader)) { return false; } + + const s = this.inlineEditState.read(reader); + if (!s) { return false; } + return s.cursorAtInlineEdit; + }); + private async _deltaSelectedInlineCompletionIndex(delta: 1 | -1): Promise { await this.triggerExplicitly(); From 2f437447613ad487d7f60f0953012c525a90e30e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:44:09 -0700 Subject: [PATCH 105/555] Add setupDelayedHoverAtMouse --- src/vs/base/browser/ui/hover/hover.ts | 21 +++++++++- .../base/browser/ui/hover/hoverDelegate2.ts | 1 + .../services/hoverService/hoverService.ts | 40 +++++++++++++------ .../services/hoverService/hoverWidget.ts | 3 +- .../hover/test/browser/nullHoverService.ts | 3 ++ 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 59b992091eda0..354a17880d18b 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -50,6 +50,7 @@ export interface IHoverDelegate2 { /** * A simple wrapper around showDelayedHover that includes listening to the mouseover event of * the {@link target} element. + * * @param target The target element to listener for mouseover events on. * @param options The options of the hover. * @param groupId The group ID of the hover. If the group ID is the same as the currently shown @@ -62,6 +63,24 @@ export interface IHoverDelegate2 { groupId: number | string | undefined, ): IDisposable; + /** + * A simple wrapper around showDelayedHover that includes listening to the mouseover event of + * the {@link target} element. This differs from {@link setupDelayedHover} in that the hover + * will be shown at the mouse position instead of the target position, ignoring any + * {@link IHoverOptions.position position options} that are passed in. + * + * @param target The target element to listener for mouseover events on. + * @param options The options of the hover. + * @param groupId The group ID of the hover. If the group ID is the same as the currently shown + * hover, the hover will be shown immediately, skipping the delay. + */ + setupDelayedHoverAtMouse( + target: HTMLElement, + // TODO: Support using a simple string (content) as options? + options: (() => Omit) | Omit, + groupId: number | string | undefined, + ): IDisposable; + /** * Hides the hover if it was visible. This call will be ignored if the the hover is currently * "locked" via the alt/option key. @@ -193,7 +212,7 @@ export interface IHoverPositionOptions { * if there is not enough room to layout the hover in the specified position, unless the * forcePosition option is set. */ - hoverPosition?: HoverPosition; + hoverPosition?: HoverPosition | MouseEvent; /** * Force the hover position, reducing the size of the hover instead of adjusting the hover diff --git a/src/vs/base/browser/ui/hover/hoverDelegate2.ts b/src/vs/base/browser/ui/hover/hoverDelegate2.ts index 3027fdc54460a..05b4c692d940d 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate2.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate2.ts @@ -10,6 +10,7 @@ let baseHoverDelegate: IHoverDelegate2 = { showHover: () => undefined, showDelayedHover: () => undefined, setupDelayedHover: () => Disposable.None, + setupDelayedHoverAtMouse: () => Disposable.None, hideHover: () => undefined, showAndFocusLastHover: () => undefined, setupManagedHover: () => null!, diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 8566ba20c5a3a..31d2a97855f2a 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -25,7 +25,7 @@ import type { IHoverDelegate, IHoverDelegateTarget } from '../../../../base/brow import { ManagedHoverWidget } from './updatableHoverWidget.js'; import { timeout, TimeoutTimer } from '../../../../base/common/async.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; +import { isNumber } from '../../../../base/common/types.js'; export class HoverService extends Disposable implements IHoverService { declare readonly _serviceBrand: undefined; @@ -126,23 +126,28 @@ export class HoverService extends Disposable implements IHoverService { options: (() => Omit) | Omit, groupId: number | string | undefined, ): IDisposable { - return addDisposableListener(target, EventType.MOUSE_OVER, e => { + return addDisposableListener(target, EventType.MOUSE_OVER, () => { const resolvedOptions: IHoverOptions = { ...typeof options === 'function' ? options() : options, target }; - if (resolvedOptions.position?.hoverPosition === HoverPosition.MOUSE) { - resolvedOptions.target = { - targetElements: [target], - x: e.x + 10 - }; - } + this.showDelayedHover(resolvedOptions, groupId); + }); + } - // TODO: setupManagedHover forces pointer on element hovers, we don't want to force it though - // else { // element - // resolvedOptions.appearance ||= {}; - // resolvedOptions.appearance.showPointer = true; - // } + setupDelayedHoverAtMouse( + target: HTMLElement, + options: (() => Omit) | Omit, + groupId: number | string | undefined, + ): IDisposable { + return addDisposableListener(target, EventType.MOUSE_OVER, e => { + const resolvedOptions: IHoverOptions = { + ...typeof options === 'function' ? options() : options, + target: { + targetElements: [target], + x: e.x + 10, + } + }; this.showDelayedHover(resolvedOptions, groupId); }); } @@ -175,6 +180,15 @@ export class HoverService extends Disposable implements IHoverService { if (options.persistence?.sticky) { hover.isLocked = true; } + + // Adjust target position when a mouse event is provided as the hover position + if (options.position?.hoverPosition && !isNumber(options.position.hoverPosition)) { + options.target = { + targetElements: isHTMLElement(options.target) ? [options.target] : options.target.targetElements, + x: options.position.hoverPosition.x + 10 + }; + } + hover.onDispose(() => { const hoverWasFocused = this._currentHover?.domNode && isAncestorOfActiveElement(this._currentHover.domNode); if (hoverWasFocused) { diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index c8a5551e7137c..089ec334be5f6 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -24,6 +24,7 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common import { status } from '../../../../base/browser/ui/aria/aria.js'; import type { IHoverOptions, IHoverTarget, IHoverWidget } from '../../../../base/browser/ui/hover/hover.js'; import { TimeoutTimer } from '../../../../base/common/async.js'; +import { isNumber } from '../../../../base/common/types.js'; const $ = dom.$; type TargetRect = { @@ -131,7 +132,7 @@ export class HoverWidget extends Widget implements IHoverWidget { this._enableFocusTraps = true; } - this._hoverPosition = options.position?.hoverPosition ?? HoverPosition.ABOVE; + this._hoverPosition = !options.position?.hoverPosition || !isNumber(options.position.hoverPosition) ? HoverPosition.ABOVE : options.position.hoverPosition; // Don't allow mousedown out of the widget, otherwise preventDefault will call and text will // not be selected. diff --git a/src/vs/platform/hover/test/browser/nullHoverService.ts b/src/vs/platform/hover/test/browser/nullHoverService.ts index a4266ef7c9309..4644330752bf6 100644 --- a/src/vs/platform/hover/test/browser/nullHoverService.ts +++ b/src/vs/platform/hover/test/browser/nullHoverService.ts @@ -10,6 +10,9 @@ export const NullHoverService: IHoverService = { _serviceBrand: undefined, hideHover: () => undefined, showHover: () => undefined, + showDelayedHover: () => undefined, + setupDelayedHover: () => Disposable.None, + setupDelayedHoverAtMouse: () => Disposable.None, setupManagedHover: () => Disposable.None as any, showAndFocusLastHover: () => undefined, showManagedHover: () => undefined From 4959dfe4c3b56fb98f07e874d1067fed69f8432d Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 30 Oct 2024 17:44:55 +0100 Subject: [PATCH 106/555] Refactoring code relative to color-decorators (#232620) * wip * fixing compilation errors --- .../hoverColorPickerContribution.ts | 2 +- .../hoverColorPickerParticipant.ts | 24 +++++++-- .../hover/browser/contentHoverComputer.ts | 4 +- .../hover/browser/contentHoverController.ts | 51 +++---------------- .../contrib/hover/browser/hoverOperation.ts | 3 +- .../contrib/hover/browser/hoverTypes.ts | 5 +- .../hover/browser/markdownHoverParticipant.ts | 3 +- .../inlayHints/browser/inlayHintsHover.ts | 3 +- 8 files changed, 39 insertions(+), 56 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts index cc302544fbca0..63ed7dc725959 100644 --- a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts +++ b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts @@ -49,7 +49,7 @@ export class HoverColorPickerContribution extends Disposable implements IEditorC return; } const range = new Range(targetRange.startLineNumber, targetRange.startColumn + 1, targetRange.endLineNumber, targetRange.endColumn + 1); - hoverController.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Mouse, false, true); + hoverController.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Click, false); } } diff --git a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts index 3b478cd397b50..119810b7b6d12 100644 --- a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts +++ b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts @@ -20,6 +20,7 @@ import { EditorOption } from '../../../../common/config/editorOptions.js'; import { Dimension } from '../../../../../base/browser/dom.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { Color } from '../../../../../base/common/color.js'; +import { HoverStartSource } from '../../../hover/browser/hoverOperation.js'; export class ColorHover implements IHoverPart, BaseColor { @@ -60,18 +61,21 @@ export class HoverColorPickerParticipant implements IEditorHoverParticipant { - return AsyncIterableObject.fromPromise(this._computeAsync(anchor, lineDecorations, token)); + public computeAsync(anchor: HoverAnchor, lineDecorations: IModelDecoration[], source: HoverStartSource, token: CancellationToken): AsyncIterableObject { + return AsyncIterableObject.fromPromise(this._computeAsync(anchor, lineDecorations, source)); } - private async _computeAsync(_anchor: HoverAnchor, lineDecorations: IModelDecoration[], _token: CancellationToken): Promise { + private async _computeAsync(_anchor: HoverAnchor, lineDecorations: IModelDecoration[], source: HoverStartSource): Promise { if (!this._editor.hasModel()) { return []; } + if (!this._isValidRequest(source)) { + return []; + } const colorDetector = ColorDetector.get(this._editor); if (!colorDetector) { return []; @@ -91,6 +95,18 @@ export class HoverColorPickerParticipant implements IEditorHoverParticipant { const editor = this._editor; if (hoverParts.length === 0 || !editor.hasModel()) { diff --git a/src/vs/editor/contrib/hover/browser/contentHoverComputer.ts b/src/vs/editor/contrib/hover/browser/contentHoverComputer.ts index c24cc1607494d..6306de9e5f77f 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverComputer.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverComputer.ts @@ -78,7 +78,7 @@ export class ContentHoverComputer implements IHoverComputer()); @@ -58,10 +52,7 @@ export class ContentHoverController extends Disposable implements IEditorContrib private _reactToEditorMouseMoveRunner: RunOnceScheduler; private _hoverSettings!: IHoverSettings; - private _hoverState: IHoverState = { - mouseDown: false, - activatedByDecoratorClick: false - }; + private _isMouseDown: boolean = false; constructor( private readonly _editor: ICodeEditor, @@ -132,7 +123,7 @@ export class ContentHoverController extends Disposable implements IEditorContrib private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { - this._hoverState.mouseDown = true; + this._isMouseDown = true; const shouldNotHideCurrentHoverWidget = this._shouldNotHideCurrentHoverWidget(mouseEvent); if (shouldNotHideCurrentHoverWidget) { @@ -155,7 +146,7 @@ export class ContentHoverController extends Disposable implements IEditorContrib } private _onEditorMouseUp(): void { - this._hoverState.mouseDown = false; + this._isMouseDown = false; } private _onEditorMouseLeave(mouseEvent: IPartialEditorMouseEvent): void { @@ -242,29 +233,8 @@ export class ContentHoverController extends Disposable implements IEditorContrib if (!mouseEvent) { return; } - - const target = mouseEvent.target; - const mouseOnDecorator = target.element?.classList.contains('colorpicker-color-decoration'); - const decoratorActivatedOn = this._editor.getOption(EditorOption.colorDecoratorsActivatedOn); - - const enabled = this._hoverSettings.enabled; - const activatedByDecoratorClick = this._hoverState.activatedByDecoratorClick; - if ( - ( - mouseOnDecorator && ( - (decoratorActivatedOn === 'click' && !activatedByDecoratorClick) || - (decoratorActivatedOn === 'hover' && !enabled && !_sticky) || - (decoratorActivatedOn === 'clickAndHover' && !enabled && !activatedByDecoratorClick)) - ) || ( - !mouseOnDecorator && !enabled && !activatedByDecoratorClick - ) - ) { - this._hideWidgets(); - return; - } - - const contentHoverShowsOrWillShow = this._tryShowHoverWidget(mouseEvent); - if (contentHoverShowsOrWillShow) { + const contentWidget: ContentHoverWidgetWrapper = this._getOrCreateContentWidget(); + if (contentWidget.showsOrWillShow(mouseEvent)) { return; } @@ -274,10 +244,6 @@ export class ContentHoverController extends Disposable implements IEditorContrib this._hideWidgets(); } - private _tryShowHoverWidget(mouseEvent: IEditorMouseEvent): boolean { - const contentWidget: IHoverWidget = this._getOrCreateContentWidget(); - return contentWidget.showsOrWillShow(mouseEvent); - } private _onKeyDown(e: IKeyboardEvent): void { if (!this._editor.hasModel()) { @@ -318,12 +284,11 @@ export class ContentHoverController extends Disposable implements IEditorContrib return; } if (( - this._hoverState.mouseDown + this._isMouseDown && this._contentWidget?.isColorPickerVisible ) || InlineSuggestionHintsContentWidget.dropDownVisible) { return; } - this._hoverState.activatedByDecoratorClick = false; this._contentWidget?.hide(); } @@ -343,10 +308,8 @@ export class ContentHoverController extends Disposable implements IEditorContrib range: Range, mode: HoverStartMode, source: HoverStartSource, - focus: boolean, - activatedByColorDecoratorClick: boolean = false + focus: boolean ): void { - this._hoverState.activatedByDecoratorClick = activatedByColorDecoratorClick; this._getOrCreateContentWidget().startShowingAtRange(range, mode, source, focus); } diff --git a/src/vs/editor/contrib/hover/browser/hoverOperation.ts b/src/vs/editor/contrib/hover/browser/hoverOperation.ts index cb73dddfb5cc1..daa3e06cc3bdf 100644 --- a/src/vs/editor/contrib/hover/browser/hoverOperation.ts +++ b/src/vs/editor/contrib/hover/browser/hoverOperation.ts @@ -37,7 +37,8 @@ export const enum HoverStartMode { export const enum HoverStartSource { Mouse = 0, - Keyboard = 1 + Click = 1, + Keyboard = 2 } export class HoverResult { diff --git a/src/vs/editor/contrib/hover/browser/hoverTypes.ts b/src/vs/editor/contrib/hover/browser/hoverTypes.ts index 7c1b9503bd3bd..e67fdbe9ff1b8 100644 --- a/src/vs/editor/contrib/hover/browser/hoverTypes.ts +++ b/src/vs/editor/contrib/hover/browser/hoverTypes.ts @@ -12,6 +12,7 @@ import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { IModelDecoration } from '../../../common/model.js'; import { BrandedService, IConstructorSignature } from '../../../../platform/instantiation/common/instantiation.js'; +import { HoverStartSource } from './hoverOperation.js'; export interface IHoverPart { /** @@ -155,8 +156,8 @@ export class RenderedHoverParts implements IRenderedHoverP export interface IEditorHoverParticipant { readonly hoverOrdinal: number; suggestHoverAnchor?(mouseEvent: IEditorMouseEvent): HoverAnchor | null; - computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): T[]; - computeAsync?(anchor: HoverAnchor, lineDecorations: IModelDecoration[], token: CancellationToken): AsyncIterableObject; + computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[], source: HoverStartSource): T[]; + computeAsync?(anchor: HoverAnchor, lineDecorations: IModelDecoration[], source: HoverStartSource, token: CancellationToken): AsyncIterableObject; createLoadingMessage?(anchor: HoverAnchor): T | null; renderHoverParts(context: IEditorHoverRenderContext, hoverParts: T[]): IRenderedHoverParts; getAccessibleContent(hoverPart: T): string; diff --git a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts index db9528985a708..7e01b66410536 100644 --- a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts @@ -34,6 +34,7 @@ import { AsyncIterableObject } from '../../../../base/common/async.js'; import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js'; import { getHoverProviderResultsAsAsyncIterable } from './getHover.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { HoverStartSource } from './hoverOperation.js'; const $ = dom.$; const increaseHoverVerbosityIcon = registerIcon('hover-increase-verbosity', Codicon.add, nls.localize('increaseHoverVerbosity', 'Icon for increaseing hover verbosity.')); @@ -151,7 +152,7 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant { + public computeAsync(anchor: HoverAnchor, lineDecorations: IModelDecoration[], source: HoverStartSource, token: CancellationToken): AsyncIterableObject { if (!this._editor.hasModel() || anchor.type !== HoverAnchorType.Range) { return AsyncIterableObject.EMPTY; } diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts index 7587ab9a72d43..9c7320f98b5fa 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts @@ -27,6 +27,7 @@ import { isNonEmptyArray } from '../../../../base/common/arrays.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { HoverStartSource } from '../../hover/browser/hoverOperation.js'; class InlayHintsHoverAnchor extends HoverForeignElementAnchor { constructor( @@ -76,7 +77,7 @@ export class InlayHintsHover extends MarkdownHoverParticipant implements IEditor return []; } - override computeAsync(anchor: HoverAnchor, _lineDecorations: IModelDecoration[], token: CancellationToken): AsyncIterableObject { + override computeAsync(anchor: HoverAnchor, _lineDecorations: IModelDecoration[], source: HoverStartSource, token: CancellationToken): AsyncIterableObject { if (!(anchor instanceof InlayHintsHoverAnchor)) { return AsyncIterableObject.EMPTY; } From 497119e2b80bcbf4484b16192941d9b6fb72d85c Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 30 Oct 2024 09:46:13 -0700 Subject: [PATCH 107/555] Use ITextBuffer in CellTextModel (#230434) ITextBuffer --- .../contrib/notebook/common/model/notebookCellTextModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 7c7ff8c87f360..637b18bd4cc5e 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -106,7 +106,7 @@ export class NotebookCellTextModel extends Disposable implements ICell { this._onDidChangeContent.fire('mime'); } - private _textBuffer!: model.IReadonlyTextBuffer; + private _textBuffer!: model.ITextBuffer; get textBuffer() { if (this._textBuffer) { From 48931d6b24ddb6f7773a5990d2b2abfa5bb6a50a Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 30 Oct 2024 09:57:22 -0700 Subject: [PATCH 108/555] bump distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8da10e14c030e..f393c9a47cc67 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.96.0", - "distro": "2ac4fd6b98c3720a7f66b21121c105f2e9db964d", + "distro": "957bf1ee92a5478360b9eca4d38bbc1e3fc9d654", "author": { "name": "Microsoft Corporation" }, From e30352e170400801c1aad6f8492f2b98ee488b2b Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 30 Oct 2024 17:59:28 +0100 Subject: [PATCH 109/555] Fixes https://github.com/microsoft/vscode-copilot/issues/9965 (#232629) --- src/vs/editor/common/config/editorOptions.ts | 20 ++- .../controller/inlineCompletionsController.ts | 2 +- .../view/inlineEdits/inlineEditsView.ts | 133 ++---------------- .../inlineEditsViewAndDiffProducer.ts | 126 +++++++++++++++++ src/vs/monaco.d.ts | 7 +- 5 files changed, 164 insertions(+), 124 deletions(-) create mode 100644 src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewAndDiffProducer.ts diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 423c4ca967630..6fcc73d944c61 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4152,14 +4152,20 @@ export interface IInlineSuggestOptions { edits?: { experimental?: { enabled?: boolean; + // side-by-side, mixed lines diff, interleaved lines diff + useMixedLinesDiff?: 'never' | 'whenPossible'; }; }; } +type RequiredRecursive = { + [P in keyof T]-?: T[P] extends object | undefined ? RequiredRecursive : T[P]; +}; + /** * @internal */ -export type InternalInlineSuggestOptions = Readonly>; +export type InternalInlineSuggestOptions = Readonly>; /** * Configuration options for inline suggestions @@ -4177,6 +4183,7 @@ class InlineEditorSuggest extends BaseEditorOption this._modelService.createModel( - '', null, this._modelUriGenerator.getUniqueUri())).keepObserved(this._store); - private readonly _modifiedModel = derivedDisposable(() => this._modelService.createModel( - '', null, this._modelUriGenerator.getUniqueUri())).keepObserved(this._store); - - private readonly _differ = new LRUCachedFunction({ getCacheKey: JSON.stringify }, (arg: { original: string; modified: string }) => { - this._originalModel.get().setValue(arg.original); - this._modifiedModel.get().setValue(arg.modified); - - const diffAlgo = this._diffProviderFactoryService.createDiffProvider({ diffAlgorithm: 'advanced' }); - - return ObservablePromise.fromFn(async () => { - const result = await diffAlgo.computeDiff(this._originalModel.get(), this._modifiedModel.get(), { - computeMoves: false, - ignoreTrimWhitespace: false, - maxComputationTimeMs: 1000, - }, CancellationToken.None); - return result; - }); - }); - - private readonly _inlineEditPromise = derived | undefined>(this, (reader) => { - const inlineEdit = this._edit.read(reader); - if (!inlineEdit) { return undefined; } - - //if (inlineEdit.text.trim() === '') { return undefined; } - - const text = new TextModelText(this._editor.getModel()!); - const edit = inlineEdit.edit.extendToFullLine(text); - - const diffResult = this._differ.get({ original: this._editor.getModel()!.getValueInRange(edit.range), modified: edit.text }); - - return diffResult.promiseResult.map(p => { - if (!p || !p.data) { - return undefined; - } - const result = p.data; - - const rangeStartPos = edit.range.getStartPosition(); - const innerChanges = result.changes.flatMap(c => c.innerChanges!); - - function addRangeToPos(pos: Position, range: Range): Range { - const start = TextLength.fromPosition(range.getStartPosition()); - return TextLength.ofRange(range).createRange(start.addToPosition(pos)); - } - - const edits = innerChanges.map(c => new SingleTextEdit( - addRangeToPos(rangeStartPos, c.originalRange), - this._modifiedModel.get()!.getValueInRange(c.modifiedRange) - )); - const diffEdits = new TextEdit(edits); - - return new InlineEditWithChanges(text, diffEdits, inlineEdit.isCollapsed, true, inlineEdit.commands); //inlineEdit.showInlineIfPossible); - }); - }); - - private readonly _inlineEdit = derivedOpts({ owner: this, equalsFn: equalsIfDefined(itemEquals()) }, reader => this._inlineEditPromise.read(reader)?.read(reader)); - - constructor( - private readonly _editor: ICodeEditor, - private readonly _edit: IObservable, - private readonly _model: IObservable, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IDiffProviderFactoryService private readonly _diffProviderFactoryService: IDiffProviderFactoryService, - @IModelService private readonly _modelService: IModelService, - ) { - super(); - - this._register(this._instantiationService.createInstance(InlineEditsView, this._editor, this._inlineEdit, this._model)); - } -} - -export class InlineEditWithChanges { - public readonly lineEdit = SingleLineEdit.fromSingleTextEdit(this.edit.toSingle(this.originalText), this.originalText); - - public readonly originalLineRange = this.lineEdit.lineRange; - public readonly modifiedLineRange = this.lineEdit.toLineEdit().getNewLineRanges()[0]; - - constructor( - public readonly originalText: AbstractText, - public readonly edit: TextEdit, - public readonly isCollapsed: boolean, - public readonly showInlineIfPossible: boolean, - public readonly commands: readonly Command[], - ) { - } - - equals(other: InlineEditWithChanges) { - return this.originalText.getValue() === other.originalText.getValue() && - this.edit.equals(other.edit) && - this.isCollapsed === other.isCollapsed && - this.showInlineIfPossible === other.showInlineIfPossible && - this.commands === other.commands; - } -} +import { InlineEditWithChanges } from './inlineEditsViewAndDiffProducer.js'; export const originalBackgroundColor = registerColor( 'inlineEdit.originalBackground', @@ -192,6 +83,8 @@ export class InlineEditsView extends Disposable { ]), ]); + private readonly _diffPresentationMode = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useMixedLinesDiff); + constructor( private readonly _editor: ICodeEditor, private readonly _edit: IObservable, @@ -301,11 +194,13 @@ export class InlineEditsView extends Disposable { ) )!; - let state: 'collapsed' | 'inline' | 'sideBySide' = 'sideBySide'; + let state: 'collapsed' | 'mixedInline' | 'sideBySide'; if (edit.isCollapsed) { state = 'collapsed'; - } else if (edit.showInlineIfPossible && diff.every(m => OriginalEditorInlineDiffView.supportsInlineDiffRendering(m))) { - state = 'inline'; + } else if (edit.showInlineIfPossible && diff.every(m => OriginalEditorInlineDiffView.supportsInlineDiffRendering(m)) && this._diffPresentationMode.read(reader) === 'whenPossible') { + state = 'mixedInline'; + } else { + state = 'sideBySide'; } return { @@ -506,7 +401,7 @@ export class InlineEditsView extends Disposable { return { modifiedText: new StringText(e.newText), diff: e.diff, - showInline: e.edit.showInlineIfPossible, + showInline: e.state === 'mixedInline', modifiedCodeEditor: this._previewEditor, }; }); @@ -518,7 +413,7 @@ export class InlineEditsView extends Disposable { const state = this._uiState.read(reader); const edit1 = this._previewEditorLayoutInfo.read(reader)?.edit1; if (!edit1 || !state) { return undefined; } - return { editTopLeft: edit1, showAlways: state.state === 'collapsed' || state.state === 'inline' }; + return { editTopLeft: edit1, showAlways: state.state === 'collapsed' || state.state === 'mixedInline' }; }), this._model, )); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewAndDiffProducer.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewAndDiffProducer.ts new file mode 100644 index 0000000000000..f900f23d457a8 --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewAndDiffProducer.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LRUCachedFunction } from '../../../../../../base/common/cache.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { equalsIfDefined, itemEquals } from '../../../../../../base/common/equals.js'; +import { createHotClass } from '../../../../../../base/common/hotReloadHelpers.js'; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { derivedDisposable, ObservablePromise, derived, IObservable, derivedOpts } from '../../../../../../base/common/observable.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; +import { IDiffProviderFactoryService } from '../../../../../browser/widget/diffEditor/diffProviderFactoryService.js'; +import { SingleLineEdit } from '../../../../../common/core/lineEdit.js'; +import { Position } from '../../../../../common/core/position.js'; +import { Range } from '../../../../../common/core/range.js'; +import { SingleTextEdit, TextEdit, AbstractText } from '../../../../../common/core/textEdit.js'; +import { TextLength } from '../../../../../common/core/textLength.js'; +import { Command } from '../../../../../common/languages.js'; +import { TextModelText } from '../../../../../common/model/textModelText.js'; +import { IModelService } from '../../../../../common/services/model.js'; +import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; +import { InlineEdit } from '../../model/inlineEdit.js'; +import { InlineEditsView } from './inlineEditsView.js'; +import { UniqueUriGenerator } from './utils.js'; + +export class InlineEditsViewAndDiffProducer extends Disposable { + public static readonly hot = createHotClass(InlineEditsViewAndDiffProducer); + + private readonly _modelUriGenerator = new UniqueUriGenerator('inline-edits'); + + private readonly _originalModel = derivedDisposable(() => this._modelService.createModel( + '', null, this._modelUriGenerator.getUniqueUri())).keepObserved(this._store); + private readonly _modifiedModel = derivedDisposable(() => this._modelService.createModel( + '', null, this._modelUriGenerator.getUniqueUri())).keepObserved(this._store); + + private readonly _differ = new LRUCachedFunction({ getCacheKey: JSON.stringify }, (arg: { original: string; modified: string }) => { + this._originalModel.get().setValue(arg.original); + this._modifiedModel.get().setValue(arg.modified); + + const diffAlgo = this._diffProviderFactoryService.createDiffProvider({ diffAlgorithm: 'advanced' }); + + return ObservablePromise.fromFn(async () => { + const result = await diffAlgo.computeDiff(this._originalModel.get(), this._modifiedModel.get(), { + computeMoves: false, + ignoreTrimWhitespace: false, + maxComputationTimeMs: 1000, + }, CancellationToken.None); + return result; + }); + }); + + private readonly _inlineEditPromise = derived | undefined>(this, (reader) => { + const inlineEdit = this._edit.read(reader); + if (!inlineEdit) { return undefined; } + + //if (inlineEdit.text.trim() === '') { return undefined; } + const text = new TextModelText(this._editor.getModel()!); + const edit = inlineEdit.edit.extendToFullLine(text); + + const diffResult = this._differ.get({ original: this._editor.getModel()!.getValueInRange(edit.range), modified: edit.text }); + + return diffResult.promiseResult.map(p => { + if (!p || !p.data) { + return undefined; + } + const result = p.data; + + const rangeStartPos = edit.range.getStartPosition(); + const innerChanges = result.changes.flatMap(c => c.innerChanges!); + + function addRangeToPos(pos: Position, range: Range): Range { + const start = TextLength.fromPosition(range.getStartPosition()); + return TextLength.ofRange(range).createRange(start.addToPosition(pos)); + } + + const edits = innerChanges.map(c => new SingleTextEdit( + addRangeToPos(rangeStartPos, c.originalRange), + this._modifiedModel.get()!.getValueInRange(c.modifiedRange) + )); + const diffEdits = new TextEdit(edits); + + return new InlineEditWithChanges(text, diffEdits, inlineEdit.isCollapsed, true, inlineEdit.commands); //inlineEdit.showInlineIfPossible); + }); + }); + + private readonly _inlineEdit = derivedOpts({ owner: this, equalsFn: equalsIfDefined(itemEquals()) }, reader => this._inlineEditPromise.read(reader)?.read(reader)); + + constructor( + private readonly _editor: ICodeEditor, + private readonly _edit: IObservable, + private readonly _model: IObservable, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IDiffProviderFactoryService private readonly _diffProviderFactoryService: IDiffProviderFactoryService, + @IModelService private readonly _modelService: IModelService + ) { + super(); + + this._register(this._instantiationService.createInstance(InlineEditsView, this._editor, this._inlineEdit, this._model)); + } +} + +export class InlineEditWithChanges { + public readonly lineEdit = SingleLineEdit.fromSingleTextEdit(this.edit.toSingle(this.originalText), this.originalText); + + public readonly originalLineRange = this.lineEdit.lineRange; + public readonly modifiedLineRange = this.lineEdit.toLineEdit().getNewLineRanges()[0]; + + constructor( + public readonly originalText: AbstractText, + public readonly edit: TextEdit, + public readonly isCollapsed: boolean, + public readonly showInlineIfPossible: boolean, + public readonly commands: readonly Command[] + ) { + } + + equals(other: InlineEditWithChanges) { + return this.originalText.getValue() === other.originalText.getValue() && + this.edit.equals(other.edit) && + this.isCollapsed === other.isCollapsed && + this.showInlineIfPossible === other.showInlineIfPossible && + this.commands === other.commands; + } +} diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index caeac06342a9f..a88a1c0c00f2f 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4593,10 +4593,15 @@ declare namespace monaco.editor { edits?: { experimental?: { enabled?: boolean; + useMixedLinesDiff?: 'never' | 'whenPossible'; }; }; } + type RequiredRecursive = { + [P in keyof T]-?: T[P] extends object | undefined ? RequiredRecursive : T[P]; + }; + export interface IBracketPairColorizationOptions { /** * Enable or disable bracket pair colorization. @@ -5149,7 +5154,7 @@ declare namespace monaco.editor { smoothScrolling: IEditorOption; stopRenderingLineAfter: IEditorOption; suggest: IEditorOption>>; - inlineSuggest: IEditorOption>>; + inlineSuggest: IEditorOption>>; inlineCompletionsAccessibilityVerbose: IEditorOption; suggestFontSize: IEditorOption; suggestLineHeight: IEditorOption; From bb3967f0480d5257f89f3dc898514667799e5035 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:09:59 -0700 Subject: [PATCH 110/555] Support keyboard events in setupDelayedHover* --- src/vs/base/browser/ui/hover/hover.ts | 2 + .../services/hoverService/hoverService.ts | 52 +++++++++++++------ .../settingsEditorSettingIndicators.ts | 28 +++++----- 3 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 354a17880d18b..6ccfa992d96fc 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -61,6 +61,7 @@ export interface IHoverDelegate2 { // TODO: Support using a simple string (content) as options? options: (() => Omit) | Omit, groupId: number | string | undefined, + setupKeyboardEvents?: boolean, ): IDisposable; /** @@ -79,6 +80,7 @@ export interface IHoverDelegate2 { // TODO: Support using a simple string (content) as options? options: (() => Omit) | Omit, groupId: number | string | undefined, + setupKeyboardEvents?: boolean, ): IDisposable; /** diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 31d2a97855f2a..8687f616b1c0d 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -26,6 +26,7 @@ import { ManagedHoverWidget } from './updatableHoverWidget.js'; import { timeout, TimeoutTimer } from '../../../../base/common/async.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { isNumber } from '../../../../base/common/types.js'; +import { KeyCode } from '../../../../base/common/keyCodes.js'; export class HoverService extends Disposable implements IHoverService { declare readonly _serviceBrand: undefined; @@ -125,31 +126,50 @@ export class HoverService extends Disposable implements IHoverService { target: HTMLElement, options: (() => Omit) | Omit, groupId: number | string | undefined, + setupKeyboardEvents?: boolean, ): IDisposable { - return addDisposableListener(target, EventType.MOUSE_OVER, () => { - const resolvedOptions: IHoverOptions = { - ...typeof options === 'function' ? options() : options, - target - }; - this.showDelayedHover(resolvedOptions, groupId); - }); + const resolveOptions = () => ({ + ...typeof options === 'function' ? options() : options, + target + } satisfies IHoverOptions); + return this._setupDelayedHover(target, resolveOptions, groupId, setupKeyboardEvents); } setupDelayedHoverAtMouse( target: HTMLElement, options: (() => Omit) | Omit, groupId: number | string | undefined, + setupKeyboardEvents?: boolean, ): IDisposable { - return addDisposableListener(target, EventType.MOUSE_OVER, e => { - const resolvedOptions: IHoverOptions = { - ...typeof options === 'function' ? options() : options, - target: { - targetElements: [target], - x: e.x + 10, + const resolveOptions = (e?: MouseEvent) => ({ + ...typeof options === 'function' ? options() : options, + target: { + targetElements: [target], + x: e !== undefined ? e.x + 10 : undefined, + } + } satisfies IHoverOptions); + return this._setupDelayedHover(target, resolveOptions, groupId, setupKeyboardEvents); + } + + private _setupDelayedHover( + target: HTMLElement, + resolveOptions: ((e?: MouseEvent) => IHoverOptions), + groupId: number | string | undefined, + setupKeyboardEvents?: boolean + ) { + const store = new DisposableStore(); + store.add(addDisposableListener(target, EventType.MOUSE_OVER, e => { + this.showDelayedHover(resolveOptions(e), groupId); + })); + if (setupKeyboardEvents) { + store.add(addDisposableListener(target, EventType.KEY_DOWN, e => { + const evt = new StandardKeyboardEvent(e); + if (evt.equals(KeyCode.Space) || evt.equals(KeyCode.Enter)) { + this.showHover(resolveOptions(), true); } - }; - this.showDelayedHover(resolvedOptions, groupId); - }); + })); + } + return store; } private _createHover(options: IHoverOptions, skipLastFocusedUpdate?: boolean): HoverWidget | undefined { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index ffb4e4d84d1ad..524df002ffe1f 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -474,22 +474,18 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { isTrusted: false, supportHtml: false }; - const showHover = (focus: boolean) => { - return this.hoverService.showHover({ - ...this.defaultHoverOptions, - content, - linkHandler: (url: string) => { - const [scope, language] = decodeURIComponent(url).split(':'); - onDidClickOverrideElement.fire({ - settingKey: element.setting.key, - scope: scope as ScopeString, - language - }); - }, - target: this.scopeOverridesIndicator.element - }, focus); - }; - this.addHoverDisposables(this.scopeOverridesIndicator.disposables, this.scopeOverridesIndicator.element, showHover); + this.scopeOverridesIndicator.disposables.add(this.hoverService.setupDelayedHover(this.scopeOverridesIndicator.element, () => ({ + ...this.defaultHoverOptions, + content, + linkHandler: (url: string) => { + const [scope, language] = decodeURIComponent(url).split(':'); + onDidClickOverrideElement.fire({ + settingKey: element.setting.key, + scope: scope as ScopeString, + language + }); + } + }), undefined, true)); } } this.render(); From 0228d5aca17c1ba219ebbccdee5501afc1840ca9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:14:34 -0700 Subject: [PATCH 111/555] Consolidate delayed options into an object --- src/vs/base/browser/ui/hover/hover.ts | 34 ++++++++++++------- .../services/hoverService/hoverService.ts | 27 +++++++-------- .../browser/parts/compositeBarActions.ts | 2 +- .../settingsEditorSettingIndicators.ts | 2 +- .../terminal/browser/xterm/decorationAddon.ts | 2 +- 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 6ccfa992d96fc..30ca0ad9e4d7a 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -52,16 +52,14 @@ export interface IHoverDelegate2 { * the {@link target} element. * * @param target The target element to listener for mouseover events on. - * @param options The options of the hover. - * @param groupId The group ID of the hover. If the group ID is the same as the currently shown - * hover, the hover will be shown immediately, skipping the delay. + * @param hoverOptions The options of the hover. + * @param delayedHoverOptions The options of the delayed hover. */ setupDelayedHover( target: HTMLElement, // TODO: Support using a simple string (content) as options? - options: (() => Omit) | Omit, - groupId: number | string | undefined, - setupKeyboardEvents?: boolean, + hoverOptions: (() => Omit) | Omit, + delayedHoverOptions?: IDelayedHoverOptions, ): IDisposable; /** @@ -71,16 +69,14 @@ export interface IHoverDelegate2 { * {@link IHoverOptions.position position options} that are passed in. * * @param target The target element to listener for mouseover events on. - * @param options The options of the hover. - * @param groupId The group ID of the hover. If the group ID is the same as the currently shown - * hover, the hover will be shown immediately, skipping the delay. + * @param hoverOptions The options of the hover. + * @param delayedHoverOptions The options of the delayed hover. */ setupDelayedHoverAtMouse( target: HTMLElement, // TODO: Support using a simple string (content) as options? - options: (() => Omit) | Omit, - groupId: number | string | undefined, - setupKeyboardEvents?: boolean, + hoverOptions: (() => Omit) | Omit, + delayedHoverOptions?: IDelayedHoverOptions, ): IDisposable; /** @@ -208,6 +204,20 @@ export interface IHoverOptions { appearance?: IHoverAppearanceOptions; } +export interface IDelayedHoverOptions { + /** + * The group ID of the hover. If the group ID is the same as the currently shown hover, the + * hover will be shown immediately, skipping the delay. + */ + groupId?: number | string; + + /** + * Whether to set up space and enter keyboard events for the hover, when these are pressed when + * the hover's target is focused it will show and focus the hover. + */ + setupKeyboardEvents?: boolean; +} + export interface IHoverPositionOptions { /** * Position of the hover. The default is to show above the target. This option will be ignored diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 8687f616b1c0d..1801e616df725 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -20,7 +20,7 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; import { mainWindow } from '../../../../base/browser/window.js'; import { ContextViewHandler } from '../../../../platform/contextview/browser/contextViewService.js'; -import type { IDelayedHoverWidget, IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; +import type { IDelayedHoverOptions, IDelayedHoverWidget, IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; import type { IHoverDelegate, IHoverDelegateTarget } from '../../../../base/browser/ui/hover/hoverDelegate.js'; import { ManagedHoverWidget } from './updatableHoverWidget.js'; import { timeout, TimeoutTimer } from '../../../../base/common/async.js'; @@ -125,47 +125,44 @@ export class HoverService extends Disposable implements IHoverService { setupDelayedHover( target: HTMLElement, options: (() => Omit) | Omit, - groupId: number | string | undefined, - setupKeyboardEvents?: boolean, + delayedHoverOptions?: IDelayedHoverOptions, ): IDisposable { - const resolveOptions = () => ({ + const resolveHoverOptions = () => ({ ...typeof options === 'function' ? options() : options, target } satisfies IHoverOptions); - return this._setupDelayedHover(target, resolveOptions, groupId, setupKeyboardEvents); + return this._setupDelayedHover(target, resolveHoverOptions, delayedHoverOptions); } setupDelayedHoverAtMouse( target: HTMLElement, options: (() => Omit) | Omit, - groupId: number | string | undefined, - setupKeyboardEvents?: boolean, + delayedHoverOptions?: IDelayedHoverOptions, ): IDisposable { - const resolveOptions = (e?: MouseEvent) => ({ + const resolveHoverOptions = (e?: MouseEvent) => ({ ...typeof options === 'function' ? options() : options, target: { targetElements: [target], x: e !== undefined ? e.x + 10 : undefined, } } satisfies IHoverOptions); - return this._setupDelayedHover(target, resolveOptions, groupId, setupKeyboardEvents); + return this._setupDelayedHover(target, resolveHoverOptions, delayedHoverOptions); } private _setupDelayedHover( target: HTMLElement, - resolveOptions: ((e?: MouseEvent) => IHoverOptions), - groupId: number | string | undefined, - setupKeyboardEvents?: boolean + resolveHoverOptions: ((e?: MouseEvent) => IHoverOptions), + delayedHoverOptions?: IDelayedHoverOptions, ) { const store = new DisposableStore(); store.add(addDisposableListener(target, EventType.MOUSE_OVER, e => { - this.showDelayedHover(resolveOptions(e), groupId); + this.showDelayedHover(resolveHoverOptions(e), delayedHoverOptions?.groupId); })); - if (setupKeyboardEvents) { + if (delayedHoverOptions?.setupKeyboardEvents) { store.add(addDisposableListener(target, EventType.KEY_DOWN, e => { const evt = new StandardKeyboardEvent(e); if (evt.equals(KeyCode.Space) || evt.equals(KeyCode.Enter)) { - this.showHover(resolveOptions(), true); + this.showHover(resolveHoverOptions(), true); } })); } diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 944d7d2d04dc9..a919b1a2efaa7 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -269,7 +269,7 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { showPointer: true, compact: true, } - }), 'composite-bar-actions')); + }), { groupId: 'composite-bar-actions' })); // Label this.label = append(container, $('a')); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 524df002ffe1f..76b3878ed8516 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -485,7 +485,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { language }); } - }), undefined, true)); + }), { setupKeyboardEvents: true })); } } this.render(); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 8d7bd48bbc484..a5a3289a59178 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -323,7 +323,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { private _createHover(element: HTMLElement, command: ITerminalCommand | undefined, hoverMessage?: string) { return this._hoverService.setupDelayedHover(element, () => ({ content: new MarkdownString(getTerminalDecorationHoverContent(command, hoverMessage)) - }), undefined); + })); } private _updateClasses(element?: HTMLElement, exitCode?: number, markProperties?: IMarkProperties): void { From a2da57d6f81fcd97b6a9296e804e71efd8a45ed7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:15:07 -0700 Subject: [PATCH 112/555] Rename to lifecycleOptions --- src/vs/base/browser/ui/hover/hover.ts | 10 +++++----- .../services/hoverService/hoverService.ts | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 30ca0ad9e4d7a..54d62627c399b 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -53,13 +53,13 @@ export interface IHoverDelegate2 { * * @param target The target element to listener for mouseover events on. * @param hoverOptions The options of the hover. - * @param delayedHoverOptions The options of the delayed hover. + * @param lifecycleOptions The options of the delayed hover. */ setupDelayedHover( target: HTMLElement, // TODO: Support using a simple string (content) as options? hoverOptions: (() => Omit) | Omit, - delayedHoverOptions?: IDelayedHoverOptions, + lifecycleOptions?: IHoverLifecycleOptions, ): IDisposable; /** @@ -70,13 +70,13 @@ export interface IHoverDelegate2 { * * @param target The target element to listener for mouseover events on. * @param hoverOptions The options of the hover. - * @param delayedHoverOptions The options of the delayed hover. + * @param lifecycleOptions The options of the delayed hover. */ setupDelayedHoverAtMouse( target: HTMLElement, // TODO: Support using a simple string (content) as options? hoverOptions: (() => Omit) | Omit, - delayedHoverOptions?: IDelayedHoverOptions, + lifecycleOptions?: IHoverLifecycleOptions, ): IDisposable; /** @@ -204,7 +204,7 @@ export interface IHoverOptions { appearance?: IHoverAppearanceOptions; } -export interface IDelayedHoverOptions { +export interface IHoverLifecycleOptions { /** * The group ID of the hover. If the group ID is the same as the currently shown hover, the * hover will be shown immediately, skipping the delay. diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 1801e616df725..7d152e8e58795 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -20,7 +20,7 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; import { mainWindow } from '../../../../base/browser/window.js'; import { ContextViewHandler } from '../../../../platform/contextview/browser/contextViewService.js'; -import type { IDelayedHoverOptions, IDelayedHoverWidget, IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; +import type { IHoverLifecycleOptions, IDelayedHoverWidget, IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; import type { IHoverDelegate, IHoverDelegateTarget } from '../../../../base/browser/ui/hover/hoverDelegate.js'; import { ManagedHoverWidget } from './updatableHoverWidget.js'; import { timeout, TimeoutTimer } from '../../../../base/common/async.js'; @@ -125,19 +125,19 @@ export class HoverService extends Disposable implements IHoverService { setupDelayedHover( target: HTMLElement, options: (() => Omit) | Omit, - delayedHoverOptions?: IDelayedHoverOptions, + lifecycleOptions?: IHoverLifecycleOptions, ): IDisposable { const resolveHoverOptions = () => ({ ...typeof options === 'function' ? options() : options, target } satisfies IHoverOptions); - return this._setupDelayedHover(target, resolveHoverOptions, delayedHoverOptions); + return this._setupDelayedHover(target, resolveHoverOptions, lifecycleOptions); } setupDelayedHoverAtMouse( target: HTMLElement, options: (() => Omit) | Omit, - delayedHoverOptions?: IDelayedHoverOptions, + lifecycleOptions?: IHoverLifecycleOptions, ): IDisposable { const resolveHoverOptions = (e?: MouseEvent) => ({ ...typeof options === 'function' ? options() : options, @@ -146,19 +146,19 @@ export class HoverService extends Disposable implements IHoverService { x: e !== undefined ? e.x + 10 : undefined, } } satisfies IHoverOptions); - return this._setupDelayedHover(target, resolveHoverOptions, delayedHoverOptions); + return this._setupDelayedHover(target, resolveHoverOptions, lifecycleOptions); } private _setupDelayedHover( target: HTMLElement, resolveHoverOptions: ((e?: MouseEvent) => IHoverOptions), - delayedHoverOptions?: IDelayedHoverOptions, + lifecycleOptions?: IHoverLifecycleOptions, ) { const store = new DisposableStore(); store.add(addDisposableListener(target, EventType.MOUSE_OVER, e => { - this.showDelayedHover(resolveHoverOptions(e), delayedHoverOptions?.groupId); + this.showDelayedHover(resolveHoverOptions(e), lifecycleOptions?.groupId); })); - if (delayedHoverOptions?.setupKeyboardEvents) { + if (lifecycleOptions?.setupKeyboardEvents) { store.add(addDisposableListener(target, EventType.KEY_DOWN, e => { const evt = new StandardKeyboardEvent(e); if (evt.equals(KeyCode.Space) || evt.equals(KeyCode.Enter)) { From 497f224f7fa4dccaedc9f7efa4a506b2c36372da Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 30 Oct 2024 10:24:06 -0700 Subject: [PATCH 113/555] See in Search Panel wording Fixes #221370 --- .../search/browser/quickTextSearch/textSearchQuickAccess.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index a767d0c86ec04..24193d66651be 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -110,7 +110,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { @@ -311,7 +311,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { From 08e5895d0bf1194b983e8b2d805d18c1488eab4e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:25:08 -0700 Subject: [PATCH 114/555] Docs --- src/vs/base/browser/ui/hover/hover.ts | 45 +++++++++++++++++++++------ 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 54d62627c399b..9d965be23dfb7 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -17,6 +17,11 @@ export interface IHoverDelegate2 { * Shows a hover immediately, provided a hover with the same {@link options} object is not * already visible. * + * Use this method when you want to: + * + * - Control showing the hover yourself. + * - Show the hover immediately. + * * @param options A set of options defining the characteristics of the hover. * @param focus Whether to focus the hover (useful for keyboard accessibility). * @@ -38,6 +43,11 @@ export interface IHoverDelegate2 { * Shows a hover after a delay, or immediately if the {@link groupId} matches the currently * shown hover. * + * Use this method when you want to: + * + * - Control showing the hover yourself. + * - Show the hover after the standard delay. + * * @param options The options of the hover. * @param groupId The group ID of the hover. If the group ID is the same as the currently shown * hover, the hover will be shown immediately, skipping the delay. @@ -48,33 +58,44 @@ export interface IHoverDelegate2 { ): IDelayedHoverWidget | IHoverWidget | undefined; /** - * A simple wrapper around showDelayedHover that includes listening to the mouseover event of - * the {@link target} element. + * A simple wrapper around showDelayedHover that includes listening to events on the + * {@link target} element that shows the hover. + * + * Use this method when you want to: + * + * - Let the hover service handle showing the hover. + * - Show the hover after the standard delay. + * - Want the hover positioned beside the {@link target} element. * * @param target The target element to listener for mouseover events on. * @param hoverOptions The options of the hover. - * @param lifecycleOptions The options of the delayed hover. + * @param lifecycleOptions The options of the hover's lifecycle. */ setupDelayedHover( target: HTMLElement, - // TODO: Support using a simple string (content) as options? hoverOptions: (() => Omit) | Omit, lifecycleOptions?: IHoverLifecycleOptions, ): IDisposable; /** - * A simple wrapper around showDelayedHover that includes listening to the mouseover event of - * the {@link target} element. This differs from {@link setupDelayedHover} in that the hover - * will be shown at the mouse position instead of the target position, ignoring any + * A simple wrapper around showDelayedHover that includes listening to events on the + * {@link target} element that shows the hover. This differs from {@link setupDelayedHover} in + * that the hover will be shown at the mouse position instead of the + * {@link target target} element's position, ignoring any * {@link IHoverOptions.position position options} that are passed in. * + * Use this method when you want to: + * + * - Let the hover service handle showing the hover. + * - Show the hover after the standard delay. + * - Want the hover positioned beside the mouse. + * * @param target The target element to listener for mouseover events on. * @param hoverOptions The options of the hover. - * @param lifecycleOptions The options of the delayed hover. + * @param lifecycleOptions The options of the hover's lifecycle. */ setupDelayedHoverAtMouse( target: HTMLElement, - // TODO: Support using a simple string (content) as options? hoverOptions: (() => Omit) | Omit, lifecycleOptions?: IHoverLifecycleOptions, ): IDisposable; @@ -103,6 +124,9 @@ export interface IHoverDelegate2 { * @param targetElement The target element to show the hover for. * @param content The content of the hover or a factory that creates it at the time it's shown. * @param options Additional options for the managed hover. + * + * @deprecated Use {@link setupDelayedHover} or {@link setupDelayedHoverAtMouse} instead where + * possible. */ // TODO: The hoverDelegate parameter should be removed in favor of just a set of options. This // will avoid confusion around IHoverDelegate/IHoverDelegate2 as well as align more with @@ -114,6 +138,9 @@ export interface IHoverDelegate2 { * Shows the hover for the given element if one has been setup. * * @param targetElement The target element of the hover, as set up in {@link setupManagedHover}. + * + * @deprecated Use {@link setupDelayedHover} or {@link setupDelayedHoverAtMouse} instead where + * possible. */ showManagedHover(targetElement: HTMLElement): void; } From fffa5e07b3c2b6fd46a53b9f117796af5161910b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 30 Oct 2024 10:27:14 -0700 Subject: [PATCH 115/555] Add `getActionBarActions` helpers (#232633) Follow up on #232546 This also simplifies calling of `createAndFillActionBarActions` similar to #232546. By far the most common use case is returning the actions instead of filling in an existing list This change lets us remove a lot of extra boilerplate code around calling this function --- .../gotoError/browser/gotoErrorWidget.ts | 6 +- .../inlineCompletionsHintsWidget.ts | 10 +-- src/vs/platform/actions/browser/buttonbar.ts | 12 ++-- .../platform/actions/browser/floatingMenu.ts | 5 +- .../browser/menuEntryActionViewItem.ts | 64 ++++++++----------- src/vs/platform/actions/browser/toolbar.ts | 10 +-- src/vs/workbench/browser/actions.ts | 14 ++-- .../browser/parts/editor/editorGroupView.ts | 24 +++---- .../browser/parts/globalCompositeBar.ts | 6 +- .../browser/parts/paneCompositePart.ts | 6 +- .../browser/parts/titlebar/titlebarPart.ts | 7 +- .../accessibility/browser/accessibleView.ts | 5 +- .../browser/callHierarchyPeek.ts | 6 +- .../contrib/chat/browser/chatInputPart.ts | 5 +- .../contrib/debug/browser/breakpointsView.ts | 16 ++--- .../contrib/debug/browser/callStackView.ts | 16 ++--- .../contrib/debug/browser/debugToolBar.ts | 8 +-- .../interactive/browser/interactiveEditor.ts | 9 +-- .../contrib/find/notebookFindReplaceWidget.ts | 18 +----- .../contrib/outline/notebookOutline.ts | 10 +-- .../notebook/browser/diff/diffComponents.ts | 12 ++-- .../browser/view/cellParts/cellOutput.ts | 10 +-- .../browser/view/cellParts/cellToolbars.ts | 12 +--- .../view/cellParts/codeCellRunToolbar.ts | 10 +-- .../contrib/remote/browser/tunnelView.ts | 10 +-- .../replNotebook/browser/replEditor.ts | 9 +-- .../contrib/scm/browser/dirtydiffDecorator.ts | 5 +- src/vs/workbench/contrib/scm/browser/menus.ts | 6 +- .../contrib/scm/browser/scmViewPane.ts | 5 +- src/vs/workbench/contrib/scm/browser/util.ts | 7 +- .../testResultsView/testResultsTree.ts | 4 +- .../testing/browser/testingExplorerView.ts | 10 +-- .../testing/browser/testingOutputPeek.ts | 6 +- .../browser/typeHierarchyPeek.ts | 6 +- src/vs/workbench/electron-sandbox/window.ts | 6 +- 35 files changed, 127 insertions(+), 248 deletions(-) diff --git a/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts index b757cfd5de1ef..5a8bc4fb7ed7f 100644 --- a/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts @@ -5,7 +5,6 @@ import * as dom from '../../../../base/browser/dom.js'; import { ScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js'; -import { IAction } from '../../../../base/common/actions.js'; import { isNonEmptyArray } from '../../../../base/common/arrays.js'; import { Color } from '../../../../base/common/color.js'; import { Emitter, Event } from '../../../../base/common/event.js'; @@ -20,7 +19,7 @@ import { Range } from '../../../common/core/range.js'; import { ScrollType } from '../../../common/editorCommon.js'; import { peekViewTitleForeground, peekViewTitleInfoForeground, PeekViewWidget } from '../../peekView/browser/peekView.js'; import * as nls from '../../../../nls.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -307,9 +306,8 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._disposables.add(this._actionbarWidget!.actionRunner.onWillRun(e => this.editor.focus())); - const actions: IAction[] = []; const menu = this._menuService.getMenuActions(MarkerNavigationWidget.TitleMenu, this._contextKeyService); - createAndFillInActionBarActions(menu, actions); + const actions = getFlatActionBarActions(menu); this._actionbarWidget!.push(actions, { label: false, icon: true, index: 0 }); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts index 1c1bc0a34dbd8..7d5e0ac15a919 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts @@ -15,7 +15,7 @@ import { IObservable, autorun, autorunWithStore, derived, derivedObservableWithC import { OS } from '../../../../../base/common/platform.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { localize } from '../../../../../nls.js'; -import { MenuEntryActionViewItem, createAndFillInActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { MenuEntryActionViewItem, getActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuWorkbenchToolBarOptions, WorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { IMenuService, MenuId, MenuItemAction } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; @@ -340,12 +340,8 @@ export class CustomizedMenuWorkbenchToolBar extends WorkbenchToolBar { } private updateToolbar(): void { - const primary: IAction[] = []; - const secondary: IAction[] = []; - createAndFillInActionBarActions( - this.menu, - this.options2?.menuOptions, - { primary, secondary }, + const { primary, secondary } = getActionBarActions( + this.menu.getActions(this.options2?.menuOptions), this.options2?.toolbarOptions?.primaryGroup, this.options2?.toolbarOptions?.shouldInlineSubmenu, this.options2?.toolbarOptions?.useSeparatorsInPrimaryActions ); diff --git a/src/vs/platform/actions/browser/buttonbar.ts b/src/vs/platform/actions/browser/buttonbar.ts index 0f4fc56be5980..bc7437bb063ce 100644 --- a/src/vs/platform/actions/browser/buttonbar.ts +++ b/src/vs/platform/actions/browser/buttonbar.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from '../../../base/common/event.js'; import { DisposableStore } from '../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { localize } from '../../../nls.js'; -import { createAndFillInActionBarActions } from './menuEntryActionViewItem.js'; +import { getActionBarActions } from './menuEntryActionViewItem.js'; import { IToolBarRenderOptions } from './toolbar.js'; import { MenuId, IMenuService, MenuItemAction, IMenuActionOptions } from '../common/actions.js'; import { IContextKeyService } from '../../contextkey/common/contextkey.js'; @@ -187,16 +187,12 @@ export class MenuWorkbenchButtonBar extends WorkbenchButtonBar { this.clear(); - const primary: IAction[] = []; - const secondary: IAction[] = []; - createAndFillInActionBarActions( - menu, - options?.menuOptions, - { primary, secondary }, + const actions = getActionBarActions( + menu.getActions(options?.menuOptions), options?.toolbarOptions?.primaryGroup ); - super.update(primary, secondary); + super.update(actions.primary, actions.secondary); }; this._store.add(menu.onDidChange(update)); update(); diff --git a/src/vs/platform/actions/browser/floatingMenu.ts b/src/vs/platform/actions/browser/floatingMenu.ts index dc72cf005c3f9..fa6b7d1aa1a0f 100644 --- a/src/vs/platform/actions/browser/floatingMenu.ts +++ b/src/vs/platform/actions/browser/floatingMenu.ts @@ -8,7 +8,7 @@ import { Widget } from '../../../base/browser/ui/widget.js'; import { IAction } from '../../../base/common/actions.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js'; -import { createAndFillInActionBarActions } from './menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from './menuEntryActionViewItem.js'; import { IMenu, IMenuService, MenuId } from '../common/actions.js'; import { IContextKeyService } from '../../contextkey/common/contextkey.js'; import { IInstantiationService } from '../../instantiation/common/instantiation.js'; @@ -69,8 +69,7 @@ export abstract class AbstractFloatingClickMenu extends Disposable { if (!this.isVisible()) { return; } - const actions: IAction[] = []; - createAndFillInActionBarActions(this.menu, { renderShortTitle: true, shouldForwardArgs: true }, actions); + const actions = getFlatActionBarActions(this.menu.getActions({ renderShortTitle: true, shouldForwardArgs: true })); if (actions.length === 0) { return; } diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 0cd8f06791987..517bd2f66deff 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -31,10 +31,10 @@ import { defaultSelectBoxStyles } from '../../theme/browser/defaultStyles.js'; import { asCssVariable, selectBorder } from '../../theme/common/colorRegistry.js'; import { isDark } from '../../theme/common/theme.js'; import { IThemeService } from '../../theme/common/themeService.js'; -import { IMenu, IMenuActionOptions, IMenuService, MenuItemAction, SubmenuItemAction } from '../common/actions.js'; +import { IMenuService, MenuItemAction, SubmenuItemAction } from '../common/actions.js'; import './menuEntryActionViewItem.css'; -interface PrimaryAndSecondaryActions { +export interface PrimaryAndSecondaryActions { primary: IAction[]; secondary: IAction[]; } @@ -67,49 +67,36 @@ function getContextMenuActionsImpl( fillInActions(groups, target, useAlternativeActions, primaryGroup ? actionGroup => actionGroup === primaryGroup : actionGroup => actionGroup === 'navigation'); } -export function createAndFillInActionBarActions( - menu: IMenu, - options: IMenuActionOptions | undefined, - target: IAction[] | PrimaryAndSecondaryActions, + +export function getActionBarActions( + groups: [string, Array][], + primaryGroup?: string | ((actionGroup: string) => boolean), + shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, + useSeparatorsInPrimaryActions?: boolean +): PrimaryAndSecondaryActions { + const target: PrimaryAndSecondaryActions = { primary: [], secondary: [] }; + fillInActionBarActions(groups, target, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions); + return target; +} + +export function getFlatActionBarActions( + groups: [string, Array][], primaryGroup?: string | ((actionGroup: string) => boolean), shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, useSeparatorsInPrimaryActions?: boolean -): void; -export function createAndFillInActionBarActions( - menu: [string, Array][], +): IAction[] { + const target: IAction[] = []; + fillInActionBarActions(groups, target, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions); + return target; +} + +export function fillInActionBarActions( + groups: [string, Array][], target: IAction[] | PrimaryAndSecondaryActions, primaryGroup?: string | ((actionGroup: string) => boolean), shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, useSeparatorsInPrimaryActions?: boolean -): void; -export function createAndFillInActionBarActions( - menu: IMenu | [string, Array][], - optionsOrTarget: IMenuActionOptions | undefined | IAction[] | PrimaryAndSecondaryActions, - targetOrPrimaryGroup?: IAction[] | PrimaryAndSecondaryActions | string | ((actionGroup: string) => boolean), - primaryGroupOrShouldInlineSubmenu?: string | ((actionGroup: string) => boolean) | ((action: SubmenuAction, group: string, groupSize: number) => boolean), - shouldInlineSubmenuOrUseSeparatorsInPrimaryActions?: ((action: SubmenuAction, group: string, groupSize: number) => boolean) | boolean, - useSeparatorsInPrimaryActionsOrUndefined?: boolean ): void { - let target: IAction[] | PrimaryAndSecondaryActions; - let primaryGroup: string | ((actionGroup: string) => boolean) | undefined; - let shouldInlineSubmenu: ((action: SubmenuAction, group: string, groupSize: number) => boolean) | undefined; - let useSeparatorsInPrimaryActions: boolean | undefined; - let groups: [string, Array][]; - if (Array.isArray(menu)) { - groups = menu; - target = optionsOrTarget as IAction[] | PrimaryAndSecondaryActions; - primaryGroup = targetOrPrimaryGroup as string | ((actionGroup: string) => boolean) | undefined; - shouldInlineSubmenu = primaryGroupOrShouldInlineSubmenu as (action: SubmenuAction, group: string, groupSize: number) => boolean; - useSeparatorsInPrimaryActions = shouldInlineSubmenuOrUseSeparatorsInPrimaryActions as boolean | undefined; - } else { - const options: IMenuActionOptions | undefined = optionsOrTarget as IMenuActionOptions | undefined; - groups = menu.getActions(options); - target = targetOrPrimaryGroup as IAction[] | PrimaryAndSecondaryActions; - primaryGroup = primaryGroupOrShouldInlineSubmenu as string | ((actionGroup: string) => boolean) | undefined; - shouldInlineSubmenu = shouldInlineSubmenuOrUseSeparatorsInPrimaryActions as (action: SubmenuAction, group: string, groupSize: number) => boolean; - useSeparatorsInPrimaryActions = useSeparatorsInPrimaryActionsOrUndefined; - } - const isPrimaryAction = typeof primaryGroup === 'string' ? (actionGroup: string) => actionGroup === primaryGroup : primaryGroup; // Action bars handle alternative actions on their own so the alternative actions should be ignored @@ -117,7 +104,8 @@ export function createAndFillInActionBarActions( } function fillInActions( - groups: ReadonlyArray<[string, ReadonlyArray]>, target: IAction[] | PrimaryAndSecondaryActions, + groups: ReadonlyArray<[string, ReadonlyArray]>, + target: IAction[] | PrimaryAndSecondaryActions, useAlternativeActions: boolean, isPrimaryAction: (actionGroup: string) => boolean = actionGroup => actionGroup === 'navigation', shouldInlineSubmenu: (action: SubmenuAction, group: string, groupSize: number) => boolean = () => false, diff --git a/src/vs/platform/actions/browser/toolbar.ts b/src/vs/platform/actions/browser/toolbar.ts index 741f946c6f8d9..5e46b390271de 100644 --- a/src/vs/platform/actions/browser/toolbar.ts +++ b/src/vs/platform/actions/browser/toolbar.ts @@ -14,7 +14,7 @@ import { Emitter, Event } from '../../../base/common/event.js'; import { Iterable } from '../../../base/common/iterator.js'; import { DisposableStore } from '../../../base/common/lifecycle.js'; import { localize } from '../../../nls.js'; -import { createActionViewItem, createAndFillInActionBarActions } from './menuEntryActionViewItem.js'; +import { createActionViewItem, getActionBarActions } from './menuEntryActionViewItem.js'; import { IMenuActionOptions, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from '../common/actions.js'; import { createConfigureKeybindingAction } from '../common/menuService.js'; import { ICommandService } from '../../commands/common/commands.js'; @@ -364,12 +364,8 @@ export class MenuWorkbenchToolBar extends WorkbenchToolBar { // update logic const menu = this._store.add(menuService.createMenu(menuId, contextKeyService, { emitEventsForSubmenuChanges: true, eventDebounceDelay: options?.eventDebounceDelay })); const updateToolbar = () => { - const primary: IAction[] = []; - const secondary: IAction[] = []; - createAndFillInActionBarActions( - menu, - options?.menuOptions, - { primary, secondary }, + const { primary, secondary } = getActionBarActions( + menu.getActions(options?.menuOptions), options?.toolbarOptions?.primaryGroup, options?.toolbarOptions?.shouldInlineSubmenu, options?.toolbarOptions?.useSeparatorsInPrimaryActions ); container.classList.toggle('has-no-actions', primary.length === 0 && secondary.length === 0); diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index 83f4cf4a45586..437ca7ff48bdb 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -8,7 +8,7 @@ import { Disposable, DisposableStore, IDisposable } from '../../base/common/life import { Emitter, Event } from '../../base/common/event.js'; import { MenuId, IMenuService, IMenu, SubmenuItemAction, IMenuActionOptions } from '../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../platform/contextkey/common/contextkey.js'; -import { createAndFillInActionBarActions } from '../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions } from '../../platform/actions/browser/menuEntryActionViewItem.js'; class MenuActions extends Disposable { @@ -41,9 +41,9 @@ class MenuActions extends Disposable { private updateActions(): void { this.disposables.clear(); - this._primaryActions = []; - this._secondaryActions = []; - createAndFillInActionBarActions(this.menu, this.options, { primary: this._primaryActions, secondary: this._secondaryActions }); + const newActions = getActionBarActions(this.menu.getActions(this.options)); + this._primaryActions = newActions.primary; + this._secondaryActions = newActions.secondary; this.disposables.add(this.updateSubmenus([...this._primaryActions, ...this._secondaryActions], {})); this._onDidChange.fire(); } @@ -93,13 +93,11 @@ export class CompositeMenuActions extends Disposable { } getContextMenuActions(): IAction[] { - const actions: IAction[] = []; - if (this.contextMenuId) { const menu = this.menuService.getMenuActions(this.contextMenuId, this.contextKeyService, this.options); - createAndFillInActionBarActions(menu, { primary: [], secondary: actions }); + return getActionBarActions(menu).secondary; } - return actions; + return []; } } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 8e9756045fb5f..2b7d3ed86ebf2 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -5,7 +5,7 @@ import './media/editorgroupview.css'; import { EditorGroupModel, IEditorOpenOptions, IGroupModelChangeEvent, ISerializedEditorGroupModel, isGroupEditorCloseEvent, isGroupEditorOpenEvent, isSerializedEditorGroupModel } from '../../../common/editor/editorGroupModel.js'; -import { GroupIdentifier, CloseDirection, IEditorCloseEvent, IEditorPane, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorResourceAccessor, EditorInputCapabilities, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, GroupModelChangeKind, IActiveEditorChangeEvent, IFindEditorOptions, IToolbarActions, TEXT_DIFF_EDITOR_ID } from '../../../common/editor.js'; +import { GroupIdentifier, CloseDirection, IEditorCloseEvent, IEditorPane, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorResourceAccessor, EditorInputCapabilities, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, GroupModelChangeKind, IActiveEditorChangeEvent, IFindEditorOptions, TEXT_DIFF_EDITOR_ID } from '../../../common/editor.js'; import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ResourceContextKey, applyAvailableEditorIds, ActiveEditorAvailableEditorIdsContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorContext, ActiveEditorReadonlyContext, ActiveEditorCanRevertContext, ActiveEditorCanToggleReadonlyContext, ActiveCompareEditorCanSwapContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext, SelectedEditorsInGroupFileOrUntitledResourceContextKey } from '../../../common/contextkeys.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { SideBySideEditorInput } from '../../../common/editor/sideBySideEditorInput.js'; @@ -31,10 +31,10 @@ import { EventType as TouchEventType, GestureEvent } from '../../../../base/brow import { IEditorGroupsView, IEditorGroupView, fillActiveEditorViewState, EditorServiceImpl, IEditorGroupTitleHeight, IInternalEditorOpenOptions, IInternalMoveCopyOptions, IInternalEditorCloseOptions, IInternalEditorTitleControlOptions, IEditorPartsView, IEditorGroupViewOptions } from './editor.js'; import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { IAction, SubmenuAction } from '../../../../base/common/actions.js'; +import { SubmenuAction } from '../../../../base/common/actions.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions, PrimaryAndSecondaryActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { hash } from '../../../../base/common/hash.js'; @@ -419,16 +419,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Toolbar actions const containerToolbarMenu = this._register(this.menuService.createMenu(MenuId.EmptyEditorGroup, this.scopedContextKeyService)); const updateContainerToolbar = () => { - const actions: IToolbarActions = { primary: [], secondary: [] }; // Clear old actions this.containerToolBarMenuDisposable.value = toDisposable(() => containerToolbar.clear()); // Create new actions - createAndFillInActionBarActions( - containerToolbarMenu, - { arg: { groupId: this.id }, shouldForwardArgs: true }, - actions, + const actions = getActionBarActions( + containerToolbarMenu.getActions({ arg: { groupId: this.id }, shouldForwardArgs: true }), 'navigation' ); @@ -2092,8 +2089,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region Editor Actions createEditorActions(disposables: DisposableStore): IActiveEditorActions { - const primary: IAction[] = []; - const secondary: IAction[] = []; + let actions: PrimaryAndSecondaryActions = { primary: [], secondary: [] }; let onDidChange; @@ -2106,10 +2102,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const shouldInlineGroup = (action: SubmenuAction, group: string) => group === 'navigation' && action.actions.length <= 1; - createAndFillInActionBarActions( - editorTitleMenu, - { arg: this.resourceContext.get(), shouldForwardArgs: true }, - { primary, secondary }, + actions = getActionBarActions( + editorTitleMenu.getActions({ arg: this.resourceContext.get(), shouldForwardArgs: true }), 'navigation', shouldInlineGroup ); @@ -2121,7 +2115,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { disposables.add(this.onDidActiveEditorChange(() => _onDidChange.fire())); } - return { actions: { primary, secondary }, onDidChange }; + return { actions, onDidChange }; } //#endregion diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 8c96ab3e46127..d93cb9b1ef1ab 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -24,7 +24,7 @@ import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { EventType as TouchEventType, GestureEvent } from '../../../base/browser/touch.js'; import { AnchorAlignment, AnchorAxisAlignment } from '../../../base/browser/ui/contextview/contextview.js'; import { Lazy } from '../../../base/common/lazy.js'; -import { createAndFillInActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js'; @@ -277,9 +277,7 @@ abstract class AbstractGlobalActivityActionViewItem extends CompositeBarActionVi } protected async resolveMainMenuActions(menu: IMenu, _disposable: DisposableStore): Promise { - const actions: IAction[] = []; - createAndFillInActionBarActions(menu, { renderShortTitle: true }, { primary: [], secondary: actions }); - return actions; + return getActionBarActions(menu.getActions({ renderShortTitle: true })).secondary; } } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index ea3364b2d132d..413ce899f786f 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -37,7 +37,7 @@ import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { IAction, SubmenuAction } from '../../../base/common/actions.js'; import { Composite } from '../composite.js'; import { ViewsSubMenu } from './views/viewPaneContainer.js'; -import { createAndFillInActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IHoverService } from '../../../platform/hover/browser/hover.js'; import { HiddenItemStrategy, WorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js'; @@ -661,11 +661,10 @@ export abstract class AbstractPaneCompositePart extends CompositePart true); + const viewsActions = getActionBarActions(menu, () => true).primary; disposables.dispose(); return viewsActions.length > 1 && viewsActions.some(a => a.enabled) ? new SubmenuAction('views', localize('views', "Views"), viewsActions) : undefined; } @@ -675,5 +674,4 @@ export abstract class AbstractPaneCompositePart extends CompositePart !this.editorActionsEnabled // Layout Actions in overflow menu when editor actions enabled in title bar ); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 132b052c85812..c9acf7b6a28c9 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -26,7 +26,7 @@ import { CodeActionController } from '../../../../editor/contrib/codeAction/brow import { localize } from '../../../../nls.js'; import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider, ExtensionContentProvider, IAccessibleViewService, IAccessibleViewSymbol } from '../../../../platform/accessibility/browser/accessibleView.js'; import { ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX, IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; @@ -655,9 +655,8 @@ export class AccessibleView extends Disposable implements ITextModelContentProvi private _updateToolbar(providedActions?: IAction[], type?: AccessibleViewType): void { this._toolbar.setAriaLabel(type === AccessibleViewType.Help ? localize('accessibleHelpToolbar', 'Accessibility Help') : localize('accessibleViewToolbar', "Accessible View")); - const menuActions: IAction[] = []; const toolbarMenu = this._register(this._menuService.createMenu(MenuId.AccessibleView, this._contextKeyService)); - createAndFillInActionBarActions(toolbarMenu, {}, menuActions); + const menuActions = getFlatActionBarActions(toolbarMenu.getActions({})); if (providedActions) { for (const providedAction of providedActions) { providedAction.class = providedAction.class || ThemeIcon.asClassName(Codicon.primitiveSquare); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 3b4ab40d0efb3..75da6466a36d1 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -26,14 +26,13 @@ import { toDisposable, DisposableStore } from '../../../../base/common/lifecycle import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from '../../../../editor/common/model.js'; import { themeColorFromId, IThemeService, IColorTheme } from '../../../../platform/theme/common/themeService.js'; import { IPosition } from '../../../../editor/common/core/position.js'; -import { IAction } from '../../../../base/common/actions.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { Color } from '../../../../base/common/color.js'; import { TreeMouseEventTarget, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; import { URI } from '../../../../base/common/uri.js'; import { MenuId, IMenuService } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; const enum State { Loading = 'loading', @@ -129,8 +128,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { const menu = this._menuService.createMenu(CallHierarchyTreePeekWidget.TitleMenu, this._contextKeyService); const updateToolbar = () => { - const actions: IAction[] = []; - createAndFillInActionBarActions(menu, undefined, actions); + const actions = getFlatActionBarActions(menu.getActions()); this._actionbarWidget!.clear(); this._actionbarWidget!.push(actions, { label: false, icon: true }); }; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 7f04efd245fc3..d6d83630b9eeb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -44,7 +44,7 @@ import { localize } from '../../../../nls.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { MenuWorkbenchButtonBar } from '../../../../platform/actions/browser/buttonbar.js'; import { DropdownWithPrimaryActionViewItem, IDropdownWithPrimaryActionViewItemOptions } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; -import { createAndFillInActionBarActions, IMenuEntryActionViewItemOptions, MenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions, IMenuEntryActionViewItemOptions, MenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; @@ -1216,8 +1216,7 @@ class ChatSubmitDropdownActionItem extends DropdownWithPrimaryActionViewItem { accessibilityService); const menu = menuService.createMenu(MenuId.ChatExecuteSecondary, contextKeyService); const setActions = () => { - const secondary: IAction[] = []; - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, secondary); + const secondary = getFlatActionBarActions(menu.getActions({ shouldForwardArgs: true })); const secondaryAgent = chatAgentService.getSecondaryAgent(); if (secondaryAgent) { secondary.forEach(a => { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 643d1ece5a1d8..bc5b100d27b46 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -14,7 +14,7 @@ import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js'; import { IListContextMenuEvent, IListRenderer, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; import { Orientation } from '../../../../base/browser/ui/splitview/splitview.js'; -import { Action, IAction } from '../../../../base/common/actions.js'; +import { Action } from '../../../../base/common/actions.js'; import { equals } from '../../../../base/common/arrays.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; import { Codicon } from '../../../../base/common/codicons.js'; @@ -28,7 +28,7 @@ import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { localize, localize2 } from '../../../../nls.js'; -import { createAndFillInActionBarActions, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -566,12 +566,11 @@ class BreakpointsRenderer implements IListRenderer 1); - createAndFillInActionBarActions(this.menu, { arg: breakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); + const { primary } = getActionBarActions(this.menu.getActions({ arg: breakpoint, shouldForwardArgs: true }), 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); breakpointIdToActionBarDomeNode.set(breakpoint.getId(), data.actionBar.domNode); @@ -650,11 +649,10 @@ class ExceptionBreakpointsRenderer implements IListRenderer 1); - createAndFillInActionBarActions(this.menu, { arg: exceptionBreakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); + const { primary } = getActionBarActions(this.menu.getActions({ arg: exceptionBreakpoint, shouldForwardArgs: true }), 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); breakpointIdToActionBarDomeNode.set(exceptionBreakpoint.getId(), data.actionBar.domNode); @@ -743,10 +741,9 @@ class FunctionBreakpointsRenderer implements IListRenderer 1); this.breakpointItemType.set('dataBreakpoint'); this.breakpointIsDataBytes.set(dataBreakpoint.src.type === DataBreakpointSetType.Address); - createAndFillInActionBarActions(this.menu, { arg: dataBreakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); + const { primary } = getActionBarActions(this.menu.getActions({ arg: dataBreakpoint, shouldForwardArgs: true }), 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); breakpointIdToActionBarDomeNode.set(dataBreakpoint.getId(), data.actionBar.domNode); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 187eb123b7b2f..782aeae9bbb9b 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -13,7 +13,7 @@ import { ITreeCompressionDelegate } from '../../../../base/browser/ui/tree/async import { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js'; import { IAsyncDataSource, ITreeContextMenuEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; -import { Action, IAction } from '../../../../base/common/actions.js'; +import { Action } from '../../../../base/common/actions.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Event } from '../../../../base/common/event.js'; @@ -23,7 +23,7 @@ import { posix } from '../../../../base/common/path.js'; import { commonSuffixLength } from '../../../../base/common/strings.js'; import { localize } from '../../../../nls.js'; import { ICommandActionTitle, Icon } from '../../../../platform/action/common/action.js'; -import { createAndFillInActionBarActions, getContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions, getContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -589,11 +589,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer { data.actionBar.clear(); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { arg: getContextForContributedActions(session), shouldForwardArgs: true }, result, 'inline'); + const { primary } = getActionBarActions(menu.getActions({ arg: getContextForContributedActions(session), shouldForwardArgs: true }), 'inline'); data.actionBar.push(primary, { icon: true, label: false }); // We need to set our internal context on the action bar, since our commands depend on that one // While the external context our extensions rely on @@ -678,11 +674,7 @@ class ThreadsRenderer implements ICompressibleTreeRenderer { data.actionBar.clear(); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { arg: getContextForContributedActions(thread), shouldForwardArgs: true }, result, 'inline'); + const { primary } = getActionBarActions(menu.getActions({ arg: getContextForContributedActions(thread), shouldForwardArgs: true }), 'inline'); data.actionBar.push(primary, { icon: true, label: false }); // We need to set our internal context on the action bar, since our commands depend on that one // While the external context our extensions rely on diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index e4bc5719080e0..2cbbdc5602aaf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -22,7 +22,7 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js import { localize } from '../../../../nls.js'; import { ICommandAction, ICommandActionTitle } from '../../../../platform/action/common/action.js'; import { DropdownWithPrimaryActionViewItem, IDropdownWithPrimaryActionViewItemOptions } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; -import { createActionViewItem, createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -132,8 +132,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { return this.hide(); } - const actions: IAction[] = []; - createAndFillInActionBarActions(this.debugToolBarMenu, { shouldForwardArgs: true }, actions); + const actions = getFlatActionBarActions(this.debugToolBarMenu.getActions({ shouldForwardArgs: true })); if (!arrays.equals(actions, this.activeActions, (first, second) => first.id === second.id && first.enabled === second.enabled)) { this.actionBar.clear(); this.actionBar.push(actions, { icon: true, label: false }); @@ -364,8 +363,7 @@ export function createDisconnectMenuItemAction(action: MenuItemAction, disposabl const instantiationService = accessor.get(IInstantiationService); const menu = menuService.getMenuActions(MenuId.DebugToolBarStop, contextKeyService, { shouldForwardArgs: true }); - const secondary: IAction[] = []; - createAndFillInActionBarActions(menu, secondary); + const secondary = getFlatActionBarActions(menu); if (!secondary.length) { return undefined; diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 0c6bb6a516f68..c65073af1df47 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -36,8 +36,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { NotebookOptions } from '../../notebook/browser/notebookOptions.js'; import { ToolBar } from '../../../../base/browser/ui/toolbar/toolbar.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { createActionViewItem, createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { IAction } from '../../../../base/common/actions.js'; +import { createActionViewItem, getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; import { ParameterHintsController } from '../../../../editor/contrib/parameterHints/browser/parameterHints.js'; import { MenuPreventer } from '../../codeEditor/browser/menuPreventer.js'; @@ -218,11 +217,7 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro renderDropdownAsChildElement: true })); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result); + const { primary, secondary } = getActionBarActions(menu.getActions({ shouldForwardArgs: true })); this._runbuttonToolbar.setActions([...primary, ...secondary]); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index ccf772fb9202e..87558ef43272b 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -28,7 +28,7 @@ import { Range } from '../../../../../../editor/common/core/range.js'; import { FindReplaceState, FindReplaceStateChangedEvent } from '../../../../../../editor/contrib/find/browser/findState.js'; import { findNextMatchIcon, findPreviousMatchIcon, findReplaceAllIcon, findReplaceIcon, findSelectionIcon, SimpleButton } from '../../../../../../editor/contrib/find/browser/findWidget.js'; import { parseReplaceString, ReplacePattern } from '../../../../../../editor/contrib/find/browser/replacePattern.js'; -import { createAndFillInActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenu } from '../../../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; @@ -279,13 +279,7 @@ export class NotebookFindInput extends FindInput { } getCellToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IAction[] } { - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); - - return result; + return getActionBarActions(menu.getActions({ shouldForwardArgs: true }), g => /^inline/.test(g)); } } @@ -651,13 +645,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { } getCellToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IAction[] } { - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); - - return result; + return getActionBarActions(menu.getActions({ shouldForwardArgs: true }), g => /^inline/.test(g)); } protected abstract onInputChanged(): boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 90839d12a98ab..b0409762b8d90 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -42,7 +42,7 @@ import { mainWindow } from '../../../../../../base/browser/window.js'; import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; import { Action2, IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2 } from '../../../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { MenuEntryActionViewItem, createAndFillInActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { MenuEntryActionViewItem, getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IAction } from '../../../../../../base/common/actions.js'; import { NotebookSectionArgs } from '../../controller/sectionActions.js'; import { MarkupCellViewModel } from '../../viewModel/markupCellViewModel.js'; @@ -250,14 +250,8 @@ class NotebookOutlineRenderer implements ITreeRenderer /^inline/.test(g)); - - return result; + return getActionBarActions(menu.getActions({ shouldForwardArgs: true, arg: args })); //, g => /^inline/.test(g)); } class NotebookOutlineAccessibility implements IListAccessibilityProvider { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index c7cb5b3ba2038..3ef8a33b0883d 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -18,8 +18,7 @@ import { IContextMenuService } from '../../../../../platform/contextview/browser import { IMenu, IMenuService, MenuId, MenuItemAction } from '../../../../../platform/actions/common/actions.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { INotificationService } from '../../../../../platform/notification/common/notification.js'; -import { IAction } from '../../../../../base/common/actions.js'; -import { createAndFillInActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { CodiconActionViewItem } from '../view/cellParts/cellActionView.js'; import { collapsedIcon, expandedIcon } from '../notebookIcons.js'; @@ -201,8 +200,7 @@ class PropertyHeader extends Disposable { private updateMenu() { const metadataChanged = this.accessor.checkIfModified(); if (metadataChanged) { - const actions: IAction[] = []; - createAndFillInActionBarActions(this._menu, { shouldForwardArgs: true }, actions); + const actions = getFlatActionBarActions(this._menu.getActions({ shouldForwardArgs: true })); this._toolbar.setActions(actions); } else { this._toolbar.setActions([]); @@ -385,9 +383,8 @@ export class NotebookDocumentMetadataElement extends Disposable { inputChanged.set(hasChanges); if (hasChanges) { - const actions: IAction[] = []; const menu = this.menuService.getMenuActions(MenuId.NotebookDiffDocumentMetadata, scopedContextKeyService, { shouldForwardArgs: true }); - createAndFillInActionBarActions(menu, actions); + const actions = getFlatActionBarActions(menu); this._toolbar.setActions(actions); } else { this._toolbar.setActions([]); @@ -1962,9 +1959,8 @@ export class ModifiedElement extends AbstractElementRenderer { inputChanged.set(hasChanges); if (hasChanges) { - const actions: IAction[] = []; const menu = this.menuService.getMenuActions(MenuId.NotebookDiffCellInputTitle, scopedContextKeyService, { shouldForwardArgs: true }); - createAndFillInActionBarActions(menu, actions); + const actions = getFlatActionBarActions(menu); this._toolbar.setActions(actions); } else { this._toolbar.setActions([]); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts index e561cf6e62609..8429713f9c7c7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts @@ -6,12 +6,12 @@ import * as DOM from '../../../../../../base/browser/dom.js'; import { FastDomNode } from '../../../../../../base/browser/fastDomNode.js'; import { renderMarkdown } from '../../../../../../base/browser/markdownRenderer.js'; -import { Action, IAction } from '../../../../../../base/common/actions.js'; +import { Action } from '../../../../../../base/common/actions.js'; import { IMarkdownString } from '../../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore } from '../../../../../../base/common/lifecycle.js'; import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; import * as nls from '../../../../../../nls.js'; -import { createAndFillInActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { WorkbenchToolBar } from '../../../../../../platform/actions/browser/toolbar.js'; import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; @@ -332,11 +332,7 @@ class CellOutputElement extends Disposable { const menu = this.toolbarDisposables.add(this.menuService.createMenu(MenuId.NotebookOutputToolbar, menuContextKeyService)); const updateMenuToolbar = () => { - const primary: IAction[] = []; - let secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu!, { shouldForwardArgs: true }, result, () => false); + let { secondary } = getActionBarActions(menu!.getActions({ shouldForwardArgs: true }), () => false); if (!isCopyEnabled) { secondary = secondary.filter((action) => action.id !== COPY_OUTPUT_COMMAND_ID); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index 4f33acf6f21ab..0a4355b1cf756 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -10,7 +10,7 @@ import { disposableTimeout } from '../../../../../../base/common/async.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js'; -import { createActionViewItem, createAndFillInActionBarActions, MenuEntryActionViewItem } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, getActionBarActions, MenuEntryActionViewItem, PrimaryAndSecondaryActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenu, IMenuService, MenuId, MenuItemAction } from '../../../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; @@ -270,14 +270,8 @@ export class CellTitleToolbarPart extends CellOverlayPart { } } -function getCellToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IAction[] } { - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); - - return result; +function getCellToolbarActions(menu: IMenu): PrimaryAndSecondaryActions { + return getActionBarActions(menu.getActions({ shouldForwardArgs: true }), g => /^inline/.test(g)); } function createDeleteToolbar(accessor: ServicesAccessor, container: HTMLElement, hoverDelegate: IHoverDelegate, elementClass?: string): ToolBar { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts index 1bdbbade43891..e5cb2273734e5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts @@ -10,7 +10,7 @@ import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; import { EditorContextKeys } from '../../../../../../editor/common/editorContextKeys.js'; import { localize } from '../../../../../../nls.js'; import { DropdownWithPrimaryActionViewItem } from '../../../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; -import { createAndFillInActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenu, IMenuService, MenuId, MenuItemAction } from '../../../../../../platform/actions/common/actions.js'; import { IContextKeyService, IScopedContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { InputFocusedContext } from '../../../../../../platform/contextkey/common/contextkeys.js'; @@ -72,13 +72,7 @@ export class RunToolbar extends CellContentPart { } getCellToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IAction[] } { - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); - - return result; + return getActionBarActions(menu.getActions({ shouldForwardArgs: true }), g => /^inline/.test(g)); } private createRunCellToolbar(container: HTMLElement, cellContainer: HTMLElement, contextKeyService: IContextKeyService) { diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 3ea82ad3a41bd..a274fff1252a0 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -24,7 +24,7 @@ import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; import { ActionRunner, IAction } from '../../../../base/common/actions.js'; import { IMenuService, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { ILocalizedString } from '../../../../platform/action/common/action.js'; -import { createAndFillInActionBarActions, createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IRemoteExplorerService, TunnelType, ITunnelItem, TUNNEL_VIEW_ID, TunnelEditId } from '../../../services/remote/common/remoteExplorerService.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; @@ -469,8 +469,7 @@ class ActionBarRenderer extends Disposable implements ITableRenderer action.id.toLowerCase().indexOf('label') >= 0); if (labelActions.length > 1) { @@ -763,6 +762,8 @@ export class TunnelPanel extends ViewPane { private portChangableContextKey: IContextKey; private protocolChangableContextKey: IContextKey; private isEditing: boolean = false; + // TODO: Should this be removed? + //@ts-expect-error private titleActions: IAction[] = []; private lastFocus: number[] = []; @@ -803,8 +804,7 @@ export class TunnelPanel extends ViewPane { const overlayContextKeyService = this.contextKeyService.createOverlay([['view', TunnelPanel.ID]]); const titleMenu = this._register(this.menuService.createMenu(MenuId.TunnelTitle, overlayContextKeyService)); const updateActions = () => { - this.titleActions = []; - createAndFillInActionBarActions(titleMenu, undefined, this.titleActions); + this.titleActions = getFlatActionBarActions(titleMenu.getActions()); this.updateActions(); }; diff --git a/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts b/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts index 10420ffdbcf53..ba554f86232be 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts @@ -33,8 +33,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { NotebookOptions } from '../../notebook/browser/notebookOptions.js'; import { ToolBar } from '../../../../base/browser/ui/toolbar/toolbar.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { createActionViewItem, createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { IAction } from '../../../../base/common/actions.js'; +import { createActionViewItem, getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; import { SelectionClipboardContributionID } from '../../codeEditor/browser/selectionClipboard.js'; import { ContextMenuController } from '../../../../editor/contrib/contextmenu/browser/contextmenu.js'; @@ -205,11 +204,7 @@ export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { renderDropdownAsChildElement: true })); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result); + const { primary, secondary } = getActionBarActions(menu.getActions({ shouldForwardArgs: true })); this._runbuttonToolbar.setActions([...primary, ...secondary]); } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 1f0622d104d7b..a2e2f108aed0d 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -36,7 +36,7 @@ import { IActionBarOptions } from '../../../../base/browser/ui/actionbar/actionb import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { basename, isEqual } from '../../../../base/common/resources.js'; import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from '../../../../platform/actions/common/actions.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { ScrollType, IEditorContribution, IDiffEditorModel, IEditorModel, IEditorDecorationsCollection } from '../../../../editor/common/editorCommon.js'; import { OverviewRulerLane, ITextModel, IModelDecorationOptions, MinimapPosition, shouldSynchronizeModel } from '../../../../editor/common/model.js'; import { equals, sortedDiff } from '../../../../base/common/arrays.js'; @@ -354,12 +354,11 @@ class DirtyDiffWidget extends PeekViewWidget { this._disposables.add(previous); this._disposables.add(next); - const actions: IAction[] = []; if (this.menu) { this.menu.dispose(); } this.menu = this.menuService.createMenu(MenuId.SCMChangeContext, this.contextKeyService); - createAndFillInActionBarActions(this.menu, { shouldForwardArgs: true }, actions); + const actions = getFlatActionBarActions(this.menu.getActions({ shouldForwardArgs: true })); this._actionbarWidget.clear(); this._actionbarWidget.push(actions.reverse(), { label: false, icon: true }); this._actionbarWidget.push([next, previous], { label: false, icon: true }); diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 3056b36f57d44..486f978b768c2 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -9,7 +9,7 @@ import { Emitter } from '../../../../base/common/event.js'; import { DisposableStore, IDisposable, dispose } from '../../../../base/common/lifecycle.js'; import './media/scm.css'; import { localize } from '../../../../nls.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenu, IMenuService, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -58,9 +58,7 @@ export class SCMTitleMenu implements IDisposable { } private updateTitleActions(): void { - const primary: IAction[] = []; - const secondary: IAction[] = []; - createAndFillInActionBarActions(this.menu, { shouldForwardArgs: true }, { primary, secondary }); + const { primary, secondary } = getActionBarActions(this.menu.getActions({ shouldForwardArgs: true })); if (equals(primary, this._actions, actionEquals) && equals(secondary, this._secondaryActions, actionEquals)) { return; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index d531945df81f3..56164a82eaa35 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -71,7 +71,7 @@ import { ColorScheme } from '../../../../platform/theme/common/theme.js'; import { LabelFuzzyScore } from '../../../../base/browser/ui/tree/abstractTree.js'; import { Selection } from '../../../../editor/common/core/selection.js'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js'; -import { createActionViewItem, createAndFillInActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, getFlatActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { Button, ButtonWithDescription, ButtonWithDropdown } from '../../../../base/browser/ui/button/button.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; @@ -1414,8 +1414,7 @@ class SCMInputWidgetToolbar extends WorkbenchToolBar { }; const updateToolbar = () => { - const actions: IAction[] = []; - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, actions); + const actions = getFlatActionBarActions(menu.getActions({ shouldForwardArgs: true })); for (const action of actions) { action.enabled = isEnabled(); diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 3406a15685b08..73a452a4efd8d 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -9,7 +9,7 @@ import { IMenu, MenuItemAction } from '../../../../platform/actions/common/actio import { IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; import { Action, IAction } from '../../../../base/common/actions.js'; -import { createActionViewItem, createAndFillInActionBarActions, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, getActionBarActions, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { equals } from '../../../../base/common/arrays.js'; import { ActionViewItem, IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; @@ -68,10 +68,7 @@ export function connectPrimaryMenu(menu: IMenu, callback: (primary: IAction[], s let cachedSecondary: IAction[] = []; const updateActions = () => { - const primary: IAction[] = []; - const secondary: IAction[] = []; - - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary, secondary }, primaryGroup); + const { primary, secondary } = getActionBarActions(menu.getActions({ shouldForwardArgs: true }), primaryGroup); if (equals(cachedPrimary, primary, compareActions) && equals(cachedSecondary, secondary, compareActions)) { return; diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts index 5aa5e5b99efb5..ac6d958a16dde 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts @@ -24,7 +24,7 @@ import { ThemeIcon } from '../../../../../base/common/themables.js'; import { isDefined } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; -import { MenuEntryActionViewItem, createAndFillInActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { MenuEntryActionViewItem, fillInActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId, MenuItemAction } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -906,7 +906,7 @@ class TreeActionsProvider { const contextOverlay = this.contextKeyService.createOverlay(contextKeys); const result = { primary, secondary }; const menu = this.menuService.getMenuActions(id, contextOverlay, { arg: element.context }); - createAndFillInActionBarActions(menu, result, 'inline'); + fillInActionBarActions(menu, result, 'inline'); return result; } } diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 3cafc9409e865..b088c7b4b88a8 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -30,7 +30,7 @@ import { URI } from '../../../../base/common/uri.js'; import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize } from '../../../../nls.js'; import { DropdownWithPrimaryActionViewItem } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; -import { MenuEntryActionViewItem, createActionViewItem, createAndFillInActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { MenuEntryActionViewItem, createActionViewItem, getActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -1611,13 +1611,9 @@ const getActionableElementActions = ( shouldForwardArgs: true, }); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - createAndFillInActionBarActions(menu, result, 'inline'); - - return { actions: result, contextOverlay }; + const actions = getActionBarActions(menu, 'inline'); + return { actions, contextOverlay }; }; registerThemingParticipant((theme, collector) => { diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 2e08dc3a766b7..9822a96ae3015 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -31,7 +31,7 @@ import { ITextModelService } from '../../../../editor/common/services/resolverSe import { IPeekViewService, PeekViewWidget, peekViewTitleForeground, peekViewTitleInfoForeground } from '../../../../editor/contrib/peekView/browser/peekView.js'; import { localize, localize2 } from '../../../../nls.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { fillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -732,7 +732,7 @@ class TestResultsPeek extends PeekViewWidget { const actionBar = this._actionbarWidget!; this._disposables.add(menu.onDidChange(() => { actions.length = 0; - createAndFillInActionBarActions(menu, undefined, actions); + fillInActionBarActions(menu.getActions(), actions); while (actionBar.getAction(1)) { actionBar.pull(0); // remove all but the view's default "close" button } @@ -740,7 +740,7 @@ class TestResultsPeek extends PeekViewWidget { })); const actions: IAction[] = []; - createAndFillInActionBarActions(menu, undefined, actions); + fillInActionBarActions(menu.getActions(), actions); actionBar.push(actions, { label: false, icon: true, index: 0 }); } diff --git a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts index 2adb2d182ef69..0af4bb62f5157 100644 --- a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts +++ b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts @@ -8,7 +8,6 @@ import { Dimension, isKeyboardEvent } from '../../../../base/browser/dom.js'; import { Orientation, Sizing, SplitView } from '../../../../base/browser/ui/splitview/splitview.js'; import { IAsyncDataTreeViewState } from '../../../../base/browser/ui/tree/asyncDataTree.js'; import { ITreeNode, TreeMouseEventTarget } from '../../../../base/browser/ui/tree/tree.js'; -import { IAction } from '../../../../base/common/actions.js'; import { Color } from '../../../../base/common/color.js'; import { Event } from '../../../../base/common/event.js'; import { FuzzyScore } from '../../../../base/common/filters.js'; @@ -24,7 +23,7 @@ import { IModelDecorationOptions, TrackedRangeStickiness, IModelDeltaDecoration, import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; import * as peekView from '../../../../editor/contrib/peekView/browser/peekView.js'; import { localize } from '../../../../nls.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -130,8 +129,7 @@ export class TypeHierarchyTreePeekWidget extends peekView.PeekViewWidget { const menu = this._menuService.createMenu(TypeHierarchyTreePeekWidget.TitleMenu, this._contextKeyService); const updateToolbar = () => { - const actions: IAction[] = []; - createAndFillInActionBarActions(menu, undefined, actions); + const actions = getFlatActionBarActions(menu.getActions()); this._actionbarWidget!.clear(); this._actionbarWidget!.push(actions, { label: false, icon: true }); }; diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 90c965412bfdb..7b351ff8a6ff4 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -25,7 +25,7 @@ import { ipcRenderer, process } from '../../base/parts/sandbox/electron-sandbox/ import { IWorkspaceEditingService } from '../services/workspaces/common/workspaceEditing.js'; import { IMenuService, MenuId, IMenu, MenuItemAction, MenuRegistry } from '../../platform/actions/common/actions.js'; import { ICommandAction } from '../../platform/action/common/action.js'; -import { createAndFillInActionBarActions } from '../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from '../../platform/actions/browser/menuEntryActionViewItem.js'; import { RunOnceScheduler } from '../../base/common/async.js'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../base/common/lifecycle.js'; import { LifecyclePhase, ILifecycleService, WillShutdownEvent, ShutdownReason, BeforeShutdownErrorEvent, BeforeShutdownEvent } from '../services/lifecycle/common/lifecycle.js'; @@ -932,14 +932,12 @@ export class NativeWindow extends BaseWindow { this.touchBarDisposables.add(this.touchBarMenu.onDidChange(() => scheduler.schedule())); } - const actions: Array = []; - const disabled = this.configurationService.getValue('keyboard.touchbar.enabled') === false; const touchbarIgnored = this.configurationService.getValue('keyboard.touchbar.ignored'); const ignoredItems = Array.isArray(touchbarIgnored) ? touchbarIgnored : []; // Fill actions into groups respecting order - createAndFillInActionBarActions(this.touchBarMenu, undefined, actions); + const actions = getFlatActionBarActions(this.touchBarMenu.getActions()); // Convert into command action multi array const items: ICommandAction[][] = []; From 55d98e3cbcbb936f90adb79fe1c6d3c912dd6aca Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:34:19 -0700 Subject: [PATCH 116/555] Move showDelayedHover to also use IHoverLifecycleOptions --- src/vs/base/browser/ui/hover/hover.ts | 2 +- .../browser/services/hoverService/hoverService.ts | 10 ++++++---- .../browser/services/hoverService/hoverWidget.ts | 2 +- .../contrib/terminal/browser/terminalTabsList.ts | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 9d965be23dfb7..92e459fbb0938 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -54,7 +54,7 @@ export interface IHoverDelegate2 { */ showDelayedHover( options: IHoverOptions, - groupId: number | string | undefined, + lifecycleOptions: Pick, ): IDelayedHoverWidget | IHoverWidget | undefined; /** diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 7d152e8e58795..076b7a2e0e5b2 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -65,7 +65,7 @@ export class HoverService extends Disposable implements IHoverService { showDelayedHover( options: IHoverOptions, - groupId?: number | string, + lifecycleOptions: Pick, ): IDelayedHoverWidget | IHoverWidget | undefined { if (!this._currentDelayedHover || this._currentDelayedHover.wasShown) { // Current hover is sticky, reject @@ -79,7 +79,7 @@ export class HoverService extends Disposable implements IHoverService { } // Check group identity, if it's the same skip the delay and show the hover immediately - if (this._currentHover && !this._currentHover.isDisposed && this._currentDelayedHoverGroupId !== undefined && this._currentDelayedHoverGroupId === groupId) { + if (this._currentHover && !this._currentHover.isDisposed && this._currentDelayedHoverGroupId !== undefined && this._currentDelayedHoverGroupId === lifecycleOptions?.groupId) { return this.showHover({ ...options, appearance: { @@ -117,7 +117,7 @@ export class HoverService extends Disposable implements IHoverService { return wasShown; } }; - this._currentDelayedHoverGroupId = groupId; + this._currentDelayedHoverGroupId = lifecycleOptions?.groupId; return this._currentDelayedHover; } @@ -156,7 +156,9 @@ export class HoverService extends Disposable implements IHoverService { ) { const store = new DisposableStore(); store.add(addDisposableListener(target, EventType.MOUSE_OVER, e => { - this.showDelayedHover(resolveHoverOptions(e), lifecycleOptions?.groupId); + this.showDelayedHover(resolveHoverOptions(e), { + groupId: lifecycleOptions?.groupId + }); })); if (lifecycleOptions?.setupKeyboardEvents) { store.add(addDisposableListener(target, EventType.KEY_DOWN, e => { diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 089ec334be5f6..3260763397686 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -132,7 +132,7 @@ export class HoverWidget extends Widget implements IHoverWidget { this._enableFocusTraps = true; } - this._hoverPosition = !options.position?.hoverPosition || !isNumber(options.position.hoverPosition) ? HoverPosition.ABOVE : options.position.hoverPosition; + this._hoverPosition = options.position?.hoverPosition === undefined || !isNumber(options.position.hoverPosition) ? HoverPosition.ABOVE : options.position.hoverPosition; // Don't allow mousedown out of the widget, otherwise preventDefault will call and text will // not be selected. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 5c1cabe8b8892..6ef488889a5f0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -281,7 +281,7 @@ class TerminalTabsRenderer implements IListRenderer Date: Wed, 30 Oct 2024 10:34:40 -0700 Subject: [PATCH 117/555] Pick up latest markdown language server (#232634) --- .../markdown-language-features/package-lock.json | 16 ++++++++-------- .../markdown-language-features/package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index cabfda2e0fe29..91a1a6c5cb9f5 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -19,7 +19,7 @@ "punycode": "^2.3.1", "vscode-languageclient": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.11", - "vscode-markdown-languageserver": "^0.5.0-alpha.8", + "vscode-markdown-languageserver": "^0.5.0-alpha.9", "vscode-uri": "^3.0.3" }, "devDependencies": { @@ -618,15 +618,15 @@ "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" }, "node_modules/vscode-markdown-languageserver": { - "version": "0.5.0-alpha.8", - "resolved": "https://registry.npmjs.org/vscode-markdown-languageserver/-/vscode-markdown-languageserver-0.5.0-alpha.8.tgz", - "integrity": "sha512-Bp6YXHy4EMQ8JpsmXpQHa78byLvv83wVnLmhVfTaJsZTBwF8IOJ56NBUasepg9L6QVffUA9T2H/PfBqEOGpeOQ==", + "version": "0.5.0-alpha.9", + "resolved": "https://registry.npmjs.org/vscode-markdown-languageserver/-/vscode-markdown-languageserver-0.5.0-alpha.9.tgz", + "integrity": "sha512-60jiPHgkstgkyODCN8qCJ4xAOrP/EoKyISmEAcJ7ILT5k2kAJF9JFEl3LvVZ+11HGGMJ2lm1L+lT2/JHvu5Pgg==", "dependencies": { "@vscode/l10n": "^0.0.11", "vscode-languageserver": "^8.1.0", "vscode-languageserver-textdocument": "^1.0.8", "vscode-languageserver-types": "^3.17.3", - "vscode-markdown-languageservice": "^0.5.0-alpha.7", + "vscode-markdown-languageservice": "^0.5.0-alpha.8", "vscode-uri": "^3.0.7" }, "engines": { @@ -639,9 +639,9 @@ "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" }, "node_modules/vscode-markdown-languageserver/node_modules/vscode-markdown-languageservice": { - "version": "0.5.0-alpha.7", - "resolved": "https://registry.npmjs.org/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.5.0-alpha.7.tgz", - "integrity": "sha512-Iq9S5YGHm3D/UG9Usm8a/O5tYCo9FwaMF7nJsDQCxKgVZu5OzwOj3ixDkhoM+c8GKXiwt23DxhhWRuvI4odkTg==", + "version": "0.5.0-alpha.8", + "resolved": "https://registry.npmjs.org/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.5.0-alpha.8.tgz", + "integrity": "sha512-b2NgVMZvzI/7hRL32Kcu9neAAPFQzkcf/Fqwlxbz9p1/Q7aIorGACOGGo00s72AJtwjkCJ29eVJwUlFMFbPKqA==", "dependencies": { "@vscode/l10n": "^0.0.10", "node-html-parser": "^6.1.5", diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 0670265610861..3f1c3a5fd5814 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -773,7 +773,7 @@ "punycode": "^2.3.1", "vscode-languageclient": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.11", - "vscode-markdown-languageserver": "^0.5.0-alpha.8", + "vscode-markdown-languageserver": "^0.5.0-alpha.9", "vscode-uri": "^3.0.3" }, "devDependencies": { From a563e80b420bc01419bcc85484a4dad55d81df6b Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 30 Oct 2024 10:38:10 -0700 Subject: [PATCH 118/555] search APIs- deep links to interface fields do not resolve Fixes #227478 --- .../services/search/common/searchExtTypes.ts | 6 +++--- .../vscode.proposed.fileSearchProvider2.d.ts | 4 ++-- src/vscode-dts/vscode.proposed.findFiles2.d.ts | 6 +++--- .../vscode.proposed.findTextInFiles2.d.ts | 16 ++++++++-------- .../vscode.proposed.textSearchProvider2.d.ts | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/services/search/common/searchExtTypes.ts b/src/vs/workbench/services/search/common/searchExtTypes.ts index fc785ae189bce..decb405a400a1 100644 --- a/src/vs/workbench/services/search/common/searchExtTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtTypes.ts @@ -152,11 +152,11 @@ export interface TextSearchProviderFolderOptions { */ local: boolean; /** - * Use ignore files at the parent directory. If set, {@link TextSearchProviderOptions.useIgnoreFiles.local} should also be `true`. + * Use ignore files at the parent directory. If set, `local` in {@link TextSearchProviderFolderOptions.useIgnoreFiles} should also be `true`. */ parent: boolean; /** - * Use global ignore files. If set, {@link TextSearchProviderOptions.useIgnoreFiles.local} should also be `true`. + * Use global ignore files. If set, `local` in {@link TextSearchProviderFolderOptions.useIgnoreFiles} should also be `true`. */ global: boolean; }; @@ -440,7 +440,7 @@ export interface TextSearchProvider2 { export interface TextSearchComplete2 { /** * Whether the search hit the limit on the maximum number of search results. - * `maxResults` on {@linkcode TextSearchOptions} specifies the max number of results. + * `maxResults` on {@link TextSearchOptions} specifies the max number of results. * - If exactly that number of matches exist, this should be false. * - If `maxResults` matches are returned and more exist, this should be true. * - If search hits an internal limit which is less than `maxResults`, this should be true. diff --git a/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts b/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts index e23123d376c4a..e986e8fbd5728 100644 --- a/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts +++ b/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts @@ -42,11 +42,11 @@ declare module 'vscode' { */ local: boolean; /** - * Use ignore files at the parent directory. If set, {@link FileSearchProviderOptions.useIgnoreFiles.local} should also be `true`. + * Use ignore files at the parent directory. If set, `local` in {@link FileSearchProviderOptions.useIgnoreFiles} should also be `true`. */ parent: boolean; /** - * Use global ignore files. If set, {@link FileSearchProviderOptions.useIgnoreFiles.local} should also be `true`. + * Use global ignore files. If set, `local` in {@link FileSearchProviderOptions.useIgnoreFiles} should also be `true`. */ global: boolean; }; diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts index 579ba732ac9da..15a0c584cb027 100644 --- a/src/vscode-dts/vscode.proposed.findFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -9,8 +9,8 @@ declare module 'vscode' { export interface FindFiles2Options { /** - * An array of {@link GlobPattern GlobPattern} that defines files to exclude. - * The glob patterns will be matched against the file paths of files relative to their workspace or {@link RelativePattern.baseUri} if applicable. + * An array of {@link GlobPattern} that defines files to exclude. + * The glob patterns will be matched against the file paths of files relative to their workspace or {@link RelativePattern}'s `baseUri` if applicable. * * If more than one value is used, the values are combined with a logical AND. * For example, consider the following code: @@ -26,7 +26,7 @@ declare module 'vscode' { exclude?: GlobPattern[]; /** - * Which settings to follow when searching for files. Defaults to {@link ExcludeSettingOptions.searchAndFilesExclude}. + * Which settings to follow when searching for files. Defaults to `ExcludeSettingOptions.searchAndFilesExclude`. */ useExcludeSettings?: ExcludeSettingOptions; diff --git a/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts b/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts index 95a01a850f38c..e62d23bfb7caa 100644 --- a/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts @@ -9,9 +9,9 @@ declare module 'vscode' { export interface FindTextInFilesOptions2 { /** - * An array of {@link GlobPattern GlobPattern} that defines the files to search for. - * The glob patterns will be matched against the file paths of files relative to their workspace or {@link baseUri GlobPattern.baseUri} if applicable. - * Use a {@link RelativePattern RelativePattern} to restrict the search results to a {@link WorkspaceFolder workspace folder}. + * An array of {@link GlobPattern} that defines the files to search for. + * The glob patterns will be matched against the file paths of files relative to their workspace or {@link GlobPattern}'s `baseUri` if applicable. + * Use a {@link RelativePattern} to restrict the search results to a {@link WorkspaceFolder workspace folder}. * * If more than one value is used, the values are combined with a logical OR. * @@ -28,8 +28,8 @@ declare module 'vscode' { include?: GlobPattern[]; /** - * An array of {@link GlobPattern GlobPattern} that defines files to exclude. - * The glob patterns will be matched against the file paths of files relative to their workspace or {@link RelativePattern.baseUri} if applicable. + * An array of {@link GlobPattern} that defines files to exclude. + * The glob patterns will be matched against the file paths of files relative to their workspace or {@link RelativePattern}'s `baseUri` if applicable. * * If more than one value is used, the values are combined with a logical AND. * For example, consider the following code: @@ -45,7 +45,7 @@ declare module 'vscode' { exclude?: GlobPattern[]; /** - * Which settings to follow when searching for files. Defaults to {@link ExcludeSettingOptions.searchAndFilesExclude}. + * Which settings to follow when searching for files. Defaults to `ExcludeSettingOptions.searchAndFilesExclude`. */ useExcludeSettings?: ExcludeSettingOptions; @@ -73,12 +73,12 @@ declare module 'vscode' { */ local?: boolean; /** - * Use ignore files at the parent directory. When set to `true`, {@link FindTextInFilesOptions2.useIgnoreFiles.local} must be `true`. + * Use ignore files at the parent directory. When set to `true`, `local` in {@link FindTextInFilesOptions2.useIgnoreFiles} must be `true`. * May default to `search.useParentIgnoreFiles` setting if not set. */ parent?: boolean; /** - * Use global ignore files. When set to `true`, {@link FindTextInFilesOptions2.useIgnoreFiles.local} must also be `true`. + * Use global ignore files. When set to `true`, `local` in {@link FindTextInFilesOptions2.useIgnoreFiles} must also be `true`. * May default to `search.useGlobalIgnoreFiles` setting if not set. */ global?: boolean; diff --git a/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts b/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts index 23d1ba018b58d..c045956da9fa6 100644 --- a/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts +++ b/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts @@ -89,11 +89,11 @@ declare module 'vscode' { */ local: boolean; /** - * Use ignore files at the parent directory. If set, {@link TextSearchProviderOptions.useIgnoreFiles.local} should also be `true`. - */ + * Use ignore files at the parent directory. If set, `local` in {@link TextSearchProviderFolderOptions.useIgnoreFiles} should also be `true`. + */ parent: boolean; /** - * Use global ignore files. If set, {@link TextSearchProviderOptions.useIgnoreFiles.local} should also be `true`. + * Use global ignore files. If set, `local` in {@link TextSearchProviderFolderOptions.useIgnoreFiles} should also be `true`. */ global: boolean; }; @@ -145,7 +145,7 @@ declare module 'vscode' { export interface TextSearchComplete2 { /** * Whether the search hit the limit on the maximum number of search results. - * `maxResults` on {@linkcode TextSearchProviderOptions} specifies the max number of results. + * `maxResults` on {@link TextSearchProviderOptions} specifies the max number of results. * - If exactly that number of matches exist, this should be false. * - If `maxResults` matches are returned and more exist, this should be true. * - If search hits an internal limit which is less than `maxResults`, this should be true. From ae33fb2d5c38966e02ff41711f65327a076b659e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:41:14 -0700 Subject: [PATCH 119/555] Remove IDelayedHoverWidget interface --- src/vs/base/browser/ui/hover/hover.ts | 11 +----- .../services/hoverService/hoverService.ts | 36 ++++++++----------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 92e459fbb0938..ed668e79fad94 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -55,7 +55,7 @@ export interface IHoverDelegate2 { showDelayedHover( options: IHoverOptions, lifecycleOptions: Pick, - ): IDelayedHoverWidget | IHoverWidget | undefined; + ): IHoverWidget | undefined; /** * A simple wrapper around showDelayedHover that includes listening to events on the @@ -152,15 +152,6 @@ export interface IHoverWidget extends IDisposable { readonly isDisposed: boolean; } -export interface IDelayedHoverWidget extends IDisposable { - /** - * Whether the hover widget has been disposed. - */ - readonly isDisposed: boolean; - - readonly wasShown: boolean; -} - export interface IHoverOptions { /** * The content to display in the primary section of the hover. The type of text determines the diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 076b7a2e0e5b2..c5877970ab53d 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -20,7 +20,7 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; import { mainWindow } from '../../../../base/browser/window.js'; import { ContextViewHandler } from '../../../../platform/contextview/browser/contextViewService.js'; -import type { IHoverLifecycleOptions, IDelayedHoverWidget, IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; +import type { IHoverLifecycleOptions, IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; import type { IHoverDelegate, IHoverDelegateTarget } from '../../../../base/browser/ui/hover/hoverDelegate.js'; import { ManagedHoverWidget } from './updatableHoverWidget.js'; import { timeout, TimeoutTimer } from '../../../../base/common/async.js'; @@ -34,7 +34,8 @@ export class HoverService extends Disposable implements IHoverService { private _contextViewHandler: IContextViewProvider; private _currentHoverOptions: IHoverOptions | undefined; private _currentHover: HoverWidget | undefined; - private _currentDelayedHover: IDelayedHoverWidget | undefined; + private _currentDelayedHover: HoverWidget | undefined; + private _currentDelayedHoverWasShown: boolean = false; private _currentDelayedHoverGroupId: number | string | undefined; private _lastHoverOptions: IHoverOptions | undefined; @@ -66,8 +67,8 @@ export class HoverService extends Disposable implements IHoverService { showDelayedHover( options: IHoverOptions, lifecycleOptions: Pick, - ): IDelayedHoverWidget | IHoverWidget | undefined { - if (!this._currentDelayedHover || this._currentDelayedHover.wasShown) { + ): IHoverWidget | undefined { + if (!this._currentDelayedHover || this._currentDelayedHoverWasShown) { // Current hover is sticky, reject if (this._currentHover && this._currentHoverOptions?.persistence?.sticky) { return undefined; @@ -93,33 +94,24 @@ export class HoverService extends Disposable implements IHoverService { const hover = this._createHover(options, undefined); if (!hover) { this._currentDelayedHover = undefined; + this._currentDelayedHoverWasShown = false; this._currentDelayedHoverGroupId = undefined; return undefined; } - const delay = this._configurationService.getValue('workbench.hover.delay'); - let wasShown = false; - timeout(delay).then(() => { + this._currentDelayedHover = hover; + this._currentDelayedHoverWasShown = false; + this._currentDelayedHoverGroupId = lifecycleOptions?.groupId; + + timeout(this._configurationService.getValue('workbench.hover.delay')).then(() => { if (hover && !hover.isDisposed) { - wasShown = true; + this._currentDelayedHoverWasShown = true; + this._currentDelayedHoverWasShown = true; this._showHover(hover, options); } }); - this._currentDelayedHover = { - dispose() { - hover?.dispose(); - }, - get isDisposed() { - return hover.isDisposed ?? true; - }, - get wasShown() { - return wasShown; - } - }; - this._currentDelayedHoverGroupId = lifecycleOptions?.groupId; - - return this._currentDelayedHover; + return hover; } setupDelayedHover( From 66b4cd70b61630e43568f6c0514868d7d3ad6728 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:48:52 -0700 Subject: [PATCH 120/555] Clean up types --- src/vs/base/browser/ui/hover/hover.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index ed668e79fad94..86aac35e1c02d 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -73,7 +73,7 @@ export interface IHoverDelegate2 { */ setupDelayedHover( target: HTMLElement, - hoverOptions: (() => Omit) | Omit, + hoverOptions: (() => IDelayedHoverOptions) | IDelayedHoverOptions, lifecycleOptions?: IHoverLifecycleOptions, ): IDisposable; @@ -96,7 +96,7 @@ export interface IHoverDelegate2 { */ setupDelayedHoverAtMouse( target: HTMLElement, - hoverOptions: (() => Omit) | Omit, + hoverOptions: (() => IDelayedHoverAtMouseOptions) | IDelayedHoverAtMouseOptions, lifecycleOptions?: IHoverLifecycleOptions, ): IDisposable; @@ -222,6 +222,13 @@ export interface IHoverOptions { appearance?: IHoverAppearanceOptions; } +// `target` is ignored for delayed hover methods as it's included in the method and added +// automatically when the hover options get resolved. +export type IDelayedHoverOptions = Omit; + +// `position` is ignored for delayed at mouse hover methods as it's overwritten by the mouse event. +export type IDelayedHoverAtMouseOptions = Omit; + export interface IHoverLifecycleOptions { /** * The group ID of the hover. If the group ID is the same as the currently shown hover, the From f058e729e10df470232d2c4a52a085dd5b0fc0cb Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 30 Oct 2024 11:11:28 -0700 Subject: [PATCH 121/555] Enable model picker label to show ellipsis instead of overflowing (#232639) Fix #231369 --- .../contrib/chat/browser/chatInputPart.ts | 4 ++-- .../contrib/chat/browser/media/chat.css | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index d6d83630b9eeb..5c1b7d03df633 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -655,6 +655,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return undefined; } })); + this.executeToolbar.getElement().classList.add('chat-execute-toolbar'); this.executeToolbar.context = { widget } satisfies IChatExecuteActionContext; this._register(this.executeToolbar.onDidChangeMenuItems(() => { if (this.cachedDimensions && typeof this.cachedExecuteToolbarWidth === 'number' && this.cachedExecuteToolbarWidth !== this.executeToolbar.getItemsWidth()) { @@ -1283,8 +1284,7 @@ class ModelPickerActionViewItem extends MenuEntryActionViewItem { if (this.label) { const model = this._languageModelsService.lookupLanguageModel(this.currentLanguageModel); if (model) { - this.label.textContent = model.name; - dom.reset(this.label, ...renderLabelWithIcons(`${model.name}$(chevron-down)`)); + dom.reset(this.label, dom.$('span.chat-model-label', undefined, model.name), ...renderLabelWithIcons(`$(chevron-down)`)); } } } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index d044da15bdfa6..eb1555ca226f3 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -699,6 +699,24 @@ have to be updated for changes to the rules above, or to support more deeply nes margin-right: auto; } +.interactive-session .chat-input-toolbars > .chat-execute-toolbar { + min-width: 0px; + + .chat-modelPicker-item { + min-width: 0px; + + .chat-model-label { + min-width: 0px; + overflow: hidden; + text-overflow: ellipsis; + } + + .codicon { + flex-shrink: 0; + } + } +} + .interactive-session .chat-input-toolbars .chat-modelPicker-item .action-label { height: 16px; padding: 3px 0px 3px 6px; From c0ee8f284890db27e072a9231341c445b5ac912c Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 30 Oct 2024 11:21:31 -0700 Subject: [PATCH 122/555] refactor: better check for arg type in Retry action (#232645) --- .../contrib/chat/browser/actions/chatTitleActions.ts | 5 +++-- .../workbench/contrib/chat/common/chatEditingService.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index c2ba129720c09..ca40e265fd235 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -28,7 +28,7 @@ import { CellEditType, CellKind, NOTEBOOK_EDITOR_ID } from '../../../notebook/co import { NOTEBOOK_IS_ACTIVE_EDITOR } from '../../../notebook/common/notebookContextKeys.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { applyingChatEditsFailedContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { applyingChatEditsFailedContextKey, IChatEditingService, isChatEditingActionContext, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IParsedChatRequest } from '../../common/chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatProgress, IChatService } from '../../common/chatService.js'; import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; @@ -211,7 +211,8 @@ export function registerChatTitleActions() { const chatWidgetService = accessor.get(IChatWidgetService); let item = args[0]; - if (typeof item === 'object' && !!item && 'sessionId' in item) { + if (isChatEditingActionContext(item)) { + // Resolve chat editing action context to the last response VM item = chatWidgetService.getWidgetBySessionId(item.sessionId)?.viewModel?.getItems().at(-1); } if (!isResponseVM(item)) { diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index 1fad277514f73..2cad349b64b7e 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -116,3 +116,12 @@ export const enum ChatEditKind { Created, Modified, } + +export interface IChatEditingActionContext { + // The chat session ID that this editing session is associated with + sessionId: string; +} + +export function isChatEditingActionContext(thing: unknown): thing is IChatEditingActionContext { + return typeof thing === 'object' && !!thing && 'sessionId' in thing; +} From c711bc9333ba339fde1a530de0094b3fa32f09de Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 30 Oct 2024 11:54:40 -0700 Subject: [PATCH 123/555] look for existing editor input (#232533) * look for existing editor input * dont increase ref count on the model * guard against stale inputs in the cache --- .../replNotebook/browser/repl.contribution.ts | 94 ++++++++++++------- .../replNotebook/browser/replEditorInput.ts | 4 +- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts b/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts index d07f9c9447c38..cac4c248c8a8a 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts @@ -3,52 +3,54 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; -import { Registry } from '../../../../platform/registry/common/platform.js'; -import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; -import { EditorExtensions, IEditorControl, IEditorFactoryRegistry, IEditorSerializer } from '../../../common/editor.js'; -import { parse } from '../../../../base/common/marshalling.js'; -import { assertType } from '../../../../base/common/types.js'; -import { URI, UriComponents } from '../../../../base/common/uri.js'; -import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; -import { EditorInput } from '../../../common/editor/editorInput.js'; -import { CellEditType, CellKind, NotebookSetting, NotebookWorkingCopyTypeIdentifier, REPL_EDITOR_ID } from '../../notebook/common/notebookCommon.js'; -import { NotebookEditorInputOptions } from '../../notebook/common/notebookEditorInput.js'; -import { isReplEditorControl, ReplEditor, ReplEditorControl } from './replEditor.js'; -import { ReplEditorInput } from './replEditorInput.js'; +import { Event } from '../../../../base/common/event.js'; +import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; -import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { IWorkingCopyIdentifier } from '../../../services/workingCopy/common/workingCopy.js'; -import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from '../../../services/workingCopy/common/workingCopyEditorService.js'; +import { ResourceMap } from '../../../../base/common/map.js'; +import { parse } from '../../../../base/common/marshalling.js'; import { isEqual } from '../../../../base/common/resources.js'; -import { INotebookService } from '../../notebook/common/notebookService.js'; -import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; -import { INotebookEditorModelResolverService } from '../../notebook/common/notebookEditorModelResolverService.js'; import { isFalsyOrWhitespace } from '../../../../base/common/strings.js'; +import { assertType } from '../../../../base/common/types.js'; +import { URI, UriComponents } from '../../../../base/common/uri.js'; import { IBulkEditService } from '../../../../editor/browser/services/bulkEditService.js'; import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js'; -import { ResourceNotebookCellEdit } from '../../bulkEdit/browser/bulkCellEdits.js'; -import { IInteractiveHistoryService } from '../../interactive/browser/interactiveHistoryService.js'; -import { NotebookEditorWidget } from '../../notebook/browser/notebookEditorWidget.js'; -import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; +import { localize2 } from '../../../../nls.js'; +import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; +import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; +import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { EditorExtensions, IEditorControl, IEditorFactoryRegistry, IEditorSerializer } from '../../../common/editor.js'; +import { EditorInput } from '../../../common/editor/editorInput.js'; +import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { IWorkingCopyIdentifier } from '../../../services/workingCopy/common/workingCopy.js'; +import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from '../../../services/workingCopy/common/workingCopyEditorService.js'; +import { ResourceNotebookCellEdit } from '../../bulkEdit/browser/bulkCellEdits.js'; import { getReplView } from '../../debug/browser/repl.js'; import { REPL_VIEW_ID } from '../../debug/common/debug.js'; -import { IViewsService } from '../../../services/views/common/viewsService.js'; -import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; -import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { localize2 } from '../../../../nls.js'; +import { InlineChatController } from '../../inlineChat/browser/inlineChatController.js'; +import { IInteractiveHistoryService } from '../../interactive/browser/interactiveHistoryService.js'; import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from '../../notebook/browser/controller/coreActions.js'; -import * as icons from '../../notebook/browser/notebookIcons.js'; -import { IEditorService } from '../../../services/editor/common/editorService.js'; import { INotebookEditorOptions } from '../../notebook/browser/notebookBrowser.js'; -import { InlineChatController } from '../../inlineChat/browser/inlineChatController.js'; +import { NotebookEditorWidget } from '../../notebook/browser/notebookEditorWidget.js'; +import * as icons from '../../notebook/browser/notebookIcons.js'; +import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; +import { CellEditType, CellKind, NotebookSetting, NotebookWorkingCopyTypeIdentifier, REPL_EDITOR_ID } from '../../notebook/common/notebookCommon.js'; +import { NotebookEditorInputOptions } from '../../notebook/common/notebookEditorInput.js'; +import { INotebookEditorModelResolverService } from '../../notebook/common/notebookEditorModelResolverService.js'; +import { INotebookService } from '../../notebook/common/notebookService.js'; +import { isReplEditorControl, ReplEditor, ReplEditorControl } from './replEditor.js'; import { ReplEditorAccessibilityHelp } from './replEditorAccessibilityHelp.js'; -import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; +import { ReplEditorInput } from './replEditorInput.js'; type SerializedNotebookEditorData = { resource: URI; preferredResource: URI; viewType: string; options?: NotebookEditorInputOptions; label?: string }; class ReplEditorSerializer implements IEditorSerializer { @@ -101,6 +103,8 @@ export class ReplDocumentContribution extends Disposable implements IWorkbenchCo static readonly ID = 'workbench.contrib.replDocument'; + private readonly editorInputCache = new ResourceMap(); + constructor( @INotebookService notebookService: INotebookService, @IEditorResolverService editorResolverService: IEditorResolverService, @@ -126,20 +130,42 @@ export class ReplDocumentContribution extends Disposable implements IWorkbenchCo }, { createUntitledEditorInput: async ({ resource, options }) => { + if (resource) { + const editor = this.editorInputCache.get(resource); + if (editor && !editor.isDisposed()) { + return { editor, options }; + } else if (editor) { + this.editorInputCache.delete(resource); + } + } const scratchpad = this.configurationService.getValue(NotebookSetting.InteractiveWindowPromptToSave) !== true; const ref = await this.notebookEditorModelResolverService.resolve({ untitledResource: resource }, 'jupyter-notebook', { scratchpad, viewType: 'repl' }); + const notebookUri = ref.object.notebook.uri; + // untitled notebooks are disposed when they get saved. we should not hold a reference // to such a disposed notebook and therefore dispose the reference as well ref.object.notebook.onWillDispose(() => { ref.dispose(); }); const label = (options as INotebookEditorOptions)?.label ?? undefined; - return { editor: this.instantiationService.createInstance(ReplEditorInput, resource!, label), options }; + const editor = this.instantiationService.createInstance(ReplEditorInput, notebookUri, label); + this.editorInputCache.set(notebookUri, editor); + Event.once(editor.onWillDispose)(() => this.editorInputCache.delete(notebookUri)); + + return { editor, options }; }, createEditorInput: async ({ resource, options }) => { + if (this.editorInputCache.has(resource)) { + return { editor: this.editorInputCache.get(resource)!, options }; + } + const label = (options as INotebookEditorOptions)?.label ?? undefined; - return { editor: this.instantiationService.createInstance(ReplEditorInput, resource, label), options }; + const editor = this.instantiationService.createInstance(ReplEditorInput, resource, label); + this.editorInputCache.set(resource, editor); + Event.once(editor.onWillDispose)(() => this.editorInputCache.delete(resource)); + + return { editor, options }; } } ); diff --git a/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts b/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts index 35ef8208542a8..271af8eac541f 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts @@ -107,13 +107,13 @@ export class ReplEditorInput extends NotebookEditorInput implements ICompositeNo override async resolve() { const model = await super.resolve(); if (model) { - await this.ensureInputBoxCell(model.notebook); + this.ensureInputBoxCell(model.notebook); } return model; } - private async ensureInputBoxCell(notebook: NotebookTextModel) { + private ensureInputBoxCell(notebook: NotebookTextModel) { const lastCell = notebook.cells[notebook.cells.length - 1]; if (!lastCell || lastCell.cellKind === CellKind.Markup || lastCell.outputs.length > 0 || lastCell.internalMetadata.executionOrder !== undefined) { From c74816239d84e3b04224fd9b4f970ea00970f8ca Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 30 Oct 2024 12:09:20 -0700 Subject: [PATCH 124/555] Pick up TS 5.7 insiders for builtin ts version (#232650) --- extensions/package-lock.json | 8 ++++---- extensions/package.json | 2 +- .../src/languageFeatures/copilotRelated.ts | 2 -- .../src/tsServer/protocol/protocol.d.ts | 13 ------------- .../src/typescriptService.ts | 1 - 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/extensions/package-lock.json b/extensions/package-lock.json index aa07b93e52e89..b902b30a51460 100644 --- a/extensions/package-lock.json +++ b/extensions/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "typescript": "5.6.3" + "typescript": "^5.7.0-dev.20241030" }, "devDependencies": { "@parcel/watcher": "2.1.0", @@ -606,9 +606,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.0-dev.20241030", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.0-dev.20241030.tgz", + "integrity": "sha512-GTDPhqdtcO+8WOPjYAp33e8dtlZN6pY9Z89aVLH1PsaiA1yaWNPVNCQ1KRCc1BK/jQGJVVBhu0fxWGSLjAE1Dw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/extensions/package.json b/extensions/package.json index a1a224947891b..08951c240c767 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "5.6.3" + "typescript": "^5.7.0-dev.20241030" }, "scripts": { "postinstall": "node ./postinstall.mjs" diff --git a/extensions/typescript-language-features/src/languageFeatures/copilotRelated.ts b/extensions/typescript-language-features/src/languageFeatures/copilotRelated.ts index f1ceac1a584a7..632b04798cbe3 100644 --- a/extensions/typescript-language-features/src/languageFeatures/copilotRelated.ts +++ b/extensions/typescript-language-features/src/languageFeatures/copilotRelated.ts @@ -70,12 +70,10 @@ export function register( if (!file) { return { entries: [] }; } - // @ts-expect-error until ts5.7 const response = await client.execute('copilotRelated', { file, }, token) as Proto.CopilotRelatedResponse; if (response.type !== 'response' || !response.body) { return { entries: [] }; } - // @ts-expect-error until ts5.7 return { entries: response.body.relatedFiles.map(f => client.toResource(f)), traits: [] }; })); } diff --git a/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts b/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts index cd70b6b7d41c3..747e7c22e3724 100644 --- a/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts +++ b/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts @@ -19,18 +19,5 @@ declare module '../../../../node_modules/typescript/lib/typescript' { interface Response { readonly _serverType?: ServerType; } - - //#region PreparePasteEdits - interface PreparePasteEditsRequest extends FileRequest { - command: 'preparePasteEdits'; - arguments: PreparePasteEditsRequestArgs; - } - interface PreparePasteEditsRequestArgs extends FileRequestArgs { - copiedTextSpan: TextSpan[]; - } - interface PreparePasteEditsResponse extends Response { - body: boolean; - } - //#endregion } } diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 33d89e1df8d5b..306769f952a31 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -77,7 +77,6 @@ interface StandardTsServerRequests { 'getMoveToRefactoringFileSuggestions': [Proto.GetMoveToRefactoringFileSuggestionsRequestArgs, Proto.GetMoveToRefactoringFileSuggestions]; 'linkedEditingRange': [Proto.FileLocationRequestArgs, Proto.LinkedEditingRangeResponse]; 'mapCode': [Proto.MapCodeRequestArgs, Proto.MapCodeResponse]; - // @ts-expect-error until ts5.7 'copilotRelated': [Proto.FileRequestArgs, Proto.CopilotRelatedResponse]; 'getPasteEdits': [Proto.GetPasteEditsRequestArgs, Proto.GetPasteEditsResponse]; 'preparePasteEdits': [Proto.PreparePasteEditsRequestArgs, Proto.PreparePasteEditsResponse]; From 56246f32440802b99495cecde8791a584bae849a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 30 Oct 2024 12:49:03 -0700 Subject: [PATCH 125/555] force app shutdown on background updated, if necessary (#231390) * force app shutdown on background updated, if necessary * fix CloseApplications * use force anyway --- build/win32/code.iss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build/win32/code.iss b/build/win32/code.iss index fca3d1e9d9b85..2f73942ba09df 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -35,6 +35,11 @@ ArchitecturesAllowed={#ArchitecturesAllowed} ArchitecturesInstallIn64BitMode={#ArchitecturesInstallIn64BitMode} WizardStyle=modern +// We've seen an uptick on broken installations from updates which were unable +// to shutdown VS Code. We rely on the fact that the update signals +// that VS Code is ready to be shutdown, so we're good to use `force` here. +CloseApplications=force + #ifdef Sign SignTool=esrp #endif From e37fecca7e25da2b299c7222cc09f38c041eb7b4 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:02:55 +0000 Subject: [PATCH 126/555] remove contrib issue reporter api (#232643) remove contrib issue reporter --- .../platform/extensions/common/extensionsApiProposals.ts | 3 --- .../services/actions/common/menusExtensionPoint.ts | 3 +-- src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts | 7 ------- 3 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 672a7cf9755a2..421d3cb91d3f8 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -98,9 +98,6 @@ const _allApiProposals = { contribEditorContentMenu: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts', }, - contribIssueReporter: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts', - }, contribLabelFormatterWorkspaceTooltip: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', }, diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index bcc7d862a8634..683d0a924527c 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -306,8 +306,7 @@ const apiMenus: IAPIMenu[] = [ { key: 'issue/reporter', id: MenuId.IssueReporter, - description: localize('issue.reporter', "The contributed issue reporter menu"), - proposed: 'contribIssueReporter' + description: localize('issue.reporter', "The contributed issue reporter menu") }, { key: 'testing/item/context', diff --git a/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts b/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts deleted file mode 100644 index 522d33344678a..0000000000000 --- a/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// empty placeholder declaration for the `issue reporter`-submenu contribution point -// https://github.com/microsoft/vscode/issues/196863 From f6d41bc2005585440527c14d9f23a0194cb2d917 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 30 Oct 2024 13:15:46 -0700 Subject: [PATCH 127/555] Small cleanup in js/ts package strings (#232660) --- .../typescript-language-features/package.nls.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 4027c8187d28e..2344f3dbc50bf 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -74,7 +74,7 @@ "configuration.tsserver.maxTsServerMemory": "The maximum amount of memory (in MB) to allocate to the TypeScript server process. To use a memory limit greater than 4 GB, use `#typescript.tsserver.nodePath#` to run TS Server with a custom Node installation.", "configuration.tsserver.experimental.enableProjectDiagnostics": "Enables project wide error reporting.", "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Defaults to use VS Code's locale.", - "typescript.locale.auto": "Use VS Code's configured display language", + "typescript.locale.auto": "Use VS Code's configured display language.", "configuration.implicitProjectConfig.module": "Sets the module system for the program. See more: https://www.typescriptlang.org/tsconfig#module.", "configuration.implicitProjectConfig.target": "Set target JavaScript language version for emitted JavaScript and include library declarations. See more: https://www.typescriptlang.org/tsconfig#target.", "configuration.implicitProjectConfig.checkJs": "Enable/disable semantic checking of JavaScript files. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", @@ -85,7 +85,7 @@ "configuration.implicitProjectConfig.strictFunctionTypes": "Enable/disable [strict function types](https://www.typescriptlang.org/tsconfig#strictFunctionTypes) in JavaScript and TypeScript files that are not part of a project. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", "configuration.suggest.jsdoc.generateReturns": "Enable/disable generating `@returns` annotations for JSDoc templates.", "configuration.suggest.autoImports": "Enable/disable auto import suggestions.", - "configuration.preferGoToSourceDefinition": "Makes Go to Definition avoid type declaration files when possible by triggering Go to Source Definition instead. This allows Go to Source Definition to be triggered with the mouse gesture.", + "configuration.preferGoToSourceDefinition": "Makes `Go to Definition` avoid type declaration files when possible by triggering `Go to Source Definition` instead. This allows `Go to Source Definition` to be triggered with the mouse gesture.", "inlayHints.parameterNames.none": "Disable parameter name hints.", "inlayHints.parameterNames.literals": "Enable parameter name hints only for literal arguments.", "inlayHints.parameterNames.all": "Enable parameter name hints for literal and non-literal arguments.", @@ -146,8 +146,8 @@ "typescript.preferences.importModuleSpecifierEnding.index": "Shorten `./component/index.js` to `./component/index`.", "typescript.preferences.importModuleSpecifierEnding.js": "Do not shorten path endings; include the `.js` or `.ts` extension.", "typescript.preferences.jsxAttributeCompletionStyle": "Preferred style for JSX attribute completions.", - "javascript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type. See `javascript.preferences.quoteStyle` to control the type of quotes used for string attributes.", - "typescript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type. See `typescript.preferences.quoteStyle` to control the type of quotes used for string attributes.", + "javascript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type. See `#javascript.preferences.quoteStyle#` to control the type of quotes used for string attributes.", + "typescript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type. See `#typescript.preferences.quoteStyle#` to control the type of quotes used for string attributes.", "typescript.preferences.jsxAttributeCompletionStyle.braces": "Insert `={}` after attribute names.", "typescript.preferences.jsxAttributeCompletionStyle.none": "Only insert attribute names.", "typescript.preferences.includePackageJsonAutoImports": "Enable/disable searching `package.json` dependencies for available auto imports.", @@ -157,7 +157,7 @@ "typescript.preferences.autoImportFileExcludePatterns": "Specify glob patterns of files to exclude from auto imports. Relative paths are resolved relative to the workspace root. Patterns are evaluated using tsconfig.json [`exclude`](https://www.typescriptlang.org/tsconfig#exclude) semantics.", "typescript.preferences.autoImportSpecifierExcludeRegexes": "Specify regular expressions to exclude auto imports with matching import specifiers. Examples:\n\n- `^node:`\n- `lib/internal` (slashes don't need to be escaped...)\n- `/lib\\/internal/i` (...unless including surrounding slashes for `i` or `u` flags)\n- `^lodash$` (only allow subpath imports from lodash)", "typescript.preferences.preferTypeOnlyAutoImports": "Include the `type` keyword in auto-imports whenever possible. Requires using TypeScript 5.3+ in the workspace.", - "typescript.workspaceSymbols.excludeLibrarySymbols": "Exclude symbols that come from library files in Go to Symbol in Workspace results. Requires using TypeScript 5.3+ in the workspace.", + "typescript.workspaceSymbols.excludeLibrarySymbols": "Exclude symbols that come from library files in `Go to Symbol in Workspace` results. Requires using TypeScript 5.3+ in the workspace.", "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code.", "typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.", "typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.", From 304fcb28d3834ca99b1381de3125c58033f96707 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 30 Oct 2024 13:20:40 -0700 Subject: [PATCH 128/555] remove 'very experimental' comment --- src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts | 2 -- src/vscode-dts/vscode.proposed.findFiles2.d.ts | 2 -- src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts | 2 -- src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts | 2 -- 4 files changed, 8 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts b/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts index e986e8fbd5728..67bd5ab16b4a9 100644 --- a/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts +++ b/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts @@ -75,8 +75,6 @@ declare module 'vscode' { */ export interface FileSearchProvider2 { /** - * WARNING: VERY EXPERIMENTAL. - * * Provide the set of files that match a certain file path pattern. * @param pattern The search pattern to match against file paths. * @param options A set of options to consider while searching files. diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts index 15a0c584cb027..8e7c11874b4c7 100644 --- a/src/vscode-dts/vscode.proposed.findFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -93,8 +93,6 @@ declare module 'vscode' { export namespace workspace { /** - * WARNING: VERY EXPERIMENTAL. - * * Find files across all {@link workspace.workspaceFolders workspace folders} in the workspace. * * @example diff --git a/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts b/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts index e62d23bfb7caa..c7c7c9507a5a6 100644 --- a/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts @@ -132,8 +132,6 @@ declare module 'vscode' { export namespace workspace { /** - * WARNING: VERY EXPERIMENTAL. - * * Search text in files across all {@link workspace.workspaceFolders workspace folders} in the workspace. * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. * @param options An optional set of query options. diff --git a/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts b/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts index c045956da9fa6..146b76b5fa5c0 100644 --- a/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts +++ b/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts @@ -248,8 +248,6 @@ declare module 'vscode' { */ export interface TextSearchProvider2 { /** - * WARNING: VERY EXPERIMENTAL. - * * Provide results that match the given text pattern. * @param query The parameters for this query. * @param options A set of options to consider while searching. From 8875f03ec8025890904defe3344c831aedaaeee7 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 30 Oct 2024 14:03:43 -0700 Subject: [PATCH 129/555] Remove code action contribution section (#232664) This was never fully implemented and I'm not sure we're going to move forward with the static contribution proposal --- .../typescript-language-features/package.json | 65 ------------------- .../package.nls.json | 20 ------ 2 files changed, 85 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 4815098a0f09e..1d0876b40ea90 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1777,71 +1777,6 @@ } } } - ], - "codeActions": [ - { - "languages": [ - "javascript", - "javascriptreact", - "typescript", - "typescriptreact" - ], - "actions": [ - { - "kind": "refactor.extract.constant", - "title": "%codeActions.refactor.extract.constant.title%", - "description": "%codeActions.refactor.extract.constant.description%" - }, - { - "kind": "refactor.extract.function", - "title": "%codeActions.refactor.extract.function.title%", - "description": "%codeActions.refactor.extract.function.description%" - }, - { - "kind": "refactor.extract.interface", - "title": "%codeActions.refactor.extract.interface.title%", - "description": "%codeActions.refactor.extract.interface.description%" - }, - { - "kind": "refactor.extract.type", - "title": "%codeActions.refactor.extract.type.title%", - "description": "%codeActions.refactor.extract.type.description%" - }, - { - "kind": "refactor.rewrite.import", - "title": "%codeActions.refactor.rewrite.import.title%", - "description": "%codeActions.refactor.rewrite.import.description%" - }, - { - "kind": "refactor.rewrite.export", - "title": "%codeActions.refactor.rewrite.export.title%", - "description": "%codeActions.refactor.rewrite.export.description%" - }, - { - "kind": "refactor.rewrite.arrow.braces", - "title": "%codeActions.refactor.rewrite.arrow.braces.title%", - "description": "%codeActions.refactor.rewrite.arrow.braces.description%" - }, - { - "kind": "refactor.rewrite.parameters.toDestructured", - "title": "%codeActions.refactor.rewrite.parameters.toDestructured.title%" - }, - { - "kind": "refactor.rewrite.property.generateAccessors", - "title": "%codeActions.refactor.rewrite.property.generateAccessors.title%", - "description": "%codeActions.refactor.rewrite.property.generateAccessors.description%" - }, - { - "kind": "refactor.move.newFile", - "title": "%codeActions.refactor.move.newFile.title%", - "description": "%codeActions.refactor.move.newFile.description%" - }, - { - "kind": "source.organizeImports", - "title": "%codeActions.source.organizeImports.title%" - } - ] - } ] }, "repository": { diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 2344f3dbc50bf..6c97bba9a6d64 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -208,26 +208,6 @@ "typescript.workspaceSymbols.scope": "Controls which files are searched by [Go to Symbol in Workspace](https://code.visualstudio.com/docs/editor/editingevolved#_open-symbol-by-name).", "typescript.workspaceSymbols.scope.allOpenProjects": "Search all open JavaScript or TypeScript projects for symbols.", "typescript.workspaceSymbols.scope.currentProject": "Only search for symbols in the current JavaScript or TypeScript project.", - "codeActions.refactor.extract.constant.title": "Extract constant", - "codeActions.refactor.extract.constant.description": "Extract expression to constant.", - "codeActions.refactor.extract.function.title": "Extract function", - "codeActions.refactor.extract.function.description": "Extract expression to method or function.", - "codeActions.refactor.extract.type.title": "Extract type", - "codeActions.refactor.extract.type.description": "Extract type to a type alias.", - "codeActions.refactor.extract.interface.title": "Extract interface", - "codeActions.refactor.extract.interface.description": "Extract type to an interface.", - "codeActions.refactor.rewrite.import.title": "Convert import", - "codeActions.refactor.rewrite.import.description": "Convert between named imports and namespace imports.", - "codeActions.refactor.rewrite.export.title": "Convert export", - "codeActions.refactor.rewrite.export.description": "Convert between default export and named export.", - "codeActions.refactor.move.newFile.title": "Move to a new file", - "codeActions.refactor.move.newFile.description": "Move the expression to a new file.", - "codeActions.refactor.rewrite.arrow.braces.title": "Rewrite arrow braces", - "codeActions.refactor.rewrite.arrow.braces.description": "Add or remove braces in an arrow function.", - "codeActions.refactor.rewrite.parameters.toDestructured.title": "Convert parameters to destructured object", - "codeActions.refactor.rewrite.property.generateAccessors.title": "Generate accessors", - "codeActions.refactor.rewrite.property.generateAccessors.description": "Generate 'get' and 'set' accessors", - "codeActions.source.organizeImports.title": "Organize Imports", "typescript.sortImports": "Sort Imports", "typescript.removeUnusedImports": "Remove Unused Imports", "typescript.findAllFileReferences": "Find File References", From d83223b7c2071e4600bc2157041058f70192883f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 30 Oct 2024 14:20:30 -0700 Subject: [PATCH 130/555] Try allowing custom commands to be sent to tsserver by extensions (#232663) For #218275 Limit this to commands that start with `_` which should indicate that it's a private command and prevent clashes with normal TS commands --- .../src/commands/tsserverRequests.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/src/commands/tsserverRequests.ts b/extensions/typescript-language-features/src/commands/tsserverRequests.ts index ba545dfbea9fe..9f79c1066c354 100644 --- a/extensions/typescript-language-features/src/commands/tsserverRequests.ts +++ b/extensions/typescript-language-features/src/commands/tsserverRequests.ts @@ -16,7 +16,7 @@ export class TSServerRequestCommand implements Command { private readonly lazyClientHost: Lazy ) { } - public execute(requestID: keyof TypeScriptRequests, args?: any, config?: any) { + public execute(command: keyof TypeScriptRequests, args?: any, config?: any) { // A cancellation token cannot be passed through the command infrastructure const token = nulToken; @@ -36,8 +36,11 @@ export class TSServerRequestCommand implements Command { 'completionInfo' ]; - if (!allowList.includes(requestID)) { return; } - return this.lazyClientHost.value.serviceClient.execute(requestID, args, token, config); + if (!allowList.includes(command) || command.startsWith('_')) { + return; + } + + return this.lazyClientHost.value.serviceClient.execute(command, args, token, config); } } From 6324f5f3ea7837b2df1187fd4c9f5a4c3203543d Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 30 Oct 2024 14:52:02 -0700 Subject: [PATCH 131/555] fix: don't truncate focused element border in chat editing widget (#232667) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index eb1555ca226f3..912438a4dba48 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -557,6 +557,7 @@ have to be updated for changes to the rules above, or to support more deeply nes white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + align-content: center; } .interactive-session .chat-editing-session .chat-editing-session-container .monaco-progress-container { @@ -572,6 +573,10 @@ have to be updated for changes to the rules above, or to support more deeply nes align-items: center; } +.interactive-session .chat-editing-session .chat-editing-session-toolbar-actions { + margin: 3px 0px; +} + .interactive-session .chat-editing-session .monaco-button { height: 17px; width: fit-content; From c7fe0659a04f95d4b12e7dbae7b648dba97dd807 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 30 Oct 2024 15:25:12 -0700 Subject: [PATCH 132/555] Add stub lmTools proposal to prevent old extensions from installing in 1.95 (#232670) --- .../platform/extensions/common/extensionsApiProposals.ts | 4 ++++ src/vscode-dts/vscode.proposed.lmTools.d.ts | 9 +++++++++ 2 files changed, 13 insertions(+) create mode 100644 src/vscode-dts/vscode.proposed.lmTools.d.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 421d3cb91d3f8..ef880b81e6dd6 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -235,6 +235,10 @@ const _allApiProposals = { languageStatusText: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatusText.d.ts', }, + lmTools: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.lmTools.d.ts', + version: 15 + }, mappedEditsProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', }, diff --git a/src/vscode-dts/vscode.proposed.lmTools.d.ts b/src/vscode-dts/vscode.proposed.lmTools.d.ts new file mode 100644 index 0000000000000..63084e91433a7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.lmTools.d.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// version: 15 + +declare module 'vscode' { +} From 95779b7b37d9c993b11cebe0971ca37b75309742 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Wed, 30 Oct 2024 15:28:59 -0700 Subject: [PATCH 133/555] add descriptions, refactor getPreferences function to be more clear --- .../typescript-language-features/package.json | 16 +++++++++++++++ .../package.nls.json | 14 +++++++++---- .../fileConfigurationManager.ts | 20 +++++++++++++------ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 4815098a0f09e..bc16920f83356 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1098,6 +1098,7 @@ "properties": { "caseSensitivity": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.caseSensitivity%", "enum": [ "auto", "caseInsensitive", @@ -1112,6 +1113,7 @@ }, "typeOrder": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.typeOrder%", "enum": [ "auto", "last", @@ -1128,6 +1130,7 @@ }, "unicodeCollation": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.unicodeCollation%", "enum": [ "ordinal", "unicode" @@ -1158,6 +1161,11 @@ "upper", "lower" ], + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.caseFirst.default%", + "%typescript.preferences.organizeImports.caseFirst.upper%", + "%typescript.preferences.organizeImports.caseFirst.lower%" + ], "default": "default" } } @@ -1168,6 +1176,7 @@ "properties": { "caseSensitivity": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.caseSensitivity%", "enum": [ "auto", "caseInsensitive", @@ -1182,6 +1191,7 @@ }, "typeOrder": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.typeOrder%", "enum": [ "auto", "last", @@ -1198,6 +1208,7 @@ }, "unicodeCollation": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.unicodeCollation%", "enum": [ "ordinal", "unicode" @@ -1228,6 +1239,11 @@ "upper", "lower" ], + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.caseFirst.default%", + "%typescript.preferences.organizeImports.caseFirst.upper%", + "%typescript.preferences.organizeImports.caseFirst.lower%" + ], "default": "default" } } diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 4027c8187d28e..7da31b7d186f0 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -192,19 +192,25 @@ "typescript.preferences.renameMatchingJsxTags": "When on a JSX tag, try to rename the matching tag instead of renaming the symbol. Requires using TypeScript 5.1+ in the workspace.", "typescript.preferences.organizeImports": "Advanced preferences that control how imports are ordered.", "javascript.preferences.organizeImports": "Advanced preferences that control how imports are ordered.", + "typescript.preferences.organizeImports.caseSensitivity": "Specifies how imports should be sorted with regards to case-sensitivity. If `auto` or unspecified, we will detect the case-sensitivity per file", "typescript.preferences.organizeImports.caseSensitivity.auto": "Detect case-sensitivity for import sorting.", "typescript.preferences.organizeImports.caseSensitivity.insensitive": "Sort imports case-insensitively.", "typescript.preferences.organizeImports.caseSensitivity.sensitive": "Sort imports case-sensitively.", + "typescript.preferences.organizeImports.typeOrder": "Specify how type-only named imports should be sorted.", "typescript.preferences.organizeImports.typeOrder.auto": "Detect where type-only named imports should be sorted.", "typescript.preferences.organizeImports.typeOrder.last": "Type only named imports are sorted to the end of the import list.", "typescript.preferences.organizeImports.typeOrder.inline": "Named imports are sorted by name only.", "typescript.preferences.organizeImports.typeOrder.first": "Type only named imports are sorted to the end of the import list.", + "typescript.preferences.organizeImports.unicodeCollation": "Specify whether to sort imports using Unicode or Ordinal collation.", "typescript.preferences.organizeImports.unicodeCollation.ordinal": "Sort imports using the numeric value of each code point.", "typescript.preferences.organizeImports.unicodeCollation.unicode": "Sort imports using the Unicode code collation.", - "typescript.preferences.organizeImports.locale": "Overrides the locale used for collation. Specify `auto` to use the UI locale. Only applies to `organizeImportsCollation: 'unicode'`.", - "typescript.preferences.organizeImports.caseFirst": "Indicates whether upper-case comes before lower-case. Only applies to `organizeImportsCollation: 'unicode'`.", - "typescript.preferences.organizeImports.numericCollation": "Sort numeric strings by integer value.", - "typescript.preferences.organizeImports.accentCollation": "Compare characters with diacritical marks as unequal to base character.", + "typescript.preferences.organizeImports.locale": "Requires `organizeImports.unicodeCollation: 'unicode'`. Overrides the locale used for collation. Specify `auto` to use the UI locale.", + "typescript.preferences.organizeImports.caseFirst": "Requires `organizeImports.unicodeCollation: 'unicode'`, and `organizeImports.caseSensitivity` is not `caseInsensitive`. Indicates whether upper-case will sort before lower-case.", + "typescript.preferences.organizeImports.caseFirst.default": "Default order given by `locale`.", + "typescript.preferences.organizeImports.caseFirst.lower": "Lower-case comes before upper-case. E.g.` a, A, z, Z`.", + "typescript.preferences.organizeImports.caseFirst.upper": "Upper-case comes before lower-case. E.g. ` A, a, B, b`.", + "typescript.preferences.organizeImports.numericCollation": "Requires `organizeImports.unicodeCollation: 'unicode'`. Sort numeric strings by integer value.", + "typescript.preferences.organizeImports.accentCollation": "Requires `organizeImports.unicodeCollation: 'unicode'`. Compare characters with diacritical marks as unequal to base character.", "typescript.workspaceSymbols.scope": "Controls which files are searched by [Go to Symbol in Workspace](https://code.visualstudio.com/docs/editor/editingevolved#_open-symbol-by-name).", "typescript.workspaceSymbols.scope.allOpenProjects": "Search all open JavaScript or TypeScript projects for symbols.", "typescript.workspaceSymbols.scope.currentProject": "Only search for symbols in the current JavaScript or TypeScript project.", diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index 5d92a3a6163e1..9b0c10a48b7f4 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -238,15 +238,23 @@ export default class FileConfigurationManager extends Disposable { } private getOrganizeImportsPreferences(config: vscode.WorkspaceConfiguration): Proto.UserPreferences { + const organizeImportsCollation = config.get<'ordinal' | 'unicode'>('organizeImports.unicodeCollation'); + const organizeImportsCaseSensitivity = config.get<'auto' | 'caseInsensitive' | 'caseSensitive'>('organizeImports.caseSensitivity'); return { // More specific settings - organizeImportsAccentCollation: config.get('organizeImports.accentCollation'), - organizeImportsCaseFirst: withDefaultAsUndefined(config.get<'default' | 'upper' | 'lower'>('organizeImports.caseFirst', 'default'), 'default'), - organizeImportsCollation: config.get<'ordinal' | 'unicode'>('organizeImports.collation'), - organizeImportsIgnoreCase: withDefaultAsUndefined(config.get<'auto' | 'caseInsensitive' | 'caseSensitive'>('organizeImports.caseSensitivity'), 'auto'), - organizeImportsLocale: config.get('organizeImports.locale'), - organizeImportsNumericCollation: config.get('organizeImports.numericCollation'), organizeImportsTypeOrder: withDefaultAsUndefined(config.get<'auto' | 'last' | 'inline' | 'first'>('organizeImports.typeOrder', 'auto'), 'auto'), + organizeImportsIgnoreCase: organizeImportsCaseSensitivity === 'caseInsensitive' ? true + : organizeImportsCaseSensitivity === 'caseSensitive' ? false + : 'auto', + organizeImportsCollation, + + // The rest of the settings are only applicable when using unicode collation + ...(organizeImportsCollation === 'unicode' ? { + organizeImportsCaseFirst: organizeImportsCaseSensitivity === 'caseInsensitive' ? undefined : withDefaultAsUndefined(config.get<'default' | 'upper' | 'lower' | false>('organizeImports.caseFirst', false), 'default'), + organizeImportsAccentCollation: config.get('organizeImports.accentCollation'), + organizeImportsLocale: config.get('organizeImports.locale'), + organizeImportsNumericCollation: config.get('organizeImports.numericCollation'), + } : {}), }; } } From c264ebed9425f3569ab5eb2c6fbc6a6bb4abaaaf Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 30 Oct 2024 15:39:34 -0700 Subject: [PATCH 134/555] Improve Secret Storage docs a bit (#232672) To call out that each platform will be different. --- src/vscode-dts/vscode.d.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 75c7d5d13b642..67a2c24b7f2af 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -8058,8 +8058,8 @@ declare module 'vscode' { }; /** - * A storage utility for secrets. Secrets are persisted across reloads and are independent of the - * current opened {@link workspace.workspaceFolders workspace}. + * A secret storage object that stores state independent + * of the current opened {@link workspace.workspaceFolders workspace}. */ readonly secrets: SecretStorage; @@ -8232,8 +8232,10 @@ declare module 'vscode' { } /** - * Represents a storage utility for secrets, information that is - * sensitive. + * Represents a storage utility for secrets (or any information that is sensitive) + * that will be stored encrypted. The implementation of the secret storage will + * be different on each platform and the secrets will not be synced across + * machines. */ export interface SecretStorage { /** From fbbcdd4f051b9d1c390140c594eef97143c4e194 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Wed, 30 Oct 2024 15:44:19 -0700 Subject: [PATCH 135/555] fix wrong description, add examples --- extensions/typescript-language-features/package.nls.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 7da31b7d186f0..834c0fac7c3ce 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -198,9 +198,9 @@ "typescript.preferences.organizeImports.caseSensitivity.sensitive": "Sort imports case-sensitively.", "typescript.preferences.organizeImports.typeOrder": "Specify how type-only named imports should be sorted.", "typescript.preferences.organizeImports.typeOrder.auto": "Detect where type-only named imports should be sorted.", - "typescript.preferences.organizeImports.typeOrder.last": "Type only named imports are sorted to the end of the import list.", - "typescript.preferences.organizeImports.typeOrder.inline": "Named imports are sorted by name only.", - "typescript.preferences.organizeImports.typeOrder.first": "Type only named imports are sorted to the end of the import list.", + "typescript.preferences.organizeImports.typeOrder.last": "Type only named imports are sorted to the end of the import list. E.g. `import { B, Z, type A, type Y } from 'module';`", + "typescript.preferences.organizeImports.typeOrder.inline": "Named imports are sorted by name only. E.g. `import { type A, B, type Y, Z } from 'module';`", + "typescript.preferences.organizeImports.typeOrder.first": "Type only named imports are sorted to the beginning of the import list. E.g. `import { type A, type Y, B, Z } from 'module';`", "typescript.preferences.organizeImports.unicodeCollation": "Specify whether to sort imports using Unicode or Ordinal collation.", "typescript.preferences.organizeImports.unicodeCollation.ordinal": "Sort imports using the numeric value of each code point.", "typescript.preferences.organizeImports.unicodeCollation.unicode": "Sort imports using the Unicode code collation.", From a0653eb347bc4cd6d496df73286cd7d6759a9d51 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 30 Oct 2024 16:05:12 -0700 Subject: [PATCH 136/555] fix: add padding to chat editing list entries (#232674) * fix: add padding to chat editing list entries * fix: center spinners vertically in chat editing response --- .../workbench/contrib/chat/browser/chatInputPart.ts | 4 +++- src/vs/workbench/contrib/chat/browser/media/chat.css | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 5c1b7d03df633..6568254875a3c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1039,6 +1039,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } // Working set + const workingSetContainer = innerContainer.querySelector('.chat-editing-session-list') as HTMLElement ?? dom.append(innerContainer, $('.chat-editing-session-list')); if (!this._chatEditList) { this._chatEditList = this._chatEditsListPool.get(); const list = this._chatEditList.object; @@ -1068,7 +1069,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._onDidFocus.fire(); } }, true)); - dom.append(innerContainer, list.getHTMLElement()); + dom.append(workingSetContainer, list.getHTMLElement()); + dom.append(innerContainer, workingSetContainer); } const maxItemsShown = 6; diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 912438a4dba48..8a658c366c057 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -310,6 +310,10 @@ top: 2px; } +.interactive-item-container .value .rendered-markdown .chat-codeblock-pill-widget .codicon { + top: 0px; +} + .interactive-item-container .value .rendered-markdown p { line-height: 1.5em; } @@ -1122,6 +1126,13 @@ have to be updated for changes to the rules above, or to support more deeply nes } } +.interactive-session .chat-editing-session-list { + + .monaco-icon-label { + padding: 0px 3px; + } +} + .interactive-item-container .chat-notification-widget { padding: 8px 12px; } From 7dd56c4ea570626a59ec4c12eb742e2ca0af97d0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 30 Oct 2024 16:45:58 -0700 Subject: [PATCH 137/555] Fix implicit context widget blinking (#232205) Debounce onDidChangeModel microsoft/vscode-copilot#9479 --- .../chat/browser/contrib/chatImplicitContext.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts index 9a1810ffa1a20..d6d035784c1c9 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts @@ -32,9 +32,13 @@ export class ChatImplicitContextContribution extends Disposable implements IWork activeEditorDisposables.clear(); const codeEditor = codeEditorService.getActiveCodeEditor(); if (codeEditor) { - activeEditorDisposables.add(codeEditor.onDidChangeModel(() => this.updateImplicitContext())); - activeEditorDisposables.add(Event.debounce(codeEditor.onDidChangeCursorSelection, () => undefined, 500)(() => this.updateImplicitContext())); - activeEditorDisposables.add(Event.debounce(codeEditor.onDidScrollChange, () => undefined, 500)(() => this.updateImplicitContext())); + activeEditorDisposables.add(Event.debounce( + Event.any( + codeEditor.onDidChangeModel, + codeEditor.onDidChangeCursorSelection, + codeEditor.onDidScrollChange), + () => undefined, + 500)(() => this.updateImplicitContext())); } this.updateImplicitContext(); From 17b7ccc2be28db3f5c79457b64bc1e14adafe5aa Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:47:21 +0000 Subject: [PATCH 138/555] fix url length issue for github issues (#232680) fix logged in github post req --- .../contrib/issue/electron-sandbox/issueReporterService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts index 5232574fab6ef..7857881fefcd5 100644 --- a/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts +++ b/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts @@ -173,15 +173,15 @@ export class IssueReporter extends BaseIssueReporterService { const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value, issueUrl); let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`; - if (url.length > MAX_URL_LENGTH) { + if (this.data.githubAccessToken && gitHubDetails) { + return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); + } else if (url.length > MAX_URL_LENGTH) { try { url = await this.writeToClipboard(baseUrl, issueBody); } catch (_) { console.error('Writing to clipboard failed'); return false; } - } else if (this.data.githubAccessToken && gitHubDetails) { - return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); } await this.nativeHostService.openExternal(url); From 53339705b042ff9283eb08be1f0b091e68efad4b Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 30 Oct 2024 17:30:17 -0700 Subject: [PATCH 139/555] fix: don't render attachment container in chat input for file entries in chat editing widget (#232681) --- .../workbench/contrib/chat/browser/chatInputPart.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 6568254875a3c..75fb31996ab1e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -719,8 +719,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.clearNode(container); const hoverDelegate = store.add(createInstantHoverDelegate()); - dom.setVisibility(Boolean(this.attachmentModel.size) || Boolean(this.implicitContext?.value), this.attachedContextContainer); - if (!this.attachmentModel.size) { + const attachments = this.location === ChatAgentLocation.EditingSession + ? [...this.attachmentModel.attachments.entries()].filter(([_, attachment]) => !attachment.isFile) + : [...this.attachmentModel.attachments.entries()]; + dom.setVisibility(Boolean(attachments.length) || Boolean(this.implicitContext?.value), this.attachedContextContainer); + if (!attachments.length) { this._indexOfLastAttachedContextDeletedWithKeyboard = -1; } @@ -730,11 +733,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } const attachmentInitPromises: Promise[] = []; - for (const [index, attachment] of this.attachmentModel.attachments.entries()) { - if (attachment.isFile && this.location === ChatAgentLocation.EditingSession) { - return; - } - + for (const [index, attachment] of attachments) { const widget = dom.append(container, $('.chat-attached-context-attachment.show-file-icons')); const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverride: widget }); From 94cc631aca2379c2b44eb7fd8b729c1e221a904f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 30 Oct 2024 17:46:48 -0700 Subject: [PATCH 140/555] Fix position symbol icon in chat anchors (#232683) Override value introduced in b6648af1eac7021cc646d1a9f59e93026a7c1689 --- .../contrib/chat/browser/media/chatInlineAnchorWidget.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/media/chatInlineAnchorWidget.css b/src/vs/workbench/contrib/chat/browser/media/chatInlineAnchorWidget.css index 370a96439b2c6..475155bed7d62 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatInlineAnchorWidget.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatInlineAnchorWidget.css @@ -31,6 +31,8 @@ line-height: 1em; font-size: 90% !important; overflow: hidden; + + top: 0 !important; } .show-file-icons.chat-inline-anchor-widget .icon::before { From 02a6d303d101d9e9a84486d75223d1b7d366733c Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 31 Oct 2024 17:57:37 +0900 Subject: [PATCH 141/555] chore: bump spdlog@0.15.1 (#232572) --- package-lock.json | 7 ++++--- remote/package-lock.json | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79141a89465b4..5a7e3112309e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2595,10 +2595,11 @@ } }, "node_modules/@vscode/spdlog": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.0.tgz", - "integrity": "sha512-5UFcQXM/G6bTRF49zJJJH3A3+47nxaXuKzT26vhTXVIiMFoV1oI9559mWOzapLEmvrntAdYtjE7Jh74lSAuMcA==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.1.tgz", + "integrity": "sha512-tVPeXmyz3/4NKqtNfNQxqcrBSSEZVIbF4lVDuDh9Nik5xhuHVceCU6cTpwmJ6yVBs+jv51SGF622txOQv95sZA==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "bindings": "^1.5.0", "mkdirp": "^1.0.4", diff --git a/remote/package-lock.json b/remote/package-lock.json index 934e61633b322..3b7aab914ebf5 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -165,10 +165,11 @@ } }, "node_modules/@vscode/spdlog": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.0.tgz", - "integrity": "sha512-5UFcQXM/G6bTRF49zJJJH3A3+47nxaXuKzT26vhTXVIiMFoV1oI9559mWOzapLEmvrntAdYtjE7Jh74lSAuMcA==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.1.tgz", + "integrity": "sha512-tVPeXmyz3/4NKqtNfNQxqcrBSSEZVIbF4lVDuDh9Nik5xhuHVceCU6cTpwmJ6yVBs+jv51SGF622txOQv95sZA==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "bindings": "^1.5.0", "mkdirp": "^1.0.4", From fb8d71703b33630a461c0ea78ef2029f6e5add65 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 31 Oct 2024 10:34:10 +0100 Subject: [PATCH 142/555] ContentHoverController refactoring (#232626) * wip * fixing compilation errors * refactoring * fixing hover not showing --- .../hover/browser/contentHoverController.ts | 143 ++++++++---------- 1 file changed, 64 insertions(+), 79 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHoverController.ts b/src/vs/editor/contrib/hover/browser/contentHoverController.ts index 01dffd9ce6771..21289d2c64a53 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverController.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverController.ts @@ -60,11 +60,9 @@ export class ContentHoverController extends Disposable implements IEditorContrib @IKeybindingService private readonly _keybindingService: IKeybindingService ) { super(); - this._reactToEditorMouseMoveRunner = this._register( - new RunOnceScheduler( - () => this._reactToEditorMouseMove(this._mouseMoveEvent), 0 - ) - ); + this._reactToEditorMouseMoveRunner = this._register(new RunOnceScheduler( + () => this._reactToEditorMouseMove(this._mouseMoveEvent), 0 + )); this._hookListeners(); this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { if (e.hasChanged(EditorOption.hover)) { @@ -79,14 +77,12 @@ export class ContentHoverController extends Disposable implements IEditorContrib } private _hookListeners(): void { - const hoverOpts = this._editor.getOption(EditorOption.hover); this._hoverSettings = { enabled: hoverOpts.enabled, sticky: hoverOpts.sticky, hidingDelay: hoverOpts.hidingDelay }; - if (hoverOpts.enabled) { this._listenersStore.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); this._listenersStore.add(this._editor.onMouseUp(() => this._onEditorMouseUp())); @@ -96,11 +92,10 @@ export class ContentHoverController extends Disposable implements IEditorContrib this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); } - this._listenersStore.add(this._editor.onMouseLeave((e) => this._onEditorMouseLeave(e))); this._listenersStore.add(this._editor.onDidChangeModel(() => { this._cancelScheduler(); - this._hideWidgets(); + this.hideContentHover(); })); this._listenersStore.add(this._editor.onDidChangeModelContent(() => this._cancelScheduler())); this._listenersStore.add(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e))); @@ -117,32 +112,28 @@ export class ContentHoverController extends Disposable implements IEditorContrib private _onEditorScrollChanged(e: IScrollEvent): void { if (e.scrollTopChanged || e.scrollLeftChanged) { - this._hideWidgets(); + this.hideContentHover(); } } private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { - this._isMouseDown = true; - - const shouldNotHideCurrentHoverWidget = this._shouldNotHideCurrentHoverWidget(mouseEvent); - if (shouldNotHideCurrentHoverWidget) { + const shouldKeepHoverWidgetVisible = this._shouldKeepHoverWidgetVisible(mouseEvent); + if (shouldKeepHoverWidgetVisible) { return; } - - this._hideWidgets(); + this.hideContentHover(); } - private _shouldNotHideCurrentHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { + private _shouldKeepHoverWidgetVisible(mouseEvent: IPartialEditorMouseEvent): boolean { return this._isMouseOnContentHoverWidget(mouseEvent) || this._isContentWidgetResizing() || isOnColorDecorator(mouseEvent); } private _isMouseOnContentHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { - const contentWidgetNode = this._contentWidget?.getDomNode(); - if (contentWidgetNode) { - return isMousePositionWithinElement(contentWidgetNode, mouseEvent.event.posx, mouseEvent.event.posy); + if (!this._contentWidget) { + return false; } - return false; + return isMousePositionWithinElement(this._contentWidget.getDomNode(), mouseEvent.event.posx, mouseEvent.event.posy); } private _onEditorMouseUp(): void { @@ -153,17 +144,15 @@ export class ContentHoverController extends Disposable implements IEditorContrib if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) { return; } - this._cancelScheduler(); - - const shouldNotHideCurrentHoverWidget = this._shouldNotHideCurrentHoverWidget(mouseEvent); - if (shouldNotHideCurrentHoverWidget) { + const shouldKeepHoverWidgetVisible = this._shouldKeepHoverWidgetVisible(mouseEvent); + if (shouldKeepHoverWidgetVisible) { return; } if (_sticky) { return; } - this._hideWidgets(); + this.hideContentHover(); } private _shouldNotRecomputeCurrentHoverWidget(mouseEvent: IEditorMouseEvent): boolean { @@ -185,51 +174,57 @@ export class ContentHoverController extends Disposable implements IEditorContrib && this._contentWidget?.containsNode(mouseEvent.event.browserEvent.view?.document.activeElement) && !mouseEvent.event.browserEvent.view?.getSelection()?.isCollapsed) ?? false; }; - return isMouseOnStickyContentHoverWidget(mouseEvent, isHoverSticky) || isMouseOnColorPicker(mouseEvent) || isTextSelectedWithinContentHoverWidget(mouseEvent, isHoverSticky); } private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { - if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) { + const shouldReactToEditorMouseMove = this._shouldReactToEditorMouseMove(mouseEvent); + if (!shouldReactToEditorMouseMove) { + return; + } + const shouldRescheduleHoverComputation = this._shouldRescheduleHoverComputation(); + if (shouldRescheduleHoverComputation) { + if (!this._reactToEditorMouseMoveRunner.isScheduled()) { + this._reactToEditorMouseMoveRunner.schedule(this._hoverSettings.hidingDelay); + } return; } + this._reactToEditorMouseMove(mouseEvent); + } + private _shouldReactToEditorMouseMove(mouseEvent: IEditorMouseEvent): boolean { + if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) { + return false; + } this._mouseMoveEvent = mouseEvent; - if (this._contentWidget?.isFocused || this._contentWidget?.isResizing) { - return; + if (this._contentWidget && (this._contentWidget.isFocused || this._contentWidget.isResizing || this._isMouseDown && this._contentWidget.isColorPickerVisible)) { + return false; } const sticky = this._hoverSettings.sticky; if (sticky && this._contentWidget?.isVisibleFromKeyboard) { // Sticky mode is on and the hover has been shown via keyboard // so moving the mouse has no effect - return; + return false; } - const shouldNotRecomputeCurrentHoverWidget = this._shouldNotRecomputeCurrentHoverWidget(mouseEvent); if (shouldNotRecomputeCurrentHoverWidget) { this._reactToEditorMouseMoveRunner.cancel(); - return; + return false; } + return true; + } + private _shouldRescheduleHoverComputation(): boolean { const hidingDelay = this._hoverSettings.hidingDelay; - const isContentHoverWidgetVisible = this._contentWidget?.isVisible; + const isContentHoverWidgetVisible = this._contentWidget?.isVisible ?? false; // If the mouse is not over the widget, and if sticky is on, // then give it a grace period before reacting to the mouse event - const shouldRescheduleHoverComputation = isContentHoverWidgetVisible && sticky && hidingDelay > 0; - - if (shouldRescheduleHoverComputation) { - if (!this._reactToEditorMouseMoveRunner.isScheduled()) { - this._reactToEditorMouseMoveRunner.schedule(hidingDelay); - } - return; - } - this._reactToEditorMouseMove(mouseEvent); + return isContentHoverWidgetVisible && this._hoverSettings.sticky && hidingDelay > 0; } private _reactToEditorMouseMove(mouseEvent: IEditorMouseEvent | undefined): void { - if (!mouseEvent) { return; } @@ -237,56 +232,50 @@ export class ContentHoverController extends Disposable implements IEditorContrib if (contentWidget.showsOrWillShow(mouseEvent)) { return; } - if (_sticky) { return; } - this._hideWidgets(); + this.hideContentHover(); } - private _onKeyDown(e: IKeyboardEvent): void { if (!this._editor.hasModel()) { return; } + const isPotentialKeyboardShortcut = this._isPotentialKeyboardShortcut(e); + const isModifierKeyPressed = this._isModifierKeyPressed(e); + if (isPotentialKeyboardShortcut || isModifierKeyPressed) { + return; + } + this.hideContentHover(); + } + private _isPotentialKeyboardShortcut(e: IKeyboardEvent): boolean { + if (!this._editor.hasModel() || !this._contentWidget) { + return false; + } const resolvedKeyboardEvent = this._keybindingService.softDispatch(e, this._editor.getDomNode()); + const moreChordsAreNeeded = resolvedKeyboardEvent.kind === ResultKind.MoreChordsNeeded; + const isHoverAction = resolvedKeyboardEvent.kind === ResultKind.KbFound + && (resolvedKeyboardEvent.commandId === SHOW_OR_FOCUS_HOVER_ACTION_ID + || resolvedKeyboardEvent.commandId === INCREASE_HOVER_VERBOSITY_ACTION_ID + || resolvedKeyboardEvent.commandId === DECREASE_HOVER_VERBOSITY_ACTION_ID) + && this._contentWidget.isVisible; + return moreChordsAreNeeded || isHoverAction; + } - // If the beginning of a multi-chord keybinding is pressed, - // or the command aims to focus the hover, - // set the variable to true, otherwise false - const shouldKeepHoverVisible = ( - resolvedKeyboardEvent.kind === ResultKind.MoreChordsNeeded || - (resolvedKeyboardEvent.kind === ResultKind.KbFound - && (resolvedKeyboardEvent.commandId === SHOW_OR_FOCUS_HOVER_ACTION_ID - || resolvedKeyboardEvent.commandId === INCREASE_HOVER_VERBOSITY_ACTION_ID - || resolvedKeyboardEvent.commandId === DECREASE_HOVER_VERBOSITY_ACTION_ID) - && this._contentWidget?.isVisible - ) - ); - - if ( - e.keyCode === KeyCode.Ctrl + private _isModifierKeyPressed(e: IKeyboardEvent): boolean { + return e.keyCode === KeyCode.Ctrl || e.keyCode === KeyCode.Alt || e.keyCode === KeyCode.Meta - || e.keyCode === KeyCode.Shift - || shouldKeepHoverVisible - ) { - // Do not hide hover when a modifier key is pressed - return; - } - - this._hideWidgets(); + || e.keyCode === KeyCode.Shift; } - private _hideWidgets(): void { + public hideContentHover(): void { if (_sticky) { return; } - if (( - this._isMouseDown - && this._contentWidget?.isColorPickerVisible - ) || InlineSuggestionHintsContentWidget.dropDownVisible) { + if (InlineSuggestionHintsContentWidget.dropDownVisible) { return; } this._contentWidget?.hide(); @@ -300,10 +289,6 @@ export class ContentHoverController extends Disposable implements IEditorContrib return this._contentWidget; } - public hideContentHover(): void { - this._hideWidgets(); - } - public showContentHover( range: Range, mode: HoverStartMode, From 38a37991730cb3961ab80e3b9b2653977cd48b20 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Thu, 31 Oct 2024 09:52:42 +0100 Subject: [PATCH 143/555] Listen on submenu changes --- src/vs/workbench/contrib/comments/browser/commentMenus.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts index 1898237851ce9..504739f7db1e6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentMenus.ts +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -5,7 +5,7 @@ import { IDisposable } from '../../../../base/common/lifecycle.js'; import { Comment } from '../../../../editor/common/languages.js'; -import { IMenu, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; +import { IMenu, IMenuCreateOptions, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; export class CommentMenus implements IDisposable { @@ -26,7 +26,7 @@ export class CommentMenus implements IDisposable { } getCommentThreadAdditionalActions(contextKeyService: IContextKeyService): IMenu { - return this.getMenu(MenuId.CommentThreadAdditionalActions, contextKeyService); + return this.getMenu(MenuId.CommentThreadAdditionalActions, contextKeyService, { emitEventsForSubmenuChanges: true }); } getCommentTitleActions(comment: Comment, contextKeyService: IContextKeyService): IMenu { @@ -41,8 +41,8 @@ export class CommentMenus implements IDisposable { return this.getMenu(MenuId.CommentThreadTitleContext, contextKeyService); } - private getMenu(menuId: MenuId, contextKeyService: IContextKeyService): IMenu { - return this.menuService.createMenu(menuId, contextKeyService); + private getMenu(menuId: MenuId, contextKeyService: IContextKeyService, options?: IMenuCreateOptions): IMenu { + return this.menuService.createMenu(menuId, contextKeyService, options); } dispose(): void { From bea51cd8de36f1d577e6ba436da5c0f5b95fbc2c Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 31 Oct 2024 11:23:38 +0100 Subject: [PATCH 144/555] Find all reference panel empty list (#232720) Fixes #232709 --- src/vs/workbench/browser/parts/views/treeView.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 28a0846356f3e..08927639e7627 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -375,7 +375,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { private updateEmptyState(nodes: ITreeItem[], childrenGroups: ITreeItem[][]): void { if ((nodes.length === 1) && (nodes[0] instanceof Root)) { const oldEmpty = this._isEmpty; - this._isEmpty = childrenGroups[0].length === 0; + this._isEmpty = (childrenGroups.length === 0) || (childrenGroups[0].length === 0); if (oldEmpty !== this._isEmpty) { this._onDidChangeEmpty.fire(); } @@ -383,6 +383,9 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { } private findCheckboxesUpdated(nodes: ITreeItem[], childrenGroups: ITreeItem[][]): ITreeItem[] { + if (childrenGroups.length === 0) { + return []; + } const checkboxesUpdated: ITreeItem[] = []; for (let i = 0; i < nodes.length; i++) { @@ -1211,7 +1214,7 @@ class TreeDataSource implements IAsyncDataSource { } try { const result = await this.batchPromise; - resolve(result ? result[indexInBatch] : []); + resolve((result && (indexInBatch < result.length)) ? result[indexInBatch] : []); } catch (e) { if (!(e.message).startsWith('Bad progress location:')) { reject(e); From 54d1a4d6f395e73204ce0b5999439b267aec3fef Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 31 Oct 2024 11:23:54 +0100 Subject: [PATCH 145/555] add more logs (#232718) --- .../browser/userDataSyncWorkbenchService.ts | 24 +++++++++---------- .../userDataSync/common/userDataSync.ts | 1 + 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index e4646e84ad18e..bdd819f1d180f 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -151,11 +151,11 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } private async waitAndInitialize(): Promise { - /* wait */ - await Promise.all([this.extensionService.whenInstalledExtensionsRegistered(), this.userDataInitializationService.whenInitializationFinished()]); - - /* initialize */ try { + /* wait */ + await Promise.all([this.extensionService.whenInstalledExtensionsRegistered(), this.userDataInitializationService.whenInitializationFinished()]); + + /* initialize */ await this.initialize(); } catch (error) { // Do not log if the current window is running extension tests @@ -181,7 +181,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } } - await this.update(); + await this.update('initialize'); this._register(this.authenticationService.onDidChangeDeclaredProviders(() => this.updateAuthenticationProviders())); @@ -189,7 +189,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat Event.any( this.authenticationService.onDidRegisterAuthenticationProvider, this.authenticationService.onDidUnregisterAuthenticationProvider, - ), info => this.isSupportedAuthenticationProviderId(info.id))(() => this.update())); + ), info => this.isSupportedAuthenticationProviderId(info.id))(() => this.update('authentication provider change'))); this._register(Event.filter(this.userDataSyncAccountService.onTokenFailed, isSuccessive => !isSuccessive)(() => this.update('token failure'))); @@ -213,11 +213,8 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat })); } - private async update(reason?: string): Promise { - - if (reason) { - this.logService.info(`Settings Sync: Updating due to ${reason}`); - } + private async update(reason: string): Promise { + this.logService.info(`Settings Sync: Updating due to ${reason}`); this.updateAuthenticationProviders(); await this.updateCurrentAccount(); @@ -548,6 +545,9 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat if (authenticationProvider) { await this.doSignIn(authenticationProvider); } else { + if (!this.authenticationProviders.length) { + throw new Error(localize('no authentication providers during signin', "Cannot sign in because there are no authentication providers available.")); + } await this.pick(); } } @@ -688,7 +688,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat this.currentAuthenticationProviderId = accountOrAuthProvider.authenticationProviderId; } this.currentSessionId = sessionId; - await this.update(); + await this.update('sign in'); } private async onDidAuthFailure(): Promise { diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index c3e5e5fa0dd7f..d0b966f4d0f80 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -67,6 +67,7 @@ export function getSyncAreaLabel(source: SyncResource): string { } export const enum AccountStatus { + Uninitialized = 'uninitialized', Unavailable = 'unavailable', Available = 'available', } From b62b4161a0b5968f58242e602de2279230cf485b Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 31 Oct 2024 19:27:30 +0900 Subject: [PATCH 146/555] fix: font config detection for snap (#232713) --- resources/linux/snap/electron-launch | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/resources/linux/snap/electron-launch b/resources/linux/snap/electron-launch index cf9e946360c2a..bbd8e76588e11 100755 --- a/resources/linux/snap/electron-launch +++ b/resources/linux/snap/electron-launch @@ -96,6 +96,7 @@ function can_open_file() { # Preserve system variables that get modified below copy_env_variable XDG_CONFIG_DIRS copy_env_variable XDG_DATA_DIRS +copy_env_variable XDG_DATA_HOME copy_env_variable LOCPATH copy_env_variable GIO_MODULE_DIR copy_env_variable GSETTINGS_SCHEMA_DIR @@ -167,6 +168,18 @@ fi # Keep an array of data dirs, for looping through them IFS=':' read -r -a data_dirs_array <<< "$XDG_DATA_DIRS" +# Font Config +export FONTCONFIG_PATH="/etc/fonts" +export FONTCONFIG_FILE="/etc/fonts/fonts.conf" + +if [ "$needs_update" = true ]; then + rm -rf "$XDG_DATA_HOME"/fonts + + if [ -d "$SNAP_REAL_HOME/.local/share/fonts" ]; then + ln -s "$SNAP_REAL_HOME/.local/share/fonts" "$XDG_DATA_HOME/fonts" + fi +fi + # Build mime.cache needed for gtk and qt icon # TODO(deepak1556): Re-enable this once we move to core22 # Refs https://github.com/microsoft/vscode/issues/230454#issuecomment-2418352959 From 162f7faca4a52a9addbcb579a2d96904a32c9d08 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 03:54:56 -0700 Subject: [PATCH 147/555] Use string only for hover ids --- src/vs/base/browser/ui/hover/hover.ts | 4 ++-- src/vs/base/browser/ui/hover/hoverDelegate.ts | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 86aac35e1c02d..08db44085612c 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -180,7 +180,7 @@ export interface IHoverOptions { * {@link IHoverService.showHover} the options object itself is used to determine if the hover * is the same one that is already showing, when this is set, the ID will be used instead. */ - id?: number | string; + id?: string; /** * A set of actions for the hover's "status bar". @@ -234,7 +234,7 @@ export interface IHoverLifecycleOptions { * The group ID of the hover. If the group ID is the same as the currently shown hover, the * hover will be shown immediately, skipping the delay. */ - groupId?: number | string; + groupId?: string; /** * Whether to set up space and enter keyboard events for the hover, when these are pressed when diff --git a/src/vs/base/browser/ui/hover/hoverDelegate.ts b/src/vs/base/browser/ui/hover/hoverDelegate.ts index 534636cf8e7c4..31ec93f727ae1 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate.ts @@ -41,8 +41,6 @@ export interface IHoverDelegateOptions extends IManagedHoverOptions { * Position of the hover. The default is to show above the target. This option will be ignored * if there is not enough room to layout the hover in the specified position, unless the * forcePosition option is set. - * - * The value 'mouse' */ hoverPosition?: HoverPosition; }; @@ -67,7 +65,7 @@ export interface IHoverDelegateOptions extends IManagedHoverOptions { } export interface IHoverDelegate { - showHover(options: IHoverDelegateOptions, focus?: boolean): Promise | IHoverWidget | undefined; + showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined; onDidHideHover?: () => void; delay: number; placement?: 'mouse' | 'element'; From 31bd0e48fa8bf6a610ef578c41550e76be81ef56 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 03:59:16 -0700 Subject: [PATCH 148/555] Add usage examples for groupId --- src/vs/base/browser/ui/hover/hover.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 08db44085612c..fae2f5fd030f9 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -233,6 +233,21 @@ export interface IHoverLifecycleOptions { /** * The group ID of the hover. If the group ID is the same as the currently shown hover, the * hover will be shown immediately, skipping the delay. + * + * @example Use a UUID to set a unique `groupId` for related hovers + * + * ```typescript + * const groupId = generateUuid(); + * showDelayedHover({ content: 'Button 1', target: someElement1 }, { groupId }); + * showDelayedHover({ content: 'Button 2', target: someElement2 }, { groupId }); + * ``` + * + * @example Use a feature-specific string to set a unqiue `groupId` for related hovers + * + * ```typescript + * showDelayedHover({ content: 'Button 1', target: someElement1 }, { groupId: 'my-feature-items' }); + * showDelayedHover({ content: 'Button 2', target: someElement2 }, { groupId: 'my-feature-items' }); + * ``` */ groupId?: string; From ca0d3cf202ad5df6a64a7910ad226fadab7d73c3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:09:33 -0700 Subject: [PATCH 149/555] Clean up --- src/vs/base/browser/ui/hover/hover.ts | 7 +++---- src/vs/base/browser/ui/hover/hoverWidget.ts | 1 - src/vs/editor/browser/services/hoverService/hoverWidget.ts | 6 +----- .../browser/services/hoverService/updatableHoverWidget.ts | 4 ++-- src/vs/platform/quickinput/browser/quickInputTree.ts | 4 ++-- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index fae2f5fd030f9..1efb81d67d027 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -128,10 +128,6 @@ export interface IHoverDelegate2 { * @deprecated Use {@link setupDelayedHover} or {@link setupDelayedHoverAtMouse} instead where * possible. */ - // TODO: The hoverDelegate parameter should be removed in favor of just a set of options. This - // will avoid confusion around IHoverDelegate/IHoverDelegate2 as well as align more with - // the design of the hover service. - // TODO: Align prototype closer to showHover, deriving options from IHoverOptions if possible. setupManagedHover(hoverDelegate: IHoverDelegate, targetElement: HTMLElement, content: IManagedHoverContentOrFactory, options?: IManagedHoverOptions): IManagedHover; /** @@ -254,6 +250,9 @@ export interface IHoverLifecycleOptions { /** * Whether to set up space and enter keyboard events for the hover, when these are pressed when * the hover's target is focused it will show and focus the hover. + * + * Typically this should _not_ be used when the space or enter events are already handled by + * something else. */ setupKeyboardEvents?: boolean; } diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index 67f6b052732f2..27cc838e0b6e6 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -18,7 +18,6 @@ export const enum HoverPosition { RIGHT, BELOW, ABOVE, - MOUSE, } export class HoverWidget extends Disposable { diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 3260763397686..79df4ca967aee 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -117,11 +117,6 @@ export class HoverWidget extends Widget implements IHoverWidget { if (options.appearance?.compact) { this._hover.containerDomNode.classList.add('workbench-hover', 'compact'); } - // if (options.appearance?.skipFadeInAnimation) { - // this._hover.containerDomNode.classList.add('skip-fade-in'); - // } else { - // this._hover.containerDomNode.classList.add('fade-in'); - // } if (options.additionalClasses) { this._hover.containerDomNode.classList.add(...options.additionalClasses); } @@ -132,6 +127,7 @@ export class HoverWidget extends Widget implements IHoverWidget { this._enableFocusTraps = true; } + // Default to position above when the position is unspecified or a mouse event this._hoverPosition = options.position?.hoverPosition === undefined || !isNumber(options.position.hoverPosition) ? HoverPosition.ABOVE : options.position.hoverPosition; // Don't allow mousedown out of the widget, otherwise preventDefault will call and text will diff --git a/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts b/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts index 542d00e8e837f..c5b656b99d363 100644 --- a/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts @@ -64,7 +64,7 @@ export class ManagedHoverWidget implements IDisposable { this.show(resolvedContent, focus, options); } - private async show(content: IManagedHoverResolvedContent, focus?: boolean, options?: IManagedHoverOptions): Promise { + private show(content: IManagedHoverResolvedContent, focus?: boolean, options?: IManagedHoverOptions): void { const oldHoverWidget = this._hoverWidget; if (this.hasContent(content)) { @@ -84,7 +84,7 @@ export class ManagedHoverWidget implements IDisposable { }, }; - this._hoverWidget = await this.hoverDelegate.showHover(hoverOptions, focus); + this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus); } oldHoverWidget?.dispose(); } diff --git a/src/vs/platform/quickinput/browser/quickInputTree.ts b/src/vs/platform/quickinput/browser/quickInputTree.ts index eeee622609444..3203be9e53570 100644 --- a/src/vs/platform/quickinput/browser/quickInputTree.ts +++ b/src/vs/platform/quickinput/browser/quickInputTree.ts @@ -1544,7 +1544,7 @@ export class QuickInputTree extends Disposable { * Disposes of the hover and shows a new one for the given index if it has a tooltip. * @param element The element to show the hover for */ - private async showHover(element: QuickPickItemElement): Promise { + private showHover(element: QuickPickItemElement): void { if (this._lastHover && !this._lastHover.isDisposed) { this.hoverDelegate.onDidHideHover?.(); this._lastHover?.dispose(); @@ -1553,7 +1553,7 @@ export class QuickInputTree extends Disposable { if (!element.element || !element.saneTooltip) { return; } - this._lastHover = await this.hoverDelegate.showHover({ + this._lastHover = this.hoverDelegate.showHover({ content: element.saneTooltip, target: element.element, linkHandler: (url) => { From 9d8fabc1b93e54398868c1d7dfb2e6c24522f797 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:26:57 -0700 Subject: [PATCH 150/555] Bring default id logic into hover service --- src/vs/base/browser/ui/hover/hover.ts | 3 +++ .../browser/services/hoverService/hoverService.ts | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 1efb81d67d027..3c533f37a771d 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -175,6 +175,9 @@ export interface IHoverOptions { * An ID to associate with the hover to be used as an equality check. Normally when calling * {@link IHoverService.showHover} the options object itself is used to determine if the hover * is the same one that is already showing, when this is set, the ID will be used instead. + * + * When `undefined`, this will default to a serialized version of {@link content}. In this case + * it will remain `undefined` if {@link content} is a `HTMLElement`. */ id?: string; diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index c5877970ab53d..b4164c04d1d9f 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -186,6 +186,16 @@ export class HoverService extends Disposable implements IHoverService { this._lastFocusedElementBeforeOpen = undefined; } } + + // Set `id` to default if it's undefined + if (options.id === undefined) { + options.id = isHTMLElement(options.content) + ? undefined + : typeof options.content === 'string' + ? options.content.toString() + : options.content.value; + } + const hoverDisposables = new DisposableStore(); const hover = this._instantiationService.createInstance(HoverWidget, options); if (options.persistence?.sticky) { From 77a3ae2145d95ed093e4b9fdcdb9e7a5bc1fa8af Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:28:13 -0700 Subject: [PATCH 151/555] Move renameWidget to use new hover API --- .../contrib/rename/browser/renameWidget.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index f9294c16cdaea..3b1764c1df92c 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -6,9 +6,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import * as aria from '../../../../base/browser/ui/aria/aria.js'; -import { IManagedHover } from '../../../../base/browser/ui/hover/hover.js'; import { getBaseLayerHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate2.js'; -import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IListRenderer, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; import { List } from '../../../../base/browser/ui/list/listWidget.js'; @@ -900,7 +898,7 @@ class InputWithButton implements IDisposable { private _domNode: HTMLDivElement | undefined; private _inputNode: HTMLInputElement | undefined; private _buttonNode: HTMLElement | undefined; - private _buttonHover: IManagedHover | undefined; + private _buttonHoverContent: string = ''; private _buttonGenHoverText: string | undefined; private _buttonCancelHoverText: string | undefined; private _sparkleIcon: HTMLElement | undefined; @@ -934,8 +932,13 @@ class InputWithButton implements IDisposable { this._buttonGenHoverText = nls.localize('generateRenameSuggestionsButton', "Generate new name suggestions"); this._buttonCancelHoverText = nls.localize('cancelRenameSuggestionsButton', "Cancel"); - this._buttonHover = getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('element'), this._buttonNode, this._buttonGenHoverText); - this._disposables.add(this._buttonHover); + this._buttonHoverContent = this._buttonGenHoverText; + this._disposables.add(getBaseLayerHoverDelegate().setupDelayedHover(this._buttonNode, () => ({ + content: this._buttonHoverContent, + appearance: { + showPointer: true + } + }))); this._domNode.appendChild(this._buttonNode); @@ -985,7 +988,7 @@ class InputWithButton implements IDisposable { dom.clearNode(this.button); this.button.appendChild(this._sparkleIcon); this.button.setAttribute('aria-label', 'Generating new name suggestions'); - this._buttonHover?.update(this._buttonGenHoverText); + this._buttonHoverContent = this._buttonGenHoverText!; this.input.focus(); } @@ -995,7 +998,7 @@ class InputWithButton implements IDisposable { dom.clearNode(this.button); this.button.appendChild(this._stopIcon); this.button.setAttribute('aria-label', 'Cancel generating new name suggestions'); - this._buttonHover?.update(this._buttonCancelHoverText); + this._buttonHoverContent = this._buttonCancelHoverText!; this.input.focus(); } From ccd6c311c066f673fae6a317cc8e818ac916a855 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:31:14 -0700 Subject: [PATCH 152/555] Disallow setting showPointer in mouse API --- src/vs/base/browser/ui/hover/hover.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 3c533f37a771d..60a8023bb2eb0 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -226,7 +226,8 @@ export interface IHoverOptions { export type IDelayedHoverOptions = Omit; // `position` is ignored for delayed at mouse hover methods as it's overwritten by the mouse event. -export type IDelayedHoverAtMouseOptions = Omit; +// `showPointer` is always false when using mouse positioning +export type IDelayedHoverAtMouseOptions = Omit & Omit; export interface IHoverLifecycleOptions { /** From ee2beb56b7500ecf18e675a83d6781b46fc537d7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:37:25 -0700 Subject: [PATCH 153/555] Move InputBox to new hover API --- src/vs/base/browser/ui/inputbox/inputBox.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 3e0eedcef9d1d..1721a89b5432a 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -12,9 +12,7 @@ import { MarkdownRenderOptions } from '../../markdownRenderer.js'; import { ActionBar } from '../actionbar/actionbar.js'; import * as aria from '../aria/aria.js'; import { AnchorAlignment, IContextViewProvider } from '../contextview/contextview.js'; -import type { IManagedHover } from '../hover/hover.js'; import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; -import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { ScrollableElement } from '../scrollbar/scrollableElement.js'; import { Widget } from '../widget.js'; import { IAction } from '../../../common/actions.js'; @@ -24,6 +22,7 @@ import { equals } from '../../../common/objects.js'; import { ScrollbarVisibility } from '../../../common/scrollable.js'; import './inputBox.css'; import * as nls from '../../../../nls.js'; +import { MutableDisposable, type IDisposable } from '../../../common/lifecycle.js'; const $ = dom.$; @@ -115,7 +114,7 @@ export class InputBox extends Widget { private cachedContentHeight: number | undefined; private maxHeight: number = Number.POSITIVE_INFINITY; private scrollableElement: ScrollableElement | undefined; - private hover: IManagedHover | undefined; + private readonly hover: MutableDisposable = this._register(new MutableDisposable()); private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; @@ -235,10 +234,10 @@ export class InputBox extends Widget { public setTooltip(tooltip: string): void { this.tooltip = tooltip; - if (!this.hover) { - this.hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.input, tooltip)); - } else { - this.hover.update(tooltip); + if (!this.hover.value) { + this.hover.value = this._register(getBaseLayerHoverDelegate().setupDelayedHoverAtMouse(this.input, () => ({ + content: tooltip + }))); } } From 59fa9a79c77e936464b751e125a4b653bdaff0d1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:40:43 -0700 Subject: [PATCH 154/555] Fix type --- src/vs/base/browser/ui/hover/hover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 60a8023bb2eb0..8f47dd86d1b6f 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -227,7 +227,7 @@ export type IDelayedHoverOptions = Omit; // `position` is ignored for delayed at mouse hover methods as it's overwritten by the mouse event. // `showPointer` is always false when using mouse positioning -export type IDelayedHoverAtMouseOptions = Omit & Omit; +export type IDelayedHoverAtMouseOptions = Omit & { appearance: Omit }; export interface IHoverLifecycleOptions { /** From 7fd747a612ee2a1a2bc9bad69e6ef42e137cef92 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:41:44 -0700 Subject: [PATCH 155/555] Move input box and rename widget back to compact hover --- src/vs/base/browser/ui/inputbox/inputBox.ts | 5 ++++- src/vs/editor/browser/services/hoverService/hoverWidget.ts | 6 +++++- src/vs/editor/contrib/rename/browser/renameWidget.ts | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 1721a89b5432a..97262fce4117e 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -236,7 +236,10 @@ export class InputBox extends Widget { this.tooltip = tooltip; if (!this.hover.value) { this.hover.value = this._register(getBaseLayerHoverDelegate().setupDelayedHoverAtMouse(this.input, () => ({ - content: tooltip + content: tooltip, + appearance: { + compact: true, + } }))); } } diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 79df4ca967aee..7a51066caeca9 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -128,7 +128,11 @@ export class HoverWidget extends Widget implements IHoverWidget { } // Default to position above when the position is unspecified or a mouse event - this._hoverPosition = options.position?.hoverPosition === undefined || !isNumber(options.position.hoverPosition) ? HoverPosition.ABOVE : options.position.hoverPosition; + this._hoverPosition = options.position?.hoverPosition === undefined + ? HoverPosition.ABOVE + : isNumber(options.position.hoverPosition) + ? options.position.hoverPosition + : HoverPosition.BELOW; // Don't allow mousedown out of the widget, otherwise preventDefault will call and text will // not be selected. diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index 3b1764c1df92c..6e93d0862fd6d 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -936,7 +936,8 @@ class InputWithButton implements IDisposable { this._disposables.add(getBaseLayerHoverDelegate().setupDelayedHover(this._buttonNode, () => ({ content: this._buttonHoverContent, appearance: { - showPointer: true + showPointer: true, + compact: true, } }))); From 49bf27ee9a080cce9b8bdaa71d9ba1340d346700 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 31 Oct 2024 07:50:41 -0700 Subject: [PATCH 156/555] fix: interactive postinst script breaks install for non-interactive environments (#227837) --- resources/linux/debian/postinst.template | 55 ++++++++++++++---------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/resources/linux/debian/postinst.template b/resources/linux/debian/postinst.template index b292cff8b2921..b44aa361cb345 100755 --- a/resources/linux/debian/postinst.template +++ b/resources/linux/debian/postinst.template @@ -36,49 +36,60 @@ if [ "@@NAME@@" != "code-oss" ]; then eval $(apt-config shell APT_TRUSTED_PARTS Dir::Etc::trustedparts/d) CODE_TRUSTED_PART=${APT_TRUSTED_PARTS}microsoft.gpg - RET=true + # RET seems to be true by default even after db_get is called on a first install. + RET='true' if [ -e '/usr/share/debconf/confmodule' ]; then . /usr/share/debconf/confmodule db_get @@NAME@@/add-microsoft-repo || true fi - # Determine whether to install the repository source list - WRITE_SOURCE=0 - if [ "$RET" = false ]; then - # The user does not want to add the Microsoft repository - WRITE_SOURCE=0 + # Determine whether to write the Microsoft repository source list + WRITE_SOURCE='no' + if [ "$RET" = 'false' ]; then + # The user specified in debconf not to add the Microsoft repository + WRITE_SOURCE='no' elif [ -f "$CODE_SOURCE_PART_DEB822" ]; then # The user has migrated themselves to the DEB822 format - WRITE_SOURCE=0 + WRITE_SOURCE='no' elif [ -f "$CODE_SOURCE_PART" ] && (grep -q "http://packages.microsoft.com/repos/vscode" $CODE_SOURCE_PART); then # Migrate from old repository - WRITE_SOURCE=2 + WRITE_SOURCE='yes' elif [ -f "$CODE_SOURCE_PART" ] && (grep -q "http://packages.microsoft.com/repos/code" $CODE_SOURCE_PART); then # Migrate from old repository - WRITE_SOURCE=2 + WRITE_SOURCE='yes' elif apt-cache policy | grep -q "https://packages.microsoft.com/repos/code"; then # The user is already on the new repository - WRITE_SOURCE=0 + WRITE_SOURCE='no' elif [ ! -f $CODE_SOURCE_PART ] && [ ! -f /etc/rpi-issue ]; then - # Write source list if it does not exist and we're not running on Raspberry Pi OS - WRITE_SOURCE=1 + # Source list does not exist and we're not running on Raspberry Pi OS + WRITE_SOURCE='ask' elif grep -q "# disabled on upgrade to" $CODE_SOURCE_PART; then - # Write source list if it was disabled by OS upgrade - WRITE_SOURCE=1 + # Source list was disabled by OS upgrade + WRITE_SOURCE='ask' fi - if [ "$WRITE_SOURCE" -eq "1" ] && [ -e '/usr/share/debconf/confmodule' ]; then - # Ask the user whether to actually write the source list - db_input high @@NAME@@/add-microsoft-repo || true - db_go || true + if [ "$WRITE_SOURCE" = 'ask' ]; then + if ! [ -t 1 ]; then + # By default, write sources in a non-interactive terminal + # to match old behavior. + WRITE_SOURCE='yes' + elif [ -e '/usr/share/debconf/confmodule' ]; then + # Ask the user whether to actually write the source list + db_input high @@NAME@@/add-microsoft-repo || true + db_go || true - db_get @@NAME@@/add-microsoft-repo - if [ "$RET" = false ]; then - WRITE_SOURCE=0 + db_get @@NAME@@/add-microsoft-repo + if [ "$RET" = false ]; then + WRITE_SOURCE='no' + fi + else + # The terminal is interactive but there is no debconf. + # Write sources to match old behavior. + WRITE_SOURCE='yes' fi fi - if [ "$WRITE_SOURCE" -ne "0" ]; then + if [ "$WRITE_SOURCE" != 'no' ]; then echo "### THIS FILE IS AUTOMATICALLY CONFIGURED ### # You may comment out this entry, but any other modifications may be lost. deb [arch=amd64,arm64,armhf] https://packages.microsoft.com/repos/code stable main" > $CODE_SOURCE_PART From 297bf7f2e962fa803173f534b7b52ef4411fff8d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:14:25 -0700 Subject: [PATCH 157/555] Don't dismiss quick fix when it's used Fixes #232656 --- .../quickFix/browser/quickFixAddon.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index cc24b3c014a9f..290c2c69a87f9 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -71,7 +71,9 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, private _lastQuickFixId: string | undefined; - private _registeredSelectors: Set = new Set(); + private readonly _registeredSelectors: Set = new Set(); + + private _didRun: boolean = false; constructor( private readonly _aliases: string[][] | undefined, @@ -129,7 +131,6 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, onSelect: async (fix: TerminalQuickFixItem) => { fix.action?.run(); this._actionWidgetService.hide(); - this._disposeQuickFix(fix.action.id, true); }, onHide: () => { this._terminal?.focus(); @@ -184,7 +185,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, return; } if (command.command !== '' && this._lastQuickFixId) { - this._disposeQuickFix(this._lastQuickFixId, false); + this._disposeQuickFix(this._lastQuickFixId); } const resolver = async (selector: ITerminalQuickFixOptions, lines?: string[]) => { @@ -212,7 +213,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, this._registerQuickFixDecoration(); } - private _disposeQuickFix(id: string, ranQuickFix: boolean): void { + private _disposeQuickFix(id: string): void { type QuickFixResultTelemetryEvent = { quickFixId: string; ranQuickFix: boolean; @@ -225,12 +226,13 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, }; this._telemetryService?.publicLog2('terminal/quick-fix', { quickFixId: id, - ranQuickFix + ranQuickFix: this._didRun }); this._decoration?.dispose(); this._decoration = undefined; this._quickFixes = undefined; this._lastQuickFixId = undefined; + this._didRun = false; } /** From 78af45ed057794792646dbf5b5c3fcf70d78b210 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:23:02 -0700 Subject: [PATCH 158/555] Improve lifecycle management of quick fix addon --- .../quickFix/browser/quickFixAddon.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index 290c2c69a87f9..9392182222d60 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -5,7 +5,7 @@ import type { ITerminalAddon } from '@xterm/headless'; import { Emitter, Event } from '../../../../../base/common/event.js'; -import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, MutableDisposable, type IDisposable } from '../../../../../base/common/lifecycle.js'; import { ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; import * as dom from '../../../../../base/browser/dom.js'; import { IAction } from '../../../../../base/common/actions.js'; @@ -65,7 +65,8 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, private _quickFixes: ITerminalAction[] | undefined; - private _decoration: IDecoration | undefined; + private readonly _decoration: MutableDisposable = this._register(new MutableDisposable()); + private readonly _decorationDisposables: MutableDisposable = this._register(new MutableDisposable()); private _currentRenderContext: { quickFixes: ITerminalAction[]; anchor: IAnchor; parentElement: HTMLElement } | undefined; @@ -228,8 +229,8 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, quickFixId: id, ranQuickFix: this._didRun }); - this._decoration?.dispose(); - this._decoration = undefined; + this._decoration.clear(); + this._decorationDisposables.clear(); this._quickFixes = undefined; this._lastQuickFixId = undefined; this._didRun = false; @@ -242,24 +243,23 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, if (!this._terminal) { return; } - if (!this._quickFixes) { + + this._decoration.clear(); + this._decorationDisposables.clear(); + const quickFixes = this._quickFixes; + if (!quickFixes || quickFixes.length === 0) { return; } const marker = this._terminal.registerMarker(); if (!marker) { return; } - const decoration = this._terminal.registerDecoration({ marker, width: 2, layer: 'top' }); + const decoration = this._decoration.value = this._terminal.registerDecoration({ marker, width: 2, layer: 'top' }); if (!decoration) { return; } - this._decoration = decoration; - const fixes = this._quickFixes; - if (!fixes) { - decoration.dispose(); - return; - } - decoration?.onRender((e: HTMLElement) => { + const store = this._decorationDisposables.value = new DisposableStore(); + store.add(decoration.onRender(e => { const rect = e.getBoundingClientRect(); const anchor = { x: rect.x, @@ -277,7 +277,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, } e.classList.add(...quickFixClasses); - const isExplainOnly = fixes.every(e => e.kind === 'explain'); + const isExplainOnly = quickFixes.every(e => e.kind === 'explain'); if (isExplainOnly) { e.classList.add('explainOnly'); } @@ -291,10 +291,10 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, return; } - this._currentRenderContext = { quickFixes: fixes, anchor, parentElement }; + this._currentRenderContext = { quickFixes, anchor, parentElement }; this._register(dom.addDisposableListener(e, dom.EventType.CLICK, () => this.showMenu())); - }); - decoration.onDispose(() => this._currentRenderContext = undefined); + })); + store.add(decoration.onDispose(() => this._currentRenderContext = undefined)); this._quickFixes = undefined; } } From 1137cb51a7c9581505c58d38bcbca659702219fc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:04:47 -0700 Subject: [PATCH 159/555] Put terminal quick fixes into the SI menu too Fixes #232658 --- src/vs/platform/terminal/common/terminal.ts | 8 ++++- .../contrib/terminal/browser/terminal.ts | 4 ++- .../terminal/browser/xterm/decorationAddon.ts | 29 +++++++++++++++++-- .../terminal/browser/xterm/xtermTerminal.ts | 3 +- .../quickFix/browser/quickFixAddon.ts | 18 ++++++++---- .../browser/terminal.quickFix.contribution.ts | 8 ++++- 6 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 34195bc820db8..f88d6eb1a54b8 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -7,7 +7,7 @@ import { Event } from '../../../base/common/event.js'; import { IProcessEnvironment, OperatingSystem } from '../../../base/common/platform.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; -import { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability, ITerminalCapabilityStore } from './capabilities/capabilities.js'; +import { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability, ITerminalCapabilityStore, type ITerminalCommand } from './capabilities/capabilities.js'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, ISetTerminalLayoutInfoArgs } from './terminalProcess.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { ISerializableEnvironmentVariableCollections } from './environmentVariable.js'; @@ -16,6 +16,8 @@ import { IWorkspaceFolder } from '../../workspace/common/workspace.js'; import { Registry } from '../../registry/common/platform.js'; import type * as performance from '../../../base/common/performance.js'; import { ILogService } from '../../log/common/log.js'; +import type { IAction } from '../../../base/common/actions.js'; +import type { IDisposable } from '../../../base/common/lifecycle.js'; export const terminalTabFocusModeContextKey = new RawContextKey('terminalTabFocusMode', false, true); @@ -929,6 +931,10 @@ export interface IShellIntegration { deserialize(serialized: ISerializedCommandDetectionCapability): void; } +export interface IDecorationAddon { + registerMenuItems(command: ITerminalCommand, items: IAction[]): IDisposable; +} + export interface ITerminalContributions { profiles?: ITerminalProfileContribution[]; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index d54f8af6561a3..2a56aeca9795d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -14,7 +14,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IKeyMods } from '../../../../platform/quickinput/common/quickInput.js'; import { IMarkProperties, ITerminalCapabilityImplMap, ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from '../../../../platform/terminal/common/capabilities/capabilities.js'; import { IMergedEnvironmentVariableCollection } from '../../../../platform/terminal/common/environmentVariable.js'; -import { IExtensionTerminalProfile, IReconnectionProperties, IShellIntegration, IShellLaunchConfig, ITerminalBackend, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TerminalType, TitleEventSource, WaitOnExitValue } from '../../../../platform/terminal/common/terminal.js'; +import { IExtensionTerminalProfile, IReconnectionProperties, IShellIntegration, IShellLaunchConfig, ITerminalBackend, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TerminalType, TitleEventSource, WaitOnExitValue, type IDecorationAddon } from '../../../../platform/terminal/common/terminal.js'; import { IColorTheme } from '../../../../platform/theme/common/themeService.js'; import { IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; @@ -1063,6 +1063,8 @@ export interface IXtermTerminal extends IDisposable { */ readonly shellIntegration: IShellIntegration; + readonly decorationAddon: IDecorationAddon; + readonly onDidChangeSelection: Event; readonly onDidChangeFindResults: Event<{ resultIndex: number; resultCount: number }>; readonly onDidRequestRunCommand: Event<{ command: ITerminalCommand; noNewLine?: boolean }>; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index bb7d902d802c5..d3ab1f9f08c3c 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -20,7 +20,7 @@ import { INotificationService, Severity } from '../../../../../platform/notifica import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { IQuickInputService, IQuickPickItem } from '../../../../../platform/quickinput/common/quickInput.js'; import { CommandInvalidationReason, ICommandDetectionCapability, IMarkProperties, ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; -import { TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; +import { TerminalSettingId, type IDecorationAddon } from '../../../../../platform/terminal/common/terminal.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; import { terminalDecorationError, terminalDecorationIncomplete, terminalDecorationMark, terminalDecorationSuccess } from '../terminalIcons.js'; import { DecorationSelector, TerminalDecorationHoverManager, updateLayout } from './decorationStyles.js'; @@ -29,7 +29,7 @@ import { ILifecycleService } from '../../../../services/lifecycle/common/lifecyc interface IDisposableDecoration { decoration: IDecoration; disposables: IDisposable[]; exitCode?: number; markProperties?: IMarkProperties } -export class DecorationAddon extends Disposable implements ITerminalAddon { +export class DecorationAddon extends Disposable implements ITerminalAddon, IDecorationAddon { protected _terminal: Terminal | undefined; private _capabilityDisposables: Map = new Map(); private _decorations: Map = new Map(); @@ -37,6 +37,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { private _showGutterDecorations?: boolean; private _showOverviewRulerDecorations?: boolean; private _terminalDecorationHoverManager: TerminalDecorationHoverManager; + private readonly _registeredMenuItems: Map = new Map(); private readonly _onDidRequestRunCommand = this._register(new Emitter<{ command: ITerminalCommand; noNewLine?: boolean }>()); readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event; @@ -311,6 +312,26 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { return decoration; } + registerMenuItems(command: ITerminalCommand, items: IAction[]): IDisposable { + const existingItems = this._registeredMenuItems.get(command); + if (existingItems) { + existingItems.push(...items); + } else { + this._registeredMenuItems.set(command, [...items]); + } + return toDisposable(() => { + const commandItems = this._registeredMenuItems.get(command); + if (commandItems) { + for (const item of items.values()) { + const index = commandItems.indexOf(item); + if (index !== -1) { + commandItems.splice(index, 1); + } + } + } + }); + } + private _createDisposables(element: HTMLElement, command?: ITerminalCommand, markProperties?: IMarkProperties): IDisposable[] { if (command?.exitCode === undefined && !command?.markProperties) { return []; @@ -385,6 +406,10 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { private async _getCommandActions(command: ITerminalCommand): Promise { const actions: IAction[] = []; + const registeredMenuItems = this._registeredMenuItems.get(command); + if (registeredMenuItems?.length) { + actions.push(...registeredMenuItems, new Separator()); + } if (command.command !== '') { const labelRun = localize("terminal.rerunCommand", 'Rerun Command'); actions.push({ diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 3b5a8c644df1d..52fb285fc7ec1 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -15,7 +15,7 @@ import { IXtermCore } from '../xterm-private.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { IEditorOptions } from '../../../../../editor/common/config/editorOptions.js'; -import { IShellIntegration, ITerminalLogService, TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; +import { IShellIntegration, ITerminalLogService, TerminalSettingId, type IDecorationAddon } from '../../../../../platform/terminal/common/terminal.js'; import { ITerminalFont, ITerminalConfiguration } from '../../common/terminal.js'; import { IMarkTracker, IInternalXtermTerminal, IXtermTerminal, IXtermColorProvider, XtermTerminalConstants, IXtermAttachToElementOptions, IDetachedXtermTerminal, ITerminalConfigurationService } from '../terminal.js'; import { LogLevel } from '../../../../../platform/log/common/log.js'; @@ -139,6 +139,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach get markTracker(): IMarkTracker { return this._markNavigationAddon; } get shellIntegration(): IShellIntegration { return this._shellIntegrationAddon; } + get decorationAddon(): IDecorationAddon { return this._decorationAddon; } get textureAtlas(): Promise | undefined { const canvas = this._webglAddon?.textureAtlas; diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index 9392182222d60..1a1da9f6d4aa0 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -46,8 +46,9 @@ const quickFixClasses = [ ]; export interface ITerminalQuickFixAddon { + readonly onDidRequestRerunCommand: Event<{ command: string; shouldExecute?: boolean }>; + readonly onDidUpdateQuickFixes: Event<{ command: ITerminalCommand; actions: ITerminalAction[] | undefined }>; showMenu(): void; - onDidRequestRerunCommand: Event<{ command: string; shouldExecute?: boolean }>; /** * Registers a listener on onCommandFinished scoped to a particular command or regular * expression and provides a callback to be executed for commands that match. @@ -56,8 +57,6 @@ export interface ITerminalQuickFixAddon { } export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, ITerminalQuickFixAddon { - private readonly _onDidRequestRerunCommand = new Emitter<{ command: string; shouldExecute?: boolean }>(); - readonly onDidRequestRerunCommand = this._onDidRequestRerunCommand.event; private _terminal: Terminal | undefined; @@ -76,6 +75,11 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, private _didRun: boolean = false; + private readonly _onDidRequestRerunCommand = new Emitter<{ command: string; shouldExecute?: boolean }>(); + readonly onDidRequestRerunCommand = this._onDidRequestRerunCommand.event; + private readonly _onDidUpdateQuickFixes = new Emitter<{ command: ITerminalCommand; actions: ITerminalAction[] | undefined }>(); + readonly onDidUpdateQuickFixes = this._onDidUpdateQuickFixes.event; + constructor( private readonly _aliases: string[][] | undefined, private readonly _capabilities: ITerminalCapabilityStore, @@ -186,7 +190,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, return; } if (command.command !== '' && this._lastQuickFixId) { - this._disposeQuickFix(this._lastQuickFixId); + this._disposeQuickFix(command, this._lastQuickFixId); } const resolver = async (selector: ITerminalQuickFixOptions, lines?: string[]) => { @@ -212,9 +216,11 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, this._quickFixes = result; this._lastQuickFixId = this._quickFixes[0].id; this._registerQuickFixDecoration(); + this._onDidUpdateQuickFixes.fire({ command, actions: this._quickFixes }); + this._quickFixes = undefined; } - private _disposeQuickFix(id: string): void { + private _disposeQuickFix(command: ITerminalCommand, id: string): void { type QuickFixResultTelemetryEvent = { quickFixId: string; ranQuickFix: boolean; @@ -231,6 +237,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, }); this._decoration.clear(); this._decorationDisposables.clear(); + this._onDidUpdateQuickFixes.fire({ command, actions: this._quickFixes }); this._quickFixes = undefined; this._lastQuickFixId = undefined; this._didRun = false; @@ -295,7 +302,6 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, this._register(dom.addDisposableListener(e, dom.EventType.CLICK, () => this.showMenu())); })); store.add(decoration.onDispose(() => this._currentRenderContext = undefined)); - this._quickFixes = undefined; } } diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts index 783e9107c14d2..25a65869ab0b9 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts @@ -5,7 +5,7 @@ import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; -import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { localize2 } from '../../../../../nls.js'; import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; @@ -38,6 +38,8 @@ class TerminalQuickFixContribution extends DisposableStore implements ITerminalC private _addon?: TerminalQuickFixAddon; get addon(): TerminalQuickFixAddon | undefined { return this._addon; } + private readonly _quickFixMenuItems = this.add(new MutableDisposable()); + constructor( private readonly _ctx: ITerminalContributionContext, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -52,6 +54,10 @@ class TerminalQuickFixContribution extends DisposableStore implements ITerminalC // Hook up listeners this.add(this._addon.onDidRequestRerunCommand((e) => this._ctx.instance.runCommand(e.command, e.shouldExecute || false))); + this.add(this._addon.onDidUpdateQuickFixes(e => { + // Only track the latest command's quick fixes + this._quickFixMenuItems.value = e.actions ? xterm.decorationAddon.registerMenuItems(e.command, e.actions) : undefined; + })); // Register quick fixes for (const actionOption of [ From cecc51724d95e5e00b69badaa1ebfe5333d60675 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 31 Oct 2024 17:32:37 +0100 Subject: [PATCH 160/555] :lipstick: --- .../workbench/contrib/terminal/browser/xterm/decorationAddon.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index a5a3289a59178..2f3174840d7a4 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -15,7 +15,6 @@ import { IClipboardService } from '../../../../../platform/clipboard/common/clip import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { IQuickInputService, IQuickPickItem } from '../../../../../platform/quickinput/common/quickInput.js'; @@ -54,7 +53,6 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { @IQuickInputService private readonly _quickInputService: IQuickInputService, @ILifecycleService lifecycleService: ILifecycleService, @ICommandService private readonly _commandService: ICommandService, - @IInstantiationService instantiationService: IInstantiationService, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @INotificationService private readonly _notificationService: INotificationService, @IHoverService private readonly _hoverService: IHoverService From 0334df2899efa05a65a86f79e3f76588e17c667f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 31 Oct 2024 17:39:53 +0100 Subject: [PATCH 161/555] keep `string` for `TreeItem#iconPath` (#232710) --- src/vscode-dts/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 67a2c24b7f2af..f10ef6a76805c 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -11901,7 +11901,7 @@ declare module 'vscode' { * When `falsy`, {@link ThemeIcon.Folder Folder Theme Icon} is assigned, if item is collapsible otherwise {@link ThemeIcon.File File Theme Icon}. * When a file or folder {@link ThemeIcon} is specified, icon is derived from the current file icon theme for the specified theme icon using {@link TreeItem.resourceUri resourceUri} (if provided). */ - iconPath?: IconPath; + iconPath?: string | IconPath; /** * A human-readable string which is rendered less prominent. From 931da885329cc1ca885ee53b960c9e8ad2641a23 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 31 Oct 2024 09:56:55 -0700 Subject: [PATCH 162/555] Fix conditional (#232742) --- .../src/commands/tsserverRequests.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/extensions/typescript-language-features/src/commands/tsserverRequests.ts b/extensions/typescript-language-features/src/commands/tsserverRequests.ts index 9f79c1066c354..50aeedfc8f853 100644 --- a/extensions/typescript-language-features/src/commands/tsserverRequests.ts +++ b/extensions/typescript-language-features/src/commands/tsserverRequests.ts @@ -16,7 +16,7 @@ export class TSServerRequestCommand implements Command { private readonly lazyClientHost: Lazy ) { } - public execute(command: keyof TypeScriptRequests, args?: any, config?: any) { + public async execute(command: keyof TypeScriptRequests, args?: any, config?: any): Promise { // A cancellation token cannot be passed through the command infrastructure const token = nulToken; @@ -36,11 +36,10 @@ export class TSServerRequestCommand implements Command { 'completionInfo' ]; - if (!allowList.includes(command) || command.startsWith('_')) { - return; + if (allowList.includes(command) || command.startsWith('_')) { + return this.lazyClientHost.value.serviceClient.execute(command, args, token, config); } - - return this.lazyClientHost.value.serviceClient.execute(command, args, token, config); + return undefined; } } From 8e7061874e9a6088af47ed6baf660232085bcae3 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:06:13 +0000 Subject: [PATCH 163/555] fix code action race condition (#232690) * revert promise wrapping * remove racetimeout * simple delay for showing code action progress * cleanup whitespace --------- Co-authored-by: Johannes --- src/vs/editor/contrib/codeAction/browser/codeAction.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeAction.ts b/src/vs/editor/contrib/codeAction/browser/codeAction.ts index c9b6312cd3e28..822d311ae1c66 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeAction.ts @@ -26,9 +26,6 @@ import { IProgress, Progress } from '../../../../platform/progress/common/progre import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { CodeActionFilter, CodeActionItem, CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource, filtersAction, mayIncludeActionsOfKind } from '../common/types.js'; import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; -import { raceTimeout } from '../../../../base/common/async.js'; - - export const codeActionCommandId = 'editor.action.codeAction'; export const quickFixCommandId = 'editor.action.quickFix'; @@ -123,10 +120,9 @@ export async function getCodeActions( const disposables = new DisposableStore(); const promises = providers.map(async provider => { + const handle = setTimeout(() => progress.report(provider), 1250); try { - const codeActionsPromise = Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token)); - - const providedCodeActions = await raceTimeout(codeActionsPromise, 1250, () => progress.report(provider)); + const providedCodeActions = await provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token); if (providedCodeActions) { disposables.add(providedCodeActions); @@ -148,6 +144,8 @@ export async function getCodeActions( } onUnexpectedExternalError(err); return emptyCodeActionsResponse; + } finally { + clearTimeout(handle); } }); From 0865c01675143b95d234763a326268957fe5a86c Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Thu, 31 Oct 2024 10:08:01 -0700 Subject: [PATCH 164/555] dont replace a cell for internal metadata change while the cell is executing (#232678) * dont replace a cell for internal metadata change while the cell is executing * switch conditional * add arg --- .../common/model/notebookCellTextModel.ts | 38 ++++++++++++++----- .../common/model/notebookTextModel.ts | 25 +++++++----- .../test/browser/notebookTextModel.test.ts | 38 +++++++++++++++++++ .../test/browser/notebookViewModel.test.ts | 2 +- 4 files changed, 82 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 637b18bd4cc5e..018aa490c9a08 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -20,6 +20,7 @@ import { ThrottledDelayer } from '../../../../../base/common/async.js'; import { ILanguageDetectionService } from '../../../../services/languageDetection/common/languageDetectionWorkerService.js'; import { toFormattedString } from '../../../../../base/common/jsonFormatter.js'; import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; +import { splitLines } from '../../../../../base/common/strings.js'; export class NotebookCellTextModel extends Disposable implements ICell { private readonly _onDidChangeOutputs = this._register(new Emitter()); @@ -429,10 +430,10 @@ export class NotebookCellTextModel extends Disposable implements ICell { * - language * - mime * - cellKind - * - internal metadata + * - internal metadata (conditionally) * - source */ - fastEqual(b: ICellDto2): boolean { + fastEqual(b: ICellDto2, ignoreMetadata: boolean): boolean { if (this.language !== b.language) { return false; } @@ -445,17 +446,21 @@ export class NotebookCellTextModel extends Disposable implements ICell { return false; } - if (this.internalMetadata?.executionOrder !== b.internalMetadata?.executionOrder - || this.internalMetadata?.lastRunSuccess !== b.internalMetadata?.lastRunSuccess - || this.internalMetadata?.runStartTime !== b.internalMetadata?.runStartTime - || this.internalMetadata?.runStartTimeAdjustment !== b.internalMetadata?.runStartTimeAdjustment - || this.internalMetadata?.runEndTime !== b.internalMetadata?.runEndTime) { - return false; + if (!ignoreMetadata) { + if (this.internalMetadata?.executionOrder !== b.internalMetadata?.executionOrder + || this.internalMetadata?.lastRunSuccess !== b.internalMetadata?.lastRunSuccess + || this.internalMetadata?.runStartTime !== b.internalMetadata?.runStartTime + || this.internalMetadata?.runStartTimeAdjustment !== b.internalMetadata?.runStartTimeAdjustment + || this.internalMetadata?.runEndTime !== b.internalMetadata?.runEndTime) { + return false; + } } // Once we attach the cell text buffer to an editor, the source of truth is the text buffer instead of the original source - if (this._textBuffer && this.getValue() !== b.source) { - return false; + if (this._textBuffer) { + if (!NotebookCellTextModel.linesAreEqual(this.textBuffer.getLinesContent(), b.source)) { + return false; + } } else if (this._source !== b.source) { return false; } @@ -463,6 +468,19 @@ export class NotebookCellTextModel extends Disposable implements ICell { return true; } + private static linesAreEqual(aLines: string[], b: string) { + const bLines = splitLines(b); + if (aLines.length !== bLines.length) { + return false; + } + for (let i = 0; i < aLines.length; i++) { + if (aLines[i] !== bLines[i]) { + return false; + } + } + return true; + } + override dispose() { dispose(this._outputs); // Manually release reference to previous text buffer to avoid large leaks diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index ea6bf326dd4a4..da42e4be6ec00 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -7,7 +7,7 @@ import { Emitter, Event, PauseableEmitter } from '../../../../../base/common/eve import { Disposable, dispose, IDisposable } from '../../../../../base/common/lifecycle.js'; import { URI } from '../../../../../base/common/uri.js'; import { NotebookCellTextModel } from './notebookCellTextModel.js'; -import { INotebookTextModel, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, diff, NotebookCellsChangeType, ICellDto2, TransientOptions, NotebookTextModelChangedEvent, IOutputDto, ICellOutput, IOutputItemDto, ISelectionState, NullablePartialNotebookCellMetadata, NotebookCellInternalMetadata, NullablePartialNotebookCellInternalMetadata, NotebookTextModelWillAddRemoveEvent, NotebookCellTextModelSplice, ICell, NotebookCellCollapseState, NotebookCellDefaultCollapseConfig, CellKind } from '../notebookCommon.js'; +import { INotebookTextModel, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, diff, NotebookCellsChangeType, ICellDto2, TransientOptions, NotebookTextModelChangedEvent, IOutputDto, ICellOutput, IOutputItemDto, ISelectionState, NullablePartialNotebookCellMetadata, NotebookCellInternalMetadata, NullablePartialNotebookCellInternalMetadata, NotebookTextModelWillAddRemoveEvent, NotebookCellTextModelSplice, ICell, NotebookCellCollapseState, NotebookCellDefaultCollapseConfig, CellKind, NotebookCellExecutionState } from '../notebookCommon.js'; import { IUndoRedoService, UndoRedoElementType, IUndoRedoElement, IResourceUndoRedoElement, UndoRedoGroup, IWorkspaceUndoRedoElement } from '../../../../../platform/undoRedo/common/undoRedo.js'; import { MoveCellEdit, SpliceCellsEdit, CellMetadataEdit } from './cellEdit.js'; import { ISequence, LcsDiff } from '../../../../../base/common/diff/diff.js'; @@ -25,6 +25,7 @@ import { IPosition } from '../../../../../editor/common/core/position.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { SearchParams } from '../../../../../editor/common/model/textModelSearch.js'; import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; +import { INotebookExecutionStateService } from '../notebookExecutionStateService.js'; class StackOperation implements IWorkspaceUndoRedoElement { type: UndoRedoElementType.Workspace; @@ -231,7 +232,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel @IUndoRedoService private readonly _undoService: IUndoRedoService, @IModelService private readonly _modelService: IModelService, @ILanguageService private readonly _languageService: ILanguageService, - @ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService + @ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService, + @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, ) { super(); this.transientOptions = options; @@ -423,7 +425,9 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel reset(cells: ICellDto2[], metadata: NotebookDocumentMetadata, transientOptions: TransientOptions): void { this.transientOptions = transientOptions; - const edits = NotebookTextModel.computeEdits(this, cells); + const executions = this._notebookExecutionStateService.getCellExecutionsForNotebook(this.uri); + const executingCellHandles = executions.filter(exe => exe.state === NotebookCellExecutionState.Executing).map(exe => exe.cellHandle); + const edits = NotebookTextModel.computeEdits(this, cells, executingCellHandles); this.applyEdits( [ @@ -437,10 +441,11 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel ); } - static computeEdits(model: NotebookTextModel, cells: ICellDto2[]) { + static computeEdits(model: NotebookTextModel, cells: ICellDto2[], executingHandles: number[] = []): ICellEditOperation[] { const edits: ICellEditOperation[] = []; + const isExecuting = (cell: NotebookCellTextModel) => executingHandles.includes(cell.handle); - const commonPrefix = this._commonPrefix(model.cells, model.cells.length, 0, cells, cells.length, 0); + const commonPrefix = this._commonPrefix(model.cells, model.cells.length, 0, cells, cells.length, 0, isExecuting); if (commonPrefix > 0) { for (let i = 0; i < commonPrefix; i++) { @@ -459,7 +464,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return edits; } - const commonSuffix = this._commonSuffix(model.cells, model.cells.length - commonPrefix, commonPrefix, cells, cells.length - commonPrefix, commonPrefix); + const commonSuffix = this._commonSuffix(model.cells, model.cells.length - commonPrefix, commonPrefix, cells, cells.length - commonPrefix, commonPrefix, isExecuting); if (commonSuffix > 0) { edits.push({ editType: CellEditType.Replace, index: commonPrefix, count: model.cells.length - commonPrefix - commonSuffix, cells: cells.slice(commonPrefix, cells.length - commonSuffix) }); @@ -514,20 +519,20 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel }); } - private static _commonPrefix(a: readonly NotebookCellTextModel[], aLen: number, aDelta: number, b: ICellDto2[], bLen: number, bDelta: number): number { + private static _commonPrefix(a: readonly NotebookCellTextModel[], aLen: number, aDelta: number, b: ICellDto2[], bLen: number, bDelta: number, isExecuting: (cell: NotebookCellTextModel) => boolean): number { const maxResult = Math.min(aLen, bLen); let result = 0; - for (let i = 0; i < maxResult && a[aDelta + i].fastEqual(b[bDelta + i]); i++) { + for (let i = 0; i < maxResult && a[aDelta + i].fastEqual(b[bDelta + i], isExecuting(a[aDelta + i])); i++) { result++; } return result; } - private static _commonSuffix(a: readonly NotebookCellTextModel[], aLen: number, aDelta: number, b: ICellDto2[], bLen: number, bDelta: number): number { + private static _commonSuffix(a: readonly NotebookCellTextModel[], aLen: number, aDelta: number, b: ICellDto2[], bLen: number, bDelta: number, isExecuting: (cell: NotebookCellTextModel) => boolean): number { const maxResult = Math.min(aLen, bLen); let result = 0; - for (let i = 0; i < maxResult && a[aDelta + aLen - i - 1].fastEqual(b[bDelta + bLen - i - 1]); i++) { + for (let i = 0; i < maxResult && a[aDelta + aLen - i - 1].fastEqual(b[bDelta + bLen - i - 1], isExecuting(a[aDelta + aLen - i - 1])); i++) { result++; } return result; diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts index da5246f35a4f3..877aace720e2c 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts @@ -1182,6 +1182,25 @@ suite('NotebookTextModel', () => { }); }); + test('computeEdits cell content changed while executing', async function () { + await withTestNotebook([ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}] + ], (editor) => { + const model = editor.textModel; + const cells = [ + { source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: {} }, + { source: 'var b = 2;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: {} } + ]; + const edits = NotebookTextModel.computeEdits(model, cells, [model.cells[1].handle]); + + assert.deepStrictEqual(edits, [ + { editType: CellEditType.Metadata, index: 0, metadata: {} }, + { editType: CellEditType.Replace, index: 1, count: 1, cells: cells.slice(1) } + ]); + }); + }); + test('computeEdits cell internal metadata changed', async function () { await withTestNotebook([ ['var a = 1;', 'javascript', CellKind.Code, [], {}], @@ -1201,6 +1220,25 @@ suite('NotebookTextModel', () => { }); }); + test('computeEdits cell internal metadata changed while executing', async function () { + await withTestNotebook([ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}] + ], (editor) => { + const model = editor.textModel; + const cells = [ + { source: 'var a = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: {} }, + { source: 'var b = 1;', language: 'javascript', cellKind: CellKind.Code, mime: undefined, outputs: [], metadata: {}, internalMetadata: { executionOrder: 1 } } + ]; + const edits = NotebookTextModel.computeEdits(model, cells, [model.cells[1].handle]); + + assert.deepStrictEqual(edits, [ + { editType: CellEditType.Metadata, index: 0, metadata: {} }, + { editType: CellEditType.Metadata, index: 1, metadata: {} }, + ]); + }); + }); + test('computeEdits cell insertion', async function () { await withTestNotebook([ ['var a = 1;', 'javascript', CellKind.Code, [], {}], diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts index ffe21e249239f..d12be746e53ea 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts @@ -64,7 +64,7 @@ suite('NotebookViewModel', () => { suiteTeardown(() => disposables.dispose()); test('ctor', function () { - const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService, languageDetectionService); + const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService, languageDetectionService, notebookExecutionStateService); const model = new NotebookEditorTestModel(notebook); const options = new NotebookOptions(mainWindow, false, undefined, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService)); const eventDispatcher = new NotebookEventDispatcher(); From b990a745074d2ba32ad46a89ed038c4d69002525 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Thu, 31 Oct 2024 10:18:32 -0700 Subject: [PATCH 165/555] clean up aria labels for different REPL editor parts (#232745) --- .../accessibility/browser/accessibilityConfiguration.ts | 7 +++---- .../interactive/browser/replInputHintContentWidget.ts | 4 ++-- .../workbench/contrib/replNotebook/browser/replEditor.ts | 2 ++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 5f00d92b18088..656c53d380992 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -58,8 +58,7 @@ export const enum AccessibilityVerbositySettingId { Hover = 'accessibility.verbosity.hover', Notification = 'accessibility.verbosity.notification', EmptyEditorHint = 'accessibility.verbosity.emptyEditorHint', - ReplEditor = 'accessibility.verbosity.notebook', - ReplInputHint = 'accessibility.verbosity.replInputHint', + ReplEditor = 'accessibility.verbosity.replEditor', Comments = 'accessibility.verbosity.comments', DiffEditorActive = 'accessibility.verbosity.diffEditorActive', Debug = 'accessibility.verbosity.debug', @@ -164,8 +163,8 @@ const configuration: IConfigurationNode = { description: localize('verbosity.emptyEditorHint', 'Provide information about relevant actions in an empty text editor.'), ...baseVerbosityProperty }, - [AccessibilityVerbositySettingId.ReplInputHint]: { - description: localize('verbosity.replInputHint', 'Provide information about relevant actions For the Repl input.'), + [AccessibilityVerbositySettingId.ReplEditor]: { + description: localize('verbosity.replEditor.description', 'Provide information about how to access the REPL editor accessibility help menu when the REPL editor is focused.'), ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Comments]: { diff --git a/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts b/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts index 4b83bb64f0105..6bf687dff0119 100644 --- a/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts +++ b/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts @@ -41,7 +41,7 @@ export class ReplInputHintContentWidget extends Disposable implements IContentWi })); const onDidFocusEditorText = Event.debounce(this.editor.onDidFocusEditorText, () => undefined, 500); this._register(onDidFocusEditorText(() => { - if (this.editor.hasTextFocus() && this.ariaLabel && configurationService.getValue(AccessibilityVerbositySettingId.ReplInputHint)) { + if (this.editor.hasTextFocus() && this.ariaLabel && configurationService.getValue(AccessibilityVerbositySettingId.ReplEditor)) { status(this.ariaLabel); } })); @@ -121,7 +121,7 @@ export class ReplInputHintContentWidget extends Disposable implements IContentWi ? localize('ReplInputAriaLabelHelp', "Use {0} for accessibility help. ", helpKeybinding) : localize('ReplInputAriaLabelHelpNoKb', "Run the Open Accessibility Help command for more information. "); - this.ariaLabel = helpInfo.concat(actionPart, localize('disableHint', ' Toggle {0} in settings to disable this hint.', AccessibilityVerbositySettingId.ReplInputHint)); + this.ariaLabel = actionPart.concat(helpInfo, localize('disableHint', ' Toggle {0} in settings to disable this hint.', AccessibilityVerbositySettingId.ReplEditor)); } } diff --git a/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts b/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts index ba554f86232be..a9c80cc984372 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts @@ -58,6 +58,7 @@ import { ReplEditorInput } from './replEditorInput.js'; import { ReplInputHintContentWidget } from '../../interactive/browser/replInputHintContentWidget.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; +import { localize } from '../../../../nls.js'; const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState'; @@ -276,6 +277,7 @@ export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { ...editorOptions, ...editorOptionsOverride, ...{ + ariaLabel: localize('replEditorInput', "REPL Input"), glyphMargin: true, padding: { top: INPUT_EDITOR_PADDING, From 4035be239404f58d2e0f0a0a83156949e097226f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 31 Oct 2024 18:48:58 +0100 Subject: [PATCH 166/555] reorder information (#232748) --- .../contrib/extensions/browser/extensionEditor.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 37bd3016708a3..749790dd3a884 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -946,14 +946,14 @@ export class ExtensionEditor extends EditorPane { this.contentDisposables.add(toDisposable(removeLayoutParticipant)); this.contentDisposables.add(scrollableContent); - this.renderCategories(content, extension); - this.renderExtensionResources(content, extension); - if (extension.gallery) { - this.renderMarketplaceInfo(content, extension); - } if (extension.local) { this.renderInstallInfo(content, extension.local); } + if (extension.gallery) { + this.renderMarketplaceInfo(content, extension); + } + this.renderCategories(content, extension); + this.renderExtensionResources(content, extension); append(container, scrollableContent.getDomNode()); scrollableContent.scanDomNode(); From 02565ff910d27d58cb375a2a4162ac857990e6fc Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Thu, 31 Oct 2024 10:53:37 -0700 Subject: [PATCH 167/555] differentiate notebook editors that are for a REPL (#232749) --- .../contrib/notebook/browser/notebookEditorWidget.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 1d4ee1948bea6..45433501b8818 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1169,18 +1169,21 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File system provider scheme for the resource' }; ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension for the resource' }; viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'View type of the notebook editor' }; + isRepl: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the notebook editor is within a REPL editor' }; }; type WorkbenchNotebookOpenEvent = { scheme: string; ext: string; viewType: string; + isRepl: boolean; }; this.telemetryService.publicLog2('notebook/editorOpened', { scheme: textModel.uri.scheme, ext: extname(textModel.uri), - viewType: textModel.viewType + viewType: textModel.viewType, + isRepl: this.isReplHistory }); } else { this.restoreListViewState(viewState); From 56d70157fb4aa1356923c4666cf25a342813f6a9 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 31 Oct 2024 11:38:04 -0700 Subject: [PATCH 168/555] Support model picker in notebook inline (#232751) --- .../contrib/chat/browser/actions/chatExecuteActions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 5ba1fd4a26fa6..679d5981eeedf 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -123,7 +123,8 @@ MenuRegistry.appendMenuItem(MenuId.ChatExecute, { ContextKeyExpr.or( ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Panel), ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.EditingSession), - ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Editor) + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Editor), + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Notebook) ) ), }); From 19726f3d9c6e8db0160d00fcbf8f8c61c98b90e7 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 31 Oct 2024 12:34:46 -0700 Subject: [PATCH 169/555] Break up renderMarkdown a little (#232756) Starts breaking up `renderMarkdown` --- src/vs/base/browser/formattedTextRenderer.ts | 2 +- src/vs/base/browser/markdownRenderer.ts | 379 ++++++++++--------- 2 files changed, 197 insertions(+), 184 deletions(-) diff --git a/src/vs/base/browser/formattedTextRenderer.ts b/src/vs/base/browser/formattedTextRenderer.ts index 66d0d575b9690..ed15b1316ab8e 100644 --- a/src/vs/base/browser/formattedTextRenderer.ts +++ b/src/vs/base/browser/formattedTextRenderer.ts @@ -9,7 +9,7 @@ import { IMouseEvent } from './mouseEvent.js'; import { DisposableStore } from '../common/lifecycle.js'; export interface IContentActionHandler { - callback: (content: string, event: IMouseEvent | IKeyboardEvent) => void; + readonly callback: (content: string, event: IMouseEvent | IKeyboardEvent) => void; readonly disposables: DisposableStore; } diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index d24679fcd37ce..3be90beeb99d9 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -96,156 +96,22 @@ const defaultMarkedRenderers = Object.freeze({ /** * Low-level way create a html element from a markdown string. * - * **Note** that for most cases you should be using [`MarkdownRenderer`](./src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts) + * **Note** that for most cases you should be using {@link import('../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js').MarkdownRenderer MarkdownRenderer} * which comes with support for pretty code block rendering and which uses the default way of handling links. */ -export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: MarkedOptions = {}): { element: HTMLElement; dispose: () => void } { +export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: Readonly = {}): { element: HTMLElement; dispose: () => void } { const disposables = new DisposableStore(); let isDisposed = false; const element = createElement(options); - const _uriMassage = function (part: string): string { - let data: any; - try { - data = parse(decodeURIComponent(part)); - } catch (e) { - // ignore - } - if (!data) { - return part; - } - data = cloneAndChange(data, value => { - if (markdown.uris && markdown.uris[value]) { - return URI.revive(markdown.uris[value]); - } else { - return undefined; - } - }); - return encodeURIComponent(JSON.stringify(data)); - }; - - const _href = function (href: string, isDomUri: boolean): string { - const data = markdown.uris && markdown.uris[href]; - let uri = URI.revive(data); - if (isDomUri) { - if (href.startsWith(Schemas.data + ':')) { - return href; - } - if (!uri) { - uri = URI.parse(href); - } - // this URI will end up as "src"-attribute of a dom node - // and because of that special rewriting needs to be done - // so that the URI uses a protocol that's understood by - // browsers (like http or https) - return FileAccess.uriToBrowserUri(uri).toString(true); - } - if (!uri) { - return href; - } - if (URI.parse(href).toString() === uri.toString()) { - return href; // no transformation performed - } - if (uri.query) { - uri = uri.with({ query: _uriMassage(uri.query) }); - } - return uri.toString(); - }; - - const renderer = new marked.Renderer(); - renderer.image = defaultMarkedRenderers.image; - renderer.link = defaultMarkedRenderers.link; - renderer.paragraph = defaultMarkedRenderers.paragraph; - - // Will collect [id, renderedElement] tuples - const codeBlocks: Promise<[string, HTMLElement]>[] = []; - const syncCodeBlocks: [string, HTMLElement][] = []; - - if (options.codeBlockRendererSync) { - renderer.code = ({ text, lang, raw }: marked.Tokens.Code) => { - const id = defaultGenerator.nextId(); - const value = options.codeBlockRendererSync!(postProcessCodeBlockLanguageId(lang), text, raw); - syncCodeBlocks.push([id, value]); - return `
${escape(text)}
`; - }; - } else if (options.codeBlockRenderer) { - renderer.code = ({ text, lang }: marked.Tokens.Code) => { - const id = defaultGenerator.nextId(); - const value = options.codeBlockRenderer!(postProcessCodeBlockLanguageId(lang), text); - codeBlocks.push(value.then(element => [id, element])); - return `
${escape(text)}
`; - }; - } - - if (options.actionHandler) { - const _activateLink = function (event: StandardMouseEvent | StandardKeyboardEvent): void { - const target = event.target.closest('a[data-href]'); - if (!DOM.isHTMLElement(target)) { - return; - } - - try { - let href = target.dataset['href']; - if (href) { - if (markdown.baseUri) { - href = resolveWithBaseUri(URI.from(markdown.baseUri), href); - } - options.actionHandler!.callback(href, event); - } - } catch (err) { - onUnexpectedError(err); - } finally { - event.preventDefault(); - } - }; - const onClick = options.actionHandler.disposables.add(new DomEmitter(element, 'click')); - const onAuxClick = options.actionHandler.disposables.add(new DomEmitter(element, 'auxclick')); - options.actionHandler.disposables.add(Event.any(onClick.event, onAuxClick.event)(e => { - const mouseEvent = new StandardMouseEvent(DOM.getWindow(element), e); - if (!mouseEvent.leftButton && !mouseEvent.middleButton) { - return; - } - _activateLink(mouseEvent); - })); - options.actionHandler.disposables.add(DOM.addDisposableListener(element, 'keydown', (e) => { - const keyboardEvent = new StandardKeyboardEvent(e); - if (!keyboardEvent.equals(KeyCode.Space) && !keyboardEvent.equals(KeyCode.Enter)) { - return; - } - _activateLink(keyboardEvent); - })); - } - - if (!markdown.supportHtml) { - // Note: we always pass the output through dompurify after this so that we don't rely on - // marked for real sanitization. - renderer.html = ({ text }) => { - if (options.sanitizerOptions?.replaceWithPlaintext) { - return escape(text); - } - - const match = markdown.isTrusted ? text.match(/^(]+>)|(<\/\s*span>)$/) : undefined; - return match ? text : ''; - }; - } - - markedOptions.renderer = renderer; - - // values that are too long will freeze the UI - let value = markdown.value ?? ''; - if (value.length > 100_000) { - value = `${value.substr(0, 100_000)}…`; - } - // escape theme icons - if (markdown.supportThemeIcons) { - value = markdownEscapeEscapedIcons(value); - } + const { renderer, codeBlocks, syncCodeBlocks } = createMarkdownRenderer(options, markdown); + const value = preprocessMarkdownString(markdown); let renderedMarkdown: string; if (options.fillInIncompleteTokens) { // The defaults are applied by parse but not lexer()/parser(), and they need to be present - const opts = { + const opts: MarkedOptions = { ...marked.defaults, ...markedOptions }; @@ -253,7 +119,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const newTokens = fillInIncompleteTokens(tokens); renderedMarkdown = marked.parser(newTokens, opts); } else { - renderedMarkdown = marked.parse(value, { ...markedOptions, async: false }); + renderedMarkdown = marked.parse(value, { ...markedOptions, renderer, async: false }); } // Rewrite theme icons @@ -265,48 +131,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const htmlParser = new DOMParser(); const markdownHtmlDoc = htmlParser.parseFromString(sanitizeRenderedMarkdown({ isTrusted: markdown.isTrusted, ...options.sanitizerOptions }, renderedMarkdown) as unknown as string, 'text/html'); - markdownHtmlDoc.body.querySelectorAll('img, audio, video, source') - .forEach(img => { - const src = img.getAttribute('src'); // Get the raw 'src' attribute value as text, not the resolved 'src' - if (src) { - let href = src; - try { - if (markdown.baseUri) { // absolute or relative local path, or file: uri - href = resolveWithBaseUri(URI.from(markdown.baseUri), href); - } - } catch (err) { } - - img.setAttribute('src', _href(href, true)); - - if (options.remoteImageIsAllowed) { - const uri = URI.parse(href); - if (uri.scheme !== Schemas.file && uri.scheme !== Schemas.data && !options.remoteImageIsAllowed(uri)) { - img.replaceWith(DOM.$('', undefined, img.outerHTML)); - } - } - } - }); - - markdownHtmlDoc.body.querySelectorAll('a') - .forEach(a => { - const href = a.getAttribute('href'); // Get the raw 'href' attribute value as text, not the resolved 'href' - a.setAttribute('href', ''); // Clear out href. We use the `data-href` for handling clicks instead - if ( - !href - || /^data:|javascript:/i.test(href) - || (/^command:/i.test(href) && !markdown.isTrusted) - || /^command:(\/\/\/)?_workbench\.downloadResource/i.test(href) - ) { - // drop the link - a.replaceWith(...a.childNodes); - } else { - let resolvedHref = _href(href, false); - if (markdown.baseUri) { - resolvedHref = resolveWithBaseUri(URI.from(markdown.baseUri), href); - } - a.dataset.href = resolvedHref; - } - }); + rewriteRenderedLinks(markdown, options, markdownHtmlDoc.body); element.innerHTML = sanitizeRenderedMarkdown({ isTrusted: markdown.isTrusted, ...options.sanitizerOptions }, markdownHtmlDoc.body.innerHTML) as unknown as string; @@ -336,7 +161,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende } } - // signal size changes for image tags + // Signal size changes for image tags if (options.asyncRenderCallback) { for (const img of element.getElementsByTagName('img')) { const listener = disposables.add(DOM.addDisposableListener(img, 'load', () => { @@ -346,6 +171,27 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende } } + // Add event listeners for links + if (options.actionHandler) { + const onClick = options.actionHandler.disposables.add(new DomEmitter(element, 'click')); + const onAuxClick = options.actionHandler.disposables.add(new DomEmitter(element, 'auxclick')); + options.actionHandler.disposables.add(Event.any(onClick.event, onAuxClick.event)(e => { + const mouseEvent = new StandardMouseEvent(DOM.getWindow(element), e); + if (!mouseEvent.leftButton && !mouseEvent.middleButton) { + return; + } + activateLink(markdown, options, mouseEvent); + })); + + options.actionHandler.disposables.add(DOM.addDisposableListener(element, 'keydown', (e) => { + const keyboardEvent = new StandardKeyboardEvent(e); + if (!keyboardEvent.equals(KeyCode.Space) && !keyboardEvent.equals(KeyCode.Enter)) { + return; + } + activateLink(markdown, options, keyboardEvent); + })); + } + return { element, dispose: () => { @@ -355,6 +201,173 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende }; } +function rewriteRenderedLinks(markdown: IMarkdownString, options: MarkdownRenderOptions, root: HTMLElement) { + for (const el of root.querySelectorAll('img, audio, video, source')) { + const src = el.getAttribute('src'); // Get the raw 'src' attribute value as text, not the resolved 'src' + if (src) { + let href = src; + try { + if (markdown.baseUri) { // absolute or relative local path, or file: uri + href = resolveWithBaseUri(URI.from(markdown.baseUri), href); + } + } catch (err) { } + + el.setAttribute('src', massageHref(markdown, href, true)); + + if (options.remoteImageIsAllowed) { + const uri = URI.parse(href); + if (uri.scheme !== Schemas.file && uri.scheme !== Schemas.data && !options.remoteImageIsAllowed(uri)) { + el.replaceWith(DOM.$('', undefined, el.outerHTML)); + } + } + } + } + + for (const el of root.querySelectorAll('a')) { + const href = el.getAttribute('href'); // Get the raw 'href' attribute value as text, not the resolved 'href' + el.setAttribute('href', ''); // Clear out href. We use the `data-href` for handling clicks instead + if (!href + || /^data:|javascript:/i.test(href) + || (/^command:/i.test(href) && !markdown.isTrusted) + || /^command:(\/\/\/)?_workbench\.downloadResource/i.test(href)) { + // drop the link + el.replaceWith(...el.childNodes); + } else { + let resolvedHref = massageHref(markdown, href, false); + if (markdown.baseUri) { + resolvedHref = resolveWithBaseUri(URI.from(markdown.baseUri), href); + } + el.dataset.href = resolvedHref; + } + } +} + +function createMarkdownRenderer(options: MarkdownRenderOptions, markdown: IMarkdownString): { renderer: marked.Renderer; codeBlocks: Promise<[string, HTMLElement]>[]; syncCodeBlocks: [string, HTMLElement][] } { + const renderer = new marked.Renderer(); + renderer.image = defaultMarkedRenderers.image; + renderer.link = defaultMarkedRenderers.link; + renderer.paragraph = defaultMarkedRenderers.paragraph; + + // Will collect [id, renderedElement] tuples + const codeBlocks: Promise<[string, HTMLElement]>[] = []; + const syncCodeBlocks: [string, HTMLElement][] = []; + + if (options.codeBlockRendererSync) { + renderer.code = ({ text, lang, raw }: marked.Tokens.Code) => { + const id = defaultGenerator.nextId(); + const value = options.codeBlockRendererSync!(postProcessCodeBlockLanguageId(lang), text, raw); + syncCodeBlocks.push([id, value]); + return `
${escape(text)}
`; + }; + } else if (options.codeBlockRenderer) { + renderer.code = ({ text, lang }: marked.Tokens.Code) => { + const id = defaultGenerator.nextId(); + const value = options.codeBlockRenderer!(postProcessCodeBlockLanguageId(lang), text); + codeBlocks.push(value.then(element => [id, element])); + return `
${escape(text)}
`; + }; + } + + if (!markdown.supportHtml) { + // Note: we always pass the output through dompurify after this so that we don't rely on + // marked for real sanitization. + renderer.html = ({ text }) => { + if (options.sanitizerOptions?.replaceWithPlaintext) { + return escape(text); + } + + const match = markdown.isTrusted ? text.match(/^(]+>)|(<\/\s*span>)$/) : undefined; + return match ? text : ''; + }; + } + return { renderer, codeBlocks, syncCodeBlocks }; +} + +function preprocessMarkdownString(markdown: IMarkdownString) { + let value = markdown.value; + + // values that are too long will freeze the UI + if (value.length > 100_000) { + value = `${value.substr(0, 100_000)}…`; + } + + // escape theme icons + if (markdown.supportThemeIcons) { + value = markdownEscapeEscapedIcons(value); + } + + return value; +} + +function activateLink(markdown: IMarkdownString, options: MarkdownRenderOptions, event: StandardMouseEvent | StandardKeyboardEvent): void { + const target = event.target.closest('a[data-href]'); + if (!DOM.isHTMLElement(target)) { + return; + } + + try { + let href = target.dataset['href']; + if (href) { + if (markdown.baseUri) { + href = resolveWithBaseUri(URI.from(markdown.baseUri), href); + } + options.actionHandler!.callback(href, event); + } + } catch (err) { + onUnexpectedError(err); + } finally { + event.preventDefault(); + } +} + +function uriMassage(markdown: IMarkdownString, part: string): string { + let data: any; + try { + data = parse(decodeURIComponent(part)); + } catch (e) { + // ignore + } + if (!data) { + return part; + } + data = cloneAndChange(data, value => { + if (markdown.uris && markdown.uris[value]) { + return URI.revive(markdown.uris[value]); + } else { + return undefined; + } + }); + return encodeURIComponent(JSON.stringify(data)); +} + +function massageHref(markdown: IMarkdownString, href: string, isDomUri: boolean): string { + const data = markdown.uris && markdown.uris[href]; + let uri = URI.revive(data); + if (isDomUri) { + if (href.startsWith(Schemas.data + ':')) { + return href; + } + if (!uri) { + uri = URI.parse(href); + } + // this URI will end up as "src"-attribute of a dom node + // and because of that special rewriting needs to be done + // so that the URI uses a protocol that's understood by + // browsers (like http or https) + return FileAccess.uriToBrowserUri(uri).toString(true); + } + if (!uri) { + return href; + } + if (URI.parse(href).toString() === uri.toString()) { + return href; // no transformation performed + } + if (uri.query) { + uri = uri.with({ query: uriMassage(markdown, uri.query) }); + } + return uri.toString(); +} + function postProcessCodeBlockLanguageId(lang: string | undefined): string { if (!lang) { return ''; From e64fb06b203b6442989a2e803399c1c636453028 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 31 Oct 2024 12:41:32 -0700 Subject: [PATCH 170/555] Disable caching of dev device id + fallback to UUID (#232761) --- src/vs/base/node/id.ts | 2 +- src/vs/code/node/cliProcessMain.ts | 2 +- src/vs/platform/telemetry/common/telemetry.ts | 1 - .../platform/telemetry/electron-main/telemetryUtils.ts | 5 ++--- src/vs/platform/telemetry/node/telemetryUtils.ts | 10 +++------- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts index f4e29d1e5805a..271c9aeb769c0 100644 --- a/src/vs/base/node/id.ts +++ b/src/vs/base/node/id.ts @@ -122,6 +122,6 @@ export async function getdevDeviceId(errorLogger: (error: any) => void): Promise return id; } catch (err) { errorLogger(err); - return ''; + return uuid.generateUuid(); } } diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index d3b20ecea47c7..2200c2c17cb89 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -186,7 +186,7 @@ class CliMain extends Disposable { } } const sqmId = await resolveSqmId(stateService, logService); - const devDeviceId = await resolvedevDeviceId(stateService, logService); + const devDeviceId = await resolvedevDeviceId(logService); // Initialize user data profiles after initializing the state userDataProfilesService.init(); diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index 6bfcb9159cdf8..a2310448b50ff 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -74,7 +74,6 @@ export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; export const machineIdKey = 'telemetry.machineId'; export const sqmIdKey = 'telemetry.sqmId'; -export const devDeviceIdKey = 'telemetry.devDeviceId'; // Configuration Keys export const TELEMETRY_SECTION_ID = 'telemetry'; diff --git a/src/vs/platform/telemetry/electron-main/telemetryUtils.ts b/src/vs/platform/telemetry/electron-main/telemetryUtils.ts index bf16f18b9a7ab..e4d49709e49bb 100644 --- a/src/vs/platform/telemetry/electron-main/telemetryUtils.ts +++ b/src/vs/platform/telemetry/electron-main/telemetryUtils.ts @@ -5,7 +5,7 @@ import { ILogService } from '../../log/common/log.js'; import { IStateService } from '../../state/node/state.js'; -import { machineIdKey, sqmIdKey, devDeviceIdKey } from '../common/telemetry.js'; +import { machineIdKey, sqmIdKey } from '../common/telemetry.js'; import { resolveMachineId as resolveNodeMachineId, resolveSqmId as resolveNodeSqmId, resolvedevDeviceId as resolveNodedevDeviceId } from '../node/telemetryUtils.js'; export async function resolveMachineId(stateService: IStateService, logService: ILogService): Promise { @@ -22,7 +22,6 @@ export async function resolveSqmId(stateService: IStateService, logService: ILog } export async function resolvedevDeviceId(stateService: IStateService, logService: ILogService): Promise { - const devDeviceId = await resolveNodedevDeviceId(stateService, logService); - stateService.setItem(devDeviceIdKey, devDeviceId); + const devDeviceId = await resolveNodedevDeviceId(logService); return devDeviceId; } diff --git a/src/vs/platform/telemetry/node/telemetryUtils.ts b/src/vs/platform/telemetry/node/telemetryUtils.ts index faca66149bc64..411efc1ea6ee0 100644 --- a/src/vs/platform/telemetry/node/telemetryUtils.ts +++ b/src/vs/platform/telemetry/node/telemetryUtils.ts @@ -7,7 +7,7 @@ import { isMacintosh } from '../../../base/common/platform.js'; import { getMachineId, getSqmMachineId, getdevDeviceId } from '../../../base/node/id.js'; import { ILogService } from '../../log/common/log.js'; import { IStateReadService } from '../../state/node/state.js'; -import { machineIdKey, sqmIdKey, devDeviceIdKey } from '../common/telemetry.js'; +import { machineIdKey, sqmIdKey } from '../common/telemetry.js'; export async function resolveMachineId(stateService: IStateReadService, logService: ILogService): Promise { @@ -30,11 +30,7 @@ export async function resolveSqmId(stateService: IStateReadService, logService: return sqmId; } -export async function resolvedevDeviceId(stateService: IStateReadService, logService: ILogService): Promise { - let devDeviceId = stateService.getItem(devDeviceIdKey); - if (typeof devDeviceId !== 'string') { - devDeviceId = await getdevDeviceId(logService.error.bind(logService)); - } - +export async function resolvedevDeviceId(logService: ILogService): Promise { + const devDeviceId = await getdevDeviceId(logService.error.bind(logService)); return devDeviceId; } From 7b7ed6b6e4585674eb18c43c87fef7c9f5f6e424 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Thu, 31 Oct 2024 14:34:46 -0700 Subject: [PATCH 171/555] dont run if inline chat should be accepted (#232766) --- .../contrib/notebook/browser/controller/executeActions.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index f4e7c8a3f3776..bcb4fcc526adb 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -19,7 +19,6 @@ import { EditorsOrder } from '../../../../common/editor.js'; import { IDebugService } from '../../../debug/common/debug.js'; import { CTX_INLINE_CHAT_FOCUSED } from '../../../inlineChat/common/inlineChat.js'; import { insertCell } from './cellOperations.js'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED } from './chat/notebookChatContext.js'; import { NotebookChatController } from './chat/notebookChatController.js'; import { CELL_TITLE_CELL_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, NotebookMultiCellAction, cellExecutionArgs, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, parseMultiCellExecutionArgs } from './coreActions.js'; import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, IFocusNotebookCellOptions, ScrollToRevealBehavior } from '../notebookBrowser.js'; @@ -192,10 +191,7 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction { precondition: executeThisCellCondition, title: localize('notebookActions.execute', "Execute Cell"), keybinding: { - when: ContextKeyExpr.or( - NOTEBOOK_CELL_LIST_FOCUSED, - ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED) - ), + when: NOTEBOOK_CELL_LIST_FOCUSED, primary: KeyMod.WinCtrl | KeyCode.Enter, win: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter From 4520d915c98954dc96dd0bc00b8bb68181cbf2b6 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 31 Oct 2024 22:50:54 +0000 Subject: [PATCH 172/555] allow extension info to be selectable in issue reporter (#232771) allow this to be user selectable --- .../contrib/issue/browser/issueReporterPage.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/issue/browser/issueReporterPage.ts b/src/vs/workbench/contrib/issue/browser/issueReporterPage.ts index b54b661ff448d..5cbc70936ffb2 100644 --- a/src/vs/workbench/contrib/issue/browser/issueReporterPage.ts +++ b/src/vs/workbench/contrib/issue/browser/issueReporterPage.ts @@ -105,7 +105,7 @@ export default (): string => ` ${sendSystemInfoLabel} (${escape(localize('show', "show"))}) - @@ -115,7 +115,7 @@ export default (): string => ` ${sendProcessInfoLabel} (${escape(localize('show', "show"))}) -