From 4a6ebe0ee5b2d13435e42416640b69d1a1004289 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 26 Mar 2024 12:32:02 +0100 Subject: [PATCH 01/17] Git - fix #208562 (#208761) --- extensions/git/src/decorationProvider.ts | 10 ++++++---- extensions/git/src/util.ts | 7 +++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 3aae16f6baf..3f8553260e9 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import { Repository, GitResourceGroup } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; -import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource, combinedDisposable } from './util'; +import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource, combinedDisposable, runAndSubscribeEvent } from './util'; import { Change, GitErrorCodes, Status } from './api/git'; class GitIgnoreDecorationProvider implements FileDecorationProvider { @@ -101,7 +101,7 @@ class GitDecorationProvider implements FileDecorationProvider { constructor(private repository: Repository) { this.disposables.push( window.registerFileDecorationProvider(this), - repository.onDidRunGitStatus(this.onDidRunGitStatus, this) + runAndSubscribeEvent(repository.onDidRunGitStatus, () => this.onDidRunGitStatus()) ); } @@ -162,8 +162,10 @@ class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider private readonly disposables: Disposable[] = []; constructor(private readonly repository: Repository) { - this.disposables.push(window.registerFileDecorationProvider(this)); - repository.historyProvider.onDidChangeCurrentHistoryItemGroup(this.onDidChangeCurrentHistoryItemGroup, this, this.disposables); + this.disposables.push( + window.registerFileDecorationProvider(this), + runAndSubscribeEvent(repository.historyProvider.onDidChangeCurrentHistoryItemGroup, () => this.onDidChangeCurrentHistoryItemGroup()) + ); } private async onDidChangeCurrentHistoryItemGroup(): Promise { diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 0d4b9241b55..219c87b148d 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -47,6 +47,13 @@ export function filterEvent(event: Event, filter: (e: T) => boolean): Even return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); } +export function runAndSubscribeEvent(event: Event, handler: (e: T) => any, initial: T): IDisposable; +export function runAndSubscribeEvent(event: Event, handler: (e: T | undefined) => any): IDisposable; +export function runAndSubscribeEvent(event: Event, handler: (e: T | undefined) => any, initial?: T): IDisposable { + handler(initial); + return event(e => handler(e)); +} + export function anyEvent(...events: Event[]): Event { return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => { const result = combinedDisposable(events.map(event => event(i => listener.call(thisArgs, i)))); From 7a3a29fee3b311ab147585d6ac92dbb4658bc0a0 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 26 Mar 2024 12:38:26 +0100 Subject: [PATCH 02/17] Assorted inline chat fixes (#208769) * fix/workaround https://github.com/microsoft/vscode/issues/208549 * fixes https://github.com/microsoft/vscode/issues/208656 * add minHeight to inline chat widget fixes https://github.com/microsoft/vscode-copilot/issues/4770 * fixes https://github.com/microsoft/vscode/issues/208606 * only honor `refer` flag when its command is the first and only fixes https://github.com/microsoft/vscode/issues/208574 --- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../browser/inlineChatContentWidget.ts | 12 ++++++++- .../browser/inlineChatController.ts | 8 ++---- .../inlineChat/browser/inlineChatWidget.ts | 26 ++++++++++++++++--- .../browser/inlineChatZoneWidget.ts | 3 +-- .../inlineChat/browser/media/inlineChat.css | 4 +++ 6 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 977267c5bfe..bd06bef78ac 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -326,7 +326,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // Only allow history navigation when the input is empty. // (If this model change happened as a result of a history navigation, this is canceled out by a call in this.navigateHistory) const model = this._inputEditor.getModel(); - const inputHasText = !!model && model.getValueLength() > 0; + const inputHasText = !!model && model.getValue().trim().length > 0; this.inputEditorHasText.set(inputHasText); // If the user is typing on a history entry, then reset the onHistoryEntry flag so that history navigation can be disabled diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts index 7b586a2bba0..c41ef5587a3 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts @@ -20,6 +20,8 @@ import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { Range } from 'vs/editor/common/core/range'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class InlineChatContentWidget implements IContentWidget { @@ -45,11 +47,19 @@ export class InlineChatContentWidget implements IContentWidget { constructor( private readonly _editor: ICodeEditor, @IInstantiationService instaService: IInstantiationService, + @IContextKeyService contextKeyService: IContextKeyService, ) { this._defaultChatModel = this._store.add(instaService.createInstance(ChatModel, `inlineChatDefaultModel/editorContentWidgetPlaceholder`, undefined)); - this._widget = instaService.createInstance( + const scopedInstaService = instaService.createChild( + new ServiceCollection([ + IContextKeyService, + this._store.add(contextKeyService.createScoped(this._domNode)) + ]) + ); + + this._widget = scopedInstaService.createInstance( ChatWidget, ChatAgentLocation.Editor, { resource: true }, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index e094b32c44f..74efad97f48 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -606,7 +606,7 @@ export class InlineChatController implements IEditorContribution { } return false; }); - if (refer && slashCommandLike) { + if (refer && slashCommandLike && !this._session.lastExchange) { this._log('[IE] seeing refer command, continuing outside editor', this._session.provider.extensionId); // cancel this request @@ -627,11 +627,7 @@ export class InlineChatController implements IEditorContribution { // if agent has a refer command, massage the input to include the agent name await this._instaService.invokeFunction(sendRequest, massagedInput); - if (!this._session.lastExchange) { - // DONE when there wasn't any exchange yet. We used the inline chat only as trampoline - return State.ACCEPT; - } - return State.WAIT_FOR_INPUT; + return State.ACCEPT; } this._session.addInput(new SessionPrompt(input, this._nextAttempt, this._nextWithIntentDetection)); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 767821931a6..9b96f16081a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -54,6 +54,7 @@ import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestCont import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; export interface InlineChatWidgetViewState { @@ -174,7 +175,16 @@ export class InlineChatWidget { this._store.add(this._progressBar); let allowRequests = false; - this._chatWidget = _instantiationService.createInstance( + + + const scopedInstaService = _instantiationService.createChild( + new ServiceCollection([ + IContextKeyService, + this._store.add(_contextKeyService.createScoped(this._elements.chatWidget)) + ]) + ); + + this._chatWidget = scopedInstaService.createInstance( ChatWidget, location, { resource: true }, @@ -365,16 +375,24 @@ export class InlineChatWidget { get contentHeight(): number { const data = { followUpsHeight: getTotalHeight(this._elements.followUps), - chatWidgetHeight: this._chatWidget.contentHeight, + chatWidgetContentHeight: this._chatWidget.contentHeight, progressHeight: getTotalHeight(this._elements.progress), statusHeight: getTotalHeight(this._elements.status), extraHeight: this._getExtraHeight() }; - const result = data.progressHeight + data.chatWidgetHeight + data.followUpsHeight + data.statusHeight + data.extraHeight; - // console.log(`InlineChat#contentHeight ${result}`, data); + const result = data.progressHeight + data.chatWidgetContentHeight + data.followUpsHeight + data.statusHeight + data.extraHeight; return result; } + get minHeight(): number { + // The chat widget is variable height and supports scrolling. It + // should be at least 100px high and at most the content height. + let value = this.contentHeight; + value -= this._chatWidget.contentHeight; + value += Math.min(100, this._chatWidget.contentHeight); + return value; + } + protected _getExtraHeight(): number { return 12 /* padding */ + 2 /*border*/ + 12 /*shadow*/; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index df70e8a35dd..93f119f260d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -114,9 +114,8 @@ export class InlineChatZoneWidget extends ZoneWidget { const chatContentHeight = this.widget.contentHeight; const editorHeight = this.editor.getLayoutInfo().height; - const contentHeight = Math.min(chatContentHeight, editorHeight * 0.42); + const contentHeight = Math.min(chatContentHeight, Math.max(this.widget.minHeight, editorHeight * 0.42)); const heightInLines = contentHeight / this.editor.getOption(EditorOption.lineHeight); - // console.log('ZONE#_computeHeightInLines', { chatContentHeight, editorHeight, contentHeight, heightInLines }); return heightInLines; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index 0223db05eee..bdd3c4d2a5c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -7,6 +7,10 @@ z-index: 3; } +.monaco-workbench .zone-widget.inline-chat-widget .interactive-session { + max-width: unset; +} + .monaco-workbench .zone-widget-container.inside-selection { background-color: var(--vscode-inlineChat-regionHighlight); } From eead56aaf0e30d40384984e9b7aa43729b0c16bf Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 26 Mar 2024 12:44:12 +0100 Subject: [PATCH 03/17] Fix setting description for preview editors (#208758) setting description fix --- src/vs/workbench/browser/workbench.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 2e8693caeb5..b93a0ecc2cf 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -286,12 +286,12 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.editor.enablePreviewFromQuickOpen': { 'type': 'boolean', - 'markdownDescription': localize({ comment: ['{0}, {1} will be a setting name rendered as a link'], key: 'enablePreviewFromQuickOpen' }, "Controls whether editors opened from Quick Open show as preview editors. Preview editors do not stay open, and are reused until explicitly set to be kept open (via double-click or editing). When enabled, hold Ctrl before selection to open an editor as a non-preview. This value is ignored when {0} is not set to {1}.", '`#workbench.editor.enablePreview#`', '`multiple`'), + 'markdownDescription': localize({ comment: ['{0}, {1} will be a setting name rendered as a link'], key: 'enablePreviewFromQuickOpen' }, "Controls whether editors opened from Quick Open show as preview editors. Preview editors do not stay open, and are reused until explicitly set to be kept open (via double-click or editing). When enabled, hold Ctrl before selection to open an editor as a non-preview. This value is ignored when {0} is not set to {1}.", '`#workbench.editor.showTabs#`', '`multiple`'), 'default': false }, 'workbench.editor.enablePreviewFromCodeNavigation': { 'type': 'boolean', - 'markdownDescription': localize({ comment: ['{0}, {1} will be a setting name rendered as a link'], key: 'enablePreviewFromCodeNavigation' }, "Controls whether editors remain in preview when a code navigation is started from them. Preview editors do not stay open, and are reused until explicitly set to be kept open (via double-click or editing). This value is ignored when {0} is not set to {1}.", '`#workbench.editor.enablePreview#`', '`multiple`'), + 'markdownDescription': localize({ comment: ['{0}, {1} will be a setting name rendered as a link'], key: 'enablePreviewFromCodeNavigation' }, "Controls whether editors remain in preview when a code navigation is started from them. Preview editors do not stay open, and are reused until explicitly set to be kept open (via double-click or editing). This value is ignored when {0} is not set to {1}.", '`#workbench.editor.showTabs#`', '`multiple`'), 'default': false }, 'workbench.editor.closeOnFileDelete': { From 9fcefcb44b73d14cb36e65fa0583e3c56d57d53f Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 26 Mar 2024 12:45:25 +0100 Subject: [PATCH 04/17] Fix Activity Bar theme colors for Secondary Side Bar (#208764) fix #208259 --- .../browser/parts/auxiliarybar/auxiliaryBarPart.ts | 11 ++++++----- .../parts/auxiliarybar/media/auxiliaryBarPart.css | 14 ++++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index b22c673e7c6..4b6a194ca8d 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -14,7 +14,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ActiveAuxiliaryContext, AuxiliaryBarFocusContext } from 'vs/workbench/common/contextkeys'; -import { ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_DRAG_AND_DROP_BORDER, PANEL_INACTIVE_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_BORDER, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; +import { ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_TOP_ACTIVE_BORDER, ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER, ACTIVITY_BAR_TOP_FOREGROUND, ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_DRAG_AND_DROP_BORDER, PANEL_INACTIVE_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_BORDER, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; @@ -169,12 +169,12 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { colors: theme => ({ activeBackgroundColor: theme.getColor(SIDE_BAR_BACKGROUND), inactiveBackgroundColor: theme.getColor(SIDE_BAR_BACKGROUND), - activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER), - activeForegroundColor: theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND), - inactiveForegroundColor: theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND), + get activeBorderBottomColor() { return $this.getCompositeBarPosition() === CompositeBarPosition.TITLE ? theme.getColor(PANEL_ACTIVE_TITLE_BORDER) : theme.getColor(ACTIVITY_BAR_TOP_ACTIVE_BORDER); }, + get activeForegroundColor() { return $this.getCompositeBarPosition() === CompositeBarPosition.TITLE ? theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND) : theme.getColor(ACTIVITY_BAR_TOP_FOREGROUND); }, + get inactiveForegroundColor() { return $this.getCompositeBarPosition() === CompositeBarPosition.TITLE ? theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND) : theme.getColor(ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND); }, badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), - dragAndDropBorder: theme.getColor(PANEL_DRAG_AND_DROP_BORDER) + get dragAndDropBorder() { return $this.getCompositeBarPosition() === CompositeBarPosition.TITLE ? theme.getColor(PANEL_DRAG_AND_DROP_BORDER) : theme.getColor(ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER); } }), compact: true }; @@ -205,6 +205,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) !== ActivityBarPosition.HIDDEN; } + // TODO@benibenj chache this protected getCompositeBarPosition(): CompositeBarPosition { const activityBarPosition = this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); switch (activityBarPosition) { diff --git a/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css b/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css index 21f22875e1f..580af0aa4b7 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css +++ b/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css @@ -51,20 +51,26 @@ left: 6px; /* place icon in center */ } -.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, -.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before { + border-top-color: var(--vscode-panelTitle-activeBorder) !important; +} + +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before { border-top-color: var(--vscode-activityBarTop-activeBorder) !important; } -.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, -.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label { color: var(--vscode-sideBarTitle-foreground) !important; } +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, +.monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label { + color: var(--vscode-activityBarTop-foreground) !important; +} + .monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, .monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, From 9ed28b5d674d01f475c98dd6a77139aaa838f291 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 26 Mar 2024 12:47:12 +0100 Subject: [PATCH 05/17] Fix Activity Bar Position Bottom with Menu (#208767) fix #207242 --- src/vs/base/browser/ui/menu/menu.ts | 22 +++++++++++----- src/vs/base/browser/ui/menu/menubar.ts | 25 ++++++++++++------- .../browser/parts/titlebar/menubarControl.ts | 12 ++++++--- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 187ac848e8f..41b406dc254 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -31,11 +31,21 @@ export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g; -export enum Direction { +export enum HorizontalDirection { Right, Left } +export enum VerticalDirection { + Above, + Below +} + +export interface IMenuDirection { + horizontal: HorizontalDirection; + vertical: VerticalDirection; +} + export interface IMenuOptions { context?: unknown; actionViewItemProvider?: IActionViewItemProvider; @@ -44,7 +54,7 @@ export interface IMenuOptions { ariaLabel?: string; enableMnemonics?: boolean; anchorAlignment?: AnchorAlignment; - expandDirection?: Direction; + expandDirection?: IMenuDirection; useEventAsContext?: boolean; submenuIds?: Set; } @@ -725,7 +735,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { private mouseOver: boolean = false; private showScheduler: RunOnceScheduler; private hideScheduler: RunOnceScheduler; - private expandDirection: Direction; + private expandDirection: IMenuDirection; constructor( action: IAction, @@ -736,7 +746,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { ) { super(action, action, submenuOptions, menuStyles); - this.expandDirection = submenuOptions && submenuOptions.expandDirection !== undefined ? submenuOptions.expandDirection : Direction.Right; + this.expandDirection = submenuOptions && submenuOptions.expandDirection !== undefined ? submenuOptions.expandDirection : { horizontal: HorizontalDirection.Right, vertical: VerticalDirection.Below }; this.showScheduler = new RunOnceScheduler(() => { if (this.mouseOver) { @@ -850,11 +860,11 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { } } - private calculateSubmenuMenuLayout(windowDimensions: Dimension, submenu: Dimension, entry: IDomNodePagePosition, expandDirection: Direction): { top: number; left: number } { + private calculateSubmenuMenuLayout(windowDimensions: Dimension, submenu: Dimension, entry: IDomNodePagePosition, expandDirection: IMenuDirection): { top: number; left: number } { const ret = { top: 0, left: 0 }; // Start with horizontal - ret.left = layout(windowDimensions.width, submenu.width, { position: expandDirection === Direction.Right ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, offset: entry.left, size: entry.width }); + ret.left = layout(windowDimensions.width, submenu.width, { position: expandDirection.horizontal === HorizontalDirection.Right ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, offset: entry.left, size: entry.width }); // We don't have enough room to layout the menu fully, so we are overlapping the menu if (ret.left >= entry.left && ret.left < entry.left + entry.width) { diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index feadeb47651..961fb8862a8 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -8,7 +8,7 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; -import { cleanMnemonic, Direction, IMenuOptions, IMenuStyles, Menu, MENU_ESCAPED_MNEMONIC_REGEX, MENU_MNEMONIC_REGEX } from 'vs/base/browser/ui/menu/menu'; +import { cleanMnemonic, HorizontalDirection, IMenuDirection, IMenuOptions, IMenuStyles, Menu, MENU_ESCAPED_MNEMONIC_REGEX, MENU_MNEMONIC_REGEX, VerticalDirection } from 'vs/base/browser/ui/menu/menu'; import { ActionRunner, IAction, IActionRunner, Separator, SubmenuAction } from 'vs/base/common/actions'; import { asArray } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -32,7 +32,7 @@ export interface IMenuBarOptions { visibility?: string; getKeybinding?: (action: IAction) => ResolvedKeybinding | undefined; alwaysOnMnemonics?: boolean; - compactMode?: Direction; + compactMode?: IMenuDirection; actionRunner?: IActionRunner; getCompactMenuActions?: () => IAction[]; } @@ -333,9 +333,9 @@ export class MenuBar extends Disposable { } else { triggerKeys.push(KeyCode.Space); - if (this.options.compactMode === Direction.Right) { + if (this.options.compactMode?.horizontal === HorizontalDirection.Right) { triggerKeys.push(KeyCode.RightArrow); - } else if (this.options.compactMode === Direction.Left) { + } else if (this.options.compactMode?.horizontal === HorizontalDirection.Left) { triggerKeys.push(KeyCode.LeftArrow); } } @@ -1007,18 +1007,25 @@ export class MenuBar extends Disposable { const titleBoundingRect = customMenu.titleElement.getBoundingClientRect(); const titleBoundingRectZoom = DOM.getDomNodeZoomLevel(customMenu.titleElement); - if (this.options.compactMode === Direction.Right) { - menuHolder.style.top = `${titleBoundingRect.top}px`; + if (this.options.compactMode?.horizontal === HorizontalDirection.Right) { menuHolder.style.left = `${titleBoundingRect.left + this.container.clientWidth}px`; - } else if (this.options.compactMode === Direction.Left) { + } else if (this.options.compactMode?.horizontal === HorizontalDirection.Left) { menuHolder.style.top = `${titleBoundingRect.top}px`; menuHolder.style.right = `${this.container.clientWidth}px`; menuHolder.style.left = 'auto'; } else { - menuHolder.style.top = `${titleBoundingRect.bottom * titleBoundingRectZoom}px`; menuHolder.style.left = `${titleBoundingRect.left * titleBoundingRectZoom}px`; } + if (this.options.compactMode?.vertical === VerticalDirection.Above) { + // TODO@benibenj Do not hardcode the height of the menu holder + menuHolder.style.top = `${titleBoundingRect.top - this.menus.length * 30 + this.container.clientHeight}px`; + } else if (this.options.compactMode?.vertical === VerticalDirection.Below) { + menuHolder.style.top = `${titleBoundingRect.top}px`; + } else { + menuHolder.style.top = `${titleBoundingRect.bottom * titleBoundingRectZoom}px`; + } + customMenu.buttonElement.appendChild(menuHolder); const menuOptions: IMenuOptions = { @@ -1026,7 +1033,7 @@ export class MenuBar extends Disposable { actionRunner: this.actionRunner, enableMnemonics: this.options.alwaysOnMnemonics || (this.mnemonicsInUse && this.options.enableMnemonics), ariaLabel: customMenu.buttonElement.getAttribute('aria-label') ?? undefined, - expandDirection: this.isCompact ? this.options.compactMode : Direction.Right, + expandDirection: this.isCompact ? this.options.compactMode : { horizontal: HorizontalDirection.Right, vertical: VerticalDirection.Below }, useEventAsContext: true }; diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 1453c7d8eeb..5b8cf1a3a7e 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -25,7 +25,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { MenuBar, IMenuBarOptions } from 'vs/base/browser/ui/menu/menubar'; -import { Direction } from 'vs/base/browser/ui/menu/menu'; +import { HorizontalDirection, IMenuDirection, VerticalDirection } from 'vs/base/browser/ui/menu/menu'; import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { isFullscreen, onDidChangeFullscreen } from 'vs/base/browser/browser'; @@ -41,6 +41,7 @@ import { isICommandActionToggleInfo } from 'vs/platform/action/common/action'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { defaultMenuStyles } from 'vs/platform/theme/browser/defaultStyles'; import { mainWindow } from 'vs/base/browser/window'; +import { ActivityBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; export type IOpenRecentAction = IAction & { uri: URI; remoteAuthority?: string }; @@ -536,14 +537,19 @@ export class CustomMenubarControl extends MenubarControl { return enableMenuBarMnemonics && (!isWeb || isFullscreen(mainWindow)); } - private get currentCompactMenuMode(): Direction | undefined { + private get currentCompactMenuMode(): IMenuDirection | undefined { if (this.currentMenubarVisibility !== 'compact') { return undefined; } // Menu bar lives in activity bar and should flow based on its location const currentSidebarLocation = this.configurationService.getValue('workbench.sideBar.location'); - return currentSidebarLocation === 'right' ? Direction.Left : Direction.Right; + const horizontalDirection = currentSidebarLocation === 'right' ? HorizontalDirection.Left : HorizontalDirection.Right; + + const activityBarLocation = this.configurationService.getValue('workbench.activityBar.location'); + const verticalDirection = activityBarLocation === ActivityBarPosition.BOTTOM ? VerticalDirection.Above : VerticalDirection.Below; + + return { horizontal: horizontalDirection, vertical: verticalDirection }; } private onDidVisibilityChange(visible: boolean): void { From 1bcfc3e9e1dec9ac654cea28f2041a422c1a4889 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 26 Mar 2024 06:51:48 -0700 Subject: [PATCH 06/17] fix #208731 --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index e166624b6e8..4ccb7167f21 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -159,7 +159,7 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.focus(); } focusResponse(): void { - const responseElement = this._inlineChatWidget.domNode.querySelector(ChatElementSelectors.ResponseEditor) || this._inlineChatWidget.domNode.querySelector(ChatElementSelectors.ResponseMessage); + const responseElement = this._inlineChatWidget.domNode.querySelector('.monaco-list'); if (responseElement instanceof HTMLElement) { responseElement.focus(); } @@ -190,7 +190,3 @@ export class TerminalChatWidget extends Disposable { } } -const enum ChatElementSelectors { - ResponseEditor = '.chatMessageContent textarea', - ResponseMessage = '.chatMessageContent', -} From 1591ace90eeb344fc23f95a9c4f762abb086bd38 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 26 Mar 2024 15:07:20 +0100 Subject: [PATCH 07/17] More inline chat fixes (#208780) * disable send dropdown menu rendering for anything but panel chat location fixes https://github.com/microsoft/vscode/issues/208569 * fix https://github.com/microsoft/vscode/issues/208634 --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 8 +++++--- .../contrib/inlineChat/browser/inlineChatWidget.ts | 9 +++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index bd06bef78ac..55987e87bcc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -372,9 +372,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }, hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu actionViewItemProvider: (action, options) => { - if ((action.id === SubmitAction.ID || action.id === CancelAction.ID) && action instanceof MenuItemAction) { - const dropdownAction = this.instantiationService.createInstance(MenuItemAction, { id: 'chat.moreExecuteActions', title: localize('notebook.moreExecuteActionsLabel', "More..."), icon: Codicon.chevronDown }, undefined, undefined, undefined); - return this.instantiationService.createInstance(ChatSubmitDropdownActionItem, action, dropdownAction); + if (this.location === ChatAgentLocation.Panel) { + if ((action.id === SubmitAction.ID || action.id === CancelAction.ID) && action instanceof MenuItemAction) { + const dropdownAction = this.instantiationService.createInstance(MenuItemAction, { id: 'chat.moreExecuteActions', title: localize('notebook.moreExecuteActionsLabel', "More..."), icon: Codicon.chevronDown }, undefined, undefined, undefined); + return this.instantiationService.createInstance(ChatSubmitDropdownActionItem, action, dropdownAction); + } } return undefined; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 9b96f16081a..ae07dfc2ca5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -553,9 +553,10 @@ export class InlineChatWidget { const isTempMessage = typeof ops.resetAfter === 'number'; if (isTempMessage && !this._elements.statusLabel.dataset['state']) { const statusLabel = this._elements.statusLabel.innerText; + const title = this._elements.statusLabel.dataset['title']; const classes = Array.from(this._elements.statusLabel.classList.values()); setTimeout(() => { - this.updateStatus(statusLabel, { classes, keepMessage: true }); + this.updateStatus(statusLabel, { classes, keepMessage: true, title }); }, ops.resetAfter); } const renderedMessage = renderLabelWithIcons(message); @@ -568,7 +569,11 @@ export class InlineChatWidget { delete this._elements.statusLabel.dataset['state']; } - this._elements.statusLabel.dataset['title'] = ops.title; + if (ops.title) { + this._elements.statusLabel.dataset['title'] = ops.title; + } else { + delete this._elements.statusLabel.dataset['title']; + } this._onDidChangeHeight.fire(); } From c0c2a8ae723bc95ea302a955a5e287b00f81d4c2 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 26 Mar 2024 07:33:11 -0700 Subject: [PATCH 08/17] use focus last message --- .../terminalContrib/chat/browser/terminalChatActions.ts | 2 +- .../terminalContrib/chat/browser/terminalChatWidget.ts | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 52b45f38e57..719bd28e497 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -91,7 +91,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.focusResponse(); + contr?.chatWidget?.inlineChatWidget.chatWidget.focusLastMessage(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4ccb7167f21..360f6d0d20f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -158,12 +158,6 @@ export class TerminalChatWidget extends Disposable { focus(): void { this._inlineChatWidget.focus(); } - focusResponse(): void { - const responseElement = this._inlineChatWidget.domNode.querySelector('.monaco-list'); - if (responseElement instanceof HTMLElement) { - responseElement.focus(); - } - } hasFocus(): boolean { return this._inlineChatWidget.hasFocus(); } From 1a54634c51d555816897d44444c748ad9e2e9618 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 26 Mar 2024 15:52:40 +0100 Subject: [PATCH 09/17] open a file on a different path but with same name fails (#208785) Fixes #204886 --- .../dialogs/browser/simpleFileDialog.ts | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index df3ba5b96f6..f8380ed0204 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -581,40 +581,46 @@ export class SimpleFileDialog implements ISimpleFileDialog { valueUri = this.root(this.currentFolder); value = this.pathFromUri(valueUri); return await this.updateItems(valueUri, true) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; - } else if (!resources.extUriIgnorePathCase.isEqual(this.currentFolder, valueUri) && (this.endsWithSlash(value) || (!resources.extUriIgnorePathCase.isEqual(this.currentFolder, resources.dirname(valueUri)) && resources.extUriIgnorePathCase.isEqualOrParent(this.currentFolder, resources.dirname(valueUri))))) { - let stat: IFileStatWithPartialMetadata | undefined; - try { - stat = await this.fileService.stat(valueUri); - } catch (e) { - // do nothing - } - if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.') && this.endsWithSlash(value)) { - valueUri = this.tryAddTrailingSeparatorToDirectory(valueUri, stat); - return await this.updateItems(valueUri) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; - } else if (this.endsWithSlash(value)) { - // The input box contains a path that doesn't exist on the system. - this.filePickBox.validationMessage = nls.localize('remoteFileDialog.badPath', 'The path does not exist.'); - // Save this bad path. It can take too long to a stat on every user entered character, but once a user enters a bad path they are likely - // to keep typing more bad path. We can compare against this bad path and see if the user entered path starts with it. - this.badPath = value; - return UpdateResult.InvalidPath; - } else { - let inputUriDirname = resources.dirname(valueUri); - const currentFolderWithoutSep = resources.removeTrailingPathSeparator(resources.addTrailingPathSeparator(this.currentFolder)); - const inputUriDirnameWithoutSep = resources.removeTrailingPathSeparator(resources.addTrailingPathSeparator(inputUriDirname)); - if (!resources.extUriIgnorePathCase.isEqual(currentFolderWithoutSep, inputUriDirnameWithoutSep) - && (!/^[a-zA-Z]:$/.test(this.filePickBox.value) - || !equalsIgnoreCase(this.pathFromUri(this.currentFolder).substring(0, this.filePickBox.value.length), this.filePickBox.value))) { - let statWithoutTrailing: IFileStatWithPartialMetadata | undefined; - try { - statWithoutTrailing = await this.fileService.stat(inputUriDirname); - } catch (e) { - // do nothing - } - if (statWithoutTrailing && statWithoutTrailing.isDirectory) { - this.badPath = undefined; - inputUriDirname = this.tryAddTrailingSeparatorToDirectory(inputUriDirname, statWithoutTrailing); - return await this.updateItems(inputUriDirname, false, resources.basename(valueUri)) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; + } else { + const newFolderIsOldFolder = resources.extUriIgnorePathCase.isEqual(this.currentFolder, valueUri); + const newFolderIsSubFolder = resources.extUriIgnorePathCase.isEqual(this.currentFolder, resources.dirname(valueUri)); + const newFolderIsParent = !newFolderIsOldFolder && resources.extUriIgnorePathCase.isEqualOrParent(this.currentFolder, resources.dirname(valueUri)); + const newFolderIsUnrelated = !newFolderIsOldFolder && !newFolderIsParent && !newFolderIsSubFolder; + if (this.endsWithSlash(value) || newFolderIsParent || newFolderIsUnrelated) { + let stat: IFileStatWithPartialMetadata | undefined; + try { + stat = await this.fileService.stat(valueUri); + } catch (e) { + // do nothing + } + if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.') && this.endsWithSlash(value)) { + valueUri = this.tryAddTrailingSeparatorToDirectory(valueUri, stat); + return await this.updateItems(valueUri) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; + } else if (this.endsWithSlash(value)) { + // The input box contains a path that doesn't exist on the system. + this.filePickBox.validationMessage = nls.localize('remoteFileDialog.badPath', 'The path does not exist.'); + // Save this bad path. It can take too long to a stat on every user entered character, but once a user enters a bad path they are likely + // to keep typing more bad path. We can compare against this bad path and see if the user entered path starts with it. + this.badPath = value; + return UpdateResult.InvalidPath; + } else { + let inputUriDirname = resources.dirname(valueUri); + const currentFolderWithoutSep = resources.removeTrailingPathSeparator(resources.addTrailingPathSeparator(this.currentFolder)); + const inputUriDirnameWithoutSep = resources.removeTrailingPathSeparator(resources.addTrailingPathSeparator(inputUriDirname)); + if (!resources.extUriIgnorePathCase.isEqual(currentFolderWithoutSep, inputUriDirnameWithoutSep) + && (!/^[a-zA-Z]:$/.test(this.filePickBox.value) + || !equalsIgnoreCase(this.pathFromUri(this.currentFolder).substring(0, this.filePickBox.value.length), this.filePickBox.value))) { + let statWithoutTrailing: IFileStatWithPartialMetadata | undefined; + try { + statWithoutTrailing = await this.fileService.stat(inputUriDirname); + } catch (e) { + // do nothing + } + if (statWithoutTrailing && statWithoutTrailing.isDirectory) { + this.badPath = undefined; + inputUriDirname = this.tryAddTrailingSeparatorToDirectory(inputUriDirname, statWithoutTrailing); + return await this.updateItems(inputUriDirname, false, resources.basename(valueUri)) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; + } } } } From 38695f10bee6442b2b8113dfb9ef9c151007aa90 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 26 Mar 2024 15:32:47 +0100 Subject: [PATCH 10/17] Fixes #208615 --- .../browser/widget/diffEditor/features/gutterFeature.ts | 2 +- src/vs/editor/common/diff/rangeMapping.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts index acfc2b0c917..8410b6eb85b 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts @@ -84,7 +84,7 @@ export class DiffEditorGutter extends Disposable { const currentDiff = this._currentDiff.read(reader); return diffs.mappings.map(m => new DiffGutterItem( - m.lineRangeMapping, + m.lineRangeMapping.withInnerChangesFromLineRanges(), m.lineRangeMapping === currentDiff?.lineRangeMapping, MenuId.DiffEditorHunkToolbar, undefined, diff --git a/src/vs/editor/common/diff/rangeMapping.ts b/src/vs/editor/common/diff/rangeMapping.ts index 1d8b154367e..810df11032f 100644 --- a/src/vs/editor/common/diff/rangeMapping.ts +++ b/src/vs/editor/common/diff/rangeMapping.ts @@ -118,6 +118,12 @@ export class DetailedLineRangeMapping extends LineRangeMapping { public override flip(): DetailedLineRangeMapping { return new DetailedLineRangeMapping(this.modified, this.original, this.innerChanges?.map(c => c.flip())); } + + public withInnerChangesFromLineRanges(): DetailedLineRangeMapping { + return new DetailedLineRangeMapping(this.original, this.modified, [ + new RangeMapping(this.original.toExclusiveRange(), this.modified.toExclusiveRange()), + ]); + } } /** From 52d017663b1746b459d001c2b776c897e1995292 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 26 Mar 2024 08:15:03 -0700 Subject: [PATCH 11/17] fix issue with kb conflict --- .../terminalContrib/chat/browser/terminalChatActions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 52b45f38e57..93b99fcaa19 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -100,7 +100,8 @@ registerActiveXtermAction({ title: localize2('focusTerminalInput', 'Focus Terminal Input'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - when: TerminalChatContextKeys.focused, + secondary: [KeyMod.CtrlCmd | KeyCode.KeyI], + when: ContextKeyExpr.and(TerminalChatContextKeys.focused, CTX_INLINE_CHAT_FOCUSED.toNegated()), weight: KeybindingWeight.WorkbenchContrib, }, f1: true, From a6c7311730e98b829ad834fd4f7154fb0cc12397 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 26 Mar 2024 08:28:38 -0700 Subject: [PATCH 12/17] fix small issues w accessibiliity help --- .../contrib/accessibility/browser/accessibleView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 862202d57cf..b64f3dc5e62 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -684,7 +684,7 @@ export class AccessibleView extends Disposable { private _getAccessibleViewHelpDialogContent(providerHasSymbols?: boolean): string { const navigationHint = this._getNavigationHint(); const goToSymbolHint = this._getGoToSymbolHint(providerHasSymbols); - const toolbarHint = localize('toolbar', "Navigate to the toolbar (Shift+Tab))."); + const toolbarHint = localize('toolbar', "Navigate to the toolbar (Shift+Tab)."); const chatHints = this._getChatHints(); let hint = localize('intro', "In the accessible view, you can:\n"); @@ -720,7 +720,7 @@ export class AccessibleView extends Disposable { if (insertIntoNewFileKb) { hint += localize('insertIntoNewFile', " - Insert the code block into a new file ({0}).\n", insertIntoNewFileKb); } else { - hint += localize('insertIntoNewFileNoKb', " - Insert the code block into a new file by configuring a keybinding for the Chat: Insert at Cursor command.\n"); + hint += localize('insertIntoNewFileNoKb', " - Insert the code block into a new file by configuring a keybinding for the Chat: Insert into New File command.\n"); } if (runInTerminalKb) { hint += localize('runInTerminal', " - Run the code block in the terminal ({0}).\n", runInTerminalKb); @@ -761,7 +761,7 @@ export class AccessibleView extends Disposable { let goToSymbolHint = ''; if (providerHasSymbols) { if (goToSymbolKb) { - goToSymbolHint = localize('goToSymbolHint', 'Go to a symbol ({0})', goToSymbolKb); + goToSymbolHint = localize('goToSymbolHint', 'Go to a symbol ({0}).', goToSymbolKb); } else { goToSymbolHint = localize('goToSymbolHintNoKb', 'To go to a symbol, configure a keybinding for the command Go To Symbol in Accessible View'); } From 62d83b2a756a8f231fcbd8e06fdcaf0ca629ee2c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 26 Mar 2024 16:36:37 +0100 Subject: [PATCH 13/17] voice - address some action integration issues for terminal inline chat (#208792) --- .../actions/voiceChatActions.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) 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 9207fd2ed42..a7cb1b2a804 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -104,6 +104,15 @@ class VoiceChatSessionControllerFactory { // Currently Focused Context if (context === 'focused') { + // Try with the terminal chat + const activeInstance = terminalService.activeInstance; + if (activeInstance) { + const terminalChat = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + if (terminalChat?.hasFocus()) { + return VoiceChatSessionControllerFactory.doCreateForTerminalChat(terminalChat); + } + } + // Try with the chat widget service, which currently // only supports the chat view and quick chat // https://github.com/microsoft/vscode/issues/191191 @@ -134,15 +143,6 @@ class VoiceChatSessionControllerFactory { return VoiceChatSessionControllerFactory.doCreateForInlineChat(inlineChat); } } - - // Try with the terminal chat - const activeInstance = terminalService.activeInstance; - if (activeInstance) { - const terminalChat = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - if (terminalChat?.hasFocus()) { - return VoiceChatSessionControllerFactory.doCreateForTerminalChat(terminalChat); - } - } } // View Chat @@ -699,7 +699,6 @@ class BaseStopListeningAction extends Action2 { private readonly target: 'inline' | 'terminal' | 'quick' | 'view' | 'editor' | undefined, context: RawContextKey, menu: MenuId | undefined, - group: 'navigation' | 'main' = 'navigation' ) { super({ ...desc, @@ -713,7 +712,7 @@ class BaseStopListeningAction extends Action2 { menu: menu ? [{ id: menu, when: ContextKeyExpr.and(CanVoiceChat, context), - group, + group: 'navigation', order: -1 }] : undefined }); @@ -765,7 +764,7 @@ export class StopListeningInTerminalChatAction extends BaseStopListeningAction { static readonly ID = 'workbench.action.chat.stopListeningInTerminalChat'; constructor() { - super({ id: StopListeningInTerminalChatAction.ID, icon: spinningLoading }, 'terminal', CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS, MenuId.for('terminalChatInput'), 'main'); + super({ id: StopListeningInTerminalChatAction.ID, icon: spinningLoading }, 'terminal', CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS, MenuId.for('terminalChatInput')); } } @@ -828,7 +827,7 @@ registerThemingParticipant((theme, collector) => { } .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after, .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { outline: 2px solid ${activeRecordingColor}; outline-offset: -1px; @@ -836,7 +835,8 @@ registerThemingParticipant((theme, collector) => { } .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, + .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { position: absolute; outline: 1px solid ${activeRecordingColor}; outline-offset: 2px; From d0851d5aaec3a5bbc25bdcf533bc1ea3ed31234d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 26 Mar 2024 08:39:19 -0700 Subject: [PATCH 14/17] Shell integration docs and readonly Addressing feedback in #208257 --- ...ode.proposed.terminalShellIntegration.d.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.terminalShellIntegration.d.ts b/src/vscode-dts/vscode.proposed.terminalShellIntegration.d.ts index 9296c957bad..1309a2ce0ec 100644 --- a/src/vscode-dts/vscode.proposed.terminalShellIntegration.d.ts +++ b/src/vscode-dts/vscode.proposed.terminalShellIntegration.d.ts @@ -12,7 +12,7 @@ declare module 'vscode' { /** * The {@link Terminal} the command was executed in. */ - terminal: Terminal; + readonly terminal: Terminal; /** * The full command line that was executed, including both the command and arguments. @@ -26,7 +26,7 @@ declare module 'vscode' { * - It may be inaccurate if the shell integration does not support command line reporting * via the [`OSC 633 ; E` sequence](https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st). */ - commandLine: string | undefined; + readonly commandLine: string | undefined; /** * The working directory that was reported by the shell when this command executed. This @@ -35,12 +35,12 @@ declare module 'vscode' { * reporting via the [`OSC 633 ; P`](https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st) * or `OSC 1337 ; CurrentDir= ST` sequences. */ - cwd: Uri | string | undefined; + readonly cwd: Uri | string | undefined; /** * The exit code reported by the shell. */ - exitCode: Thenable; + readonly exitCode: Thenable; /** * Creates a stream of raw data (including escape sequences) that is written to the @@ -65,8 +65,12 @@ declare module 'vscode' { * features for the terminal. This will always be undefined immediately after the terminal * is created. Listen to {@link window.onDidActivateTerminalShellIntegration} to be notified * when shell integration is activated for a terminal. + * + * Note that this object may remain undefined if shell integation never activates. For + * example Command Prompt does not support shell integration and a user's shell setup could + * conflict with the automatic shell integration activation. */ - shellIntegration: TerminalShellIntegration | undefined; + readonly shellIntegration: TerminalShellIntegration | undefined; } export interface TerminalShellIntegration { @@ -75,7 +79,7 @@ declare module 'vscode' { * The current working directory of the terminal. This will be a {@link Uri} if the string * reported by the shell can reliably be mapped to the connected machine. */ - cwd: Uri | string | undefined; + readonly cwd: Uri | string | undefined; /** * Execute a command, sending ^C as necessary to interrupt any running command if needed. @@ -177,11 +181,11 @@ declare module 'vscode' { /** * The terminal that shell integration has been activated in. */ - terminal: Terminal; + readonly terminal: Terminal; /** * The shell integration object. */ - shellIntegration: TerminalShellIntegration; + readonly shellIntegration: TerminalShellIntegration; } export namespace window { From 86c791ebb28a9b493f05a5c9274cff0abfa2b631 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Tue, 26 Mar 2024 08:43:28 -0700 Subject: [PATCH 15/17] clear diagnostics on delete, no diagnostics for IW cells (#208794) --- .../cellDiagnostics/cellDiagnostics.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnostics.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnostics.ts index 3da0a85f2d2..d04037b939f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnostics.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnostics.ts @@ -38,14 +38,16 @@ export class CellDiagnostics extends Disposable { ) { super(); - this.updateEnabled(); + if (cell.viewType !== 'interactive') { + this.updateEnabled(); - this._register(inlineChatService.onDidChangeProviders(() => this.updateEnabled())); - this._register(configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration(NotebookSetting.cellFailureDiagnostics)) { - this.updateEnabled(); - } - })); + this._register(inlineChatService.onDidChangeProviders(() => this.updateEnabled())); + this._register(configurationService.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration(NotebookSetting.cellFailureDiagnostics)) { + this.updateEnabled(); + } + })); + } } private updateEnabled() { @@ -103,4 +105,9 @@ export class CellDiagnostics extends Disposable { }; } + override dispose() { + super.dispose(); + this.clear(); + } + } From 3574756bf43f5456707a47e6aaa260944dded0e1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 26 Mar 2024 16:56:30 +0100 Subject: [PATCH 16/17] voice - remove now obsolete CSS rules (#208797) --- .../actions/media/voiceChatActions.css | 12 +++--------- .../electron-sandbox/actions/voiceChatActions.ts | 16 ++++------------ 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css b/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css index 3411d749fce..f386a4a0089 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css @@ -6,9 +6,7 @@ /* * Replace with "microphone" icon. */ -.monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, -.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, -.monaco-workbench .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { +.monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { content: "\ec1c"; font-family: 'codicon'; } @@ -16,18 +14,14 @@ /* * Clear animation styles when reduced motion is enabled. */ -.monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), -.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), -.monaco-workbench.reduce-motion .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { +.monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { animation: none; } /* * Replace with "stop" icon when reduced motion is enabled. */ -.monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, -.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, -.monaco-workbench.reduce-motion .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { +.monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { content: "\ead7"; font-family: 'codicon'; } 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 a7cb1b2a804..31968a7bbec 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -805,9 +805,7 @@ registerThemingParticipant((theme, collector) => { // Show a "microphone" icon when recording is in progress that glows via outline. collector.addRule(` - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), - .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { + .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { color: ${activeRecordingColor}; outline: 1px solid ${activeRecordingColor}; outline-offset: -1px; @@ -815,9 +813,7 @@ registerThemingParticipant((theme, collector) => { border-radius: 50%; } - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, - .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { + .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { position: absolute; outline: 1px solid ${activeRecordingColor}; outline-offset: 2px; @@ -826,17 +822,13 @@ registerThemingParticipant((theme, collector) => { height: 16px; } - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after, - .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { + .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { outline: 2px solid ${activeRecordingColor}; outline-offset: -1px; animation: pulseAnimation 1500ms cubic-bezier(0.75, 0, 0.25, 1) infinite; } - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, - .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { + .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { position: absolute; outline: 1px solid ${activeRecordingColor}; outline-offset: 2px; From 1a2cc56282f0ded2a2cce279c185d145ed4f20e9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 26 Mar 2024 17:04:13 +0100 Subject: [PATCH 17/17] Voice: terminal controller is not behaving like view/inline (fix #208791) (#208800) --- .../terminalContrib/chat/browser/terminalChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 6d0b6287ddb..8d7f7ddd5fa 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -261,6 +261,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr throw new Error('Could not start chat session'); } } + this._messages.fire(Message.ACCEPT_INPUT); const model = this._model.value; this._lastInput = this._chatWidget?.value?.input(); @@ -326,7 +327,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.value.inlineChatWidget.updateChatMessage({ message: new MarkdownString(responseContent), requestId: this._currentRequest.id, providerId: 'terminal' }, false, containsCode); this._responseContainsCodeBlockContextKey.set(containsCode); this._chatWidget?.value.inlineChatWidget.updateToolbar(true); - this._messages.fire(Message.ACCEPT_INPUT); } const supportIssueReporting = this._currentRequest?.response?.agent?.metadata?.supportIssueReporting; if (supportIssueReporting !== undefined) {