mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
Merge branch 'main' into benibenj/interested-slug
This commit is contained in:
@@ -650,8 +650,6 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP
|
||||
async function getSchemaAssociations(forceRefresh: boolean): Promise<ISchemaAssociation[]> {
|
||||
if (!schemaAssociationsCache || forceRefresh) {
|
||||
schemaAssociationsCache = computeSchemaAssociations();
|
||||
runtime.logOutputChannel.info(`Computed schema associations: ${(await schemaAssociationsCache).map(a => `${a.uri} -> [${a.fileMatch.join(', ')}]`).join('\n')}`);
|
||||
|
||||
}
|
||||
return schemaAssociationsCache;
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.109.0",
|
||||
"distro": "84c8b9580d546487fee8ff25a29c5f3f49d33799",
|
||||
"distro": "6c9f72a1ba8565301b303ec4314f5a24d585f012",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -240,4 +240,4 @@
|
||||
"optionalDependencies": {
|
||||
"windows-foreground-love": "0.6.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,11 @@ import { BrowserFeatures } from '../../canIUse.js';
|
||||
import * as DOM from '../../dom.js';
|
||||
import { StandardMouseEvent } from '../../mouseEvent.js';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../common/lifecycle.js';
|
||||
import { AnchorAlignment, AnchorAxisAlignment, AnchorPosition, IRect, layout2d } from '../../../common/layout.js';
|
||||
import * as platform from '../../../common/platform.js';
|
||||
import { Range } from '../../../common/range.js';
|
||||
import { OmitOptional } from '../../../common/types.js';
|
||||
import './contextview.css';
|
||||
|
||||
export { AnchorAlignment, AnchorAxisAlignment, AnchorPosition } from '../../../common/layout.js';
|
||||
|
||||
export const enum ContextViewDOMPosition {
|
||||
ABSOLUTE = 1,
|
||||
FIXED,
|
||||
@@ -33,6 +31,18 @@ export function isAnchor(obj: unknown): obj is IAnchor | OmitOptional<IAnchor> {
|
||||
return !!anchor && typeof anchor.x === 'number' && typeof anchor.y === 'number';
|
||||
}
|
||||
|
||||
export const enum AnchorAlignment {
|
||||
LEFT, RIGHT
|
||||
}
|
||||
|
||||
export const enum AnchorPosition {
|
||||
BELOW, ABOVE
|
||||
}
|
||||
|
||||
export const enum AnchorAxisAlignment {
|
||||
VERTICAL, HORIZONTAL
|
||||
}
|
||||
|
||||
export interface IDelegate {
|
||||
/**
|
||||
* The anchor where to position the context view.
|
||||
@@ -63,40 +73,66 @@ export interface IContextViewProvider {
|
||||
layout(): void;
|
||||
}
|
||||
|
||||
export function getAnchorRect(anchor: HTMLElement | StandardMouseEvent | IAnchor): IRect {
|
||||
// Get the element's position and size (to anchor the view)
|
||||
if (DOM.isHTMLElement(anchor)) {
|
||||
const elementPosition = DOM.getDomNodePagePosition(anchor);
|
||||
export interface IPosition {
|
||||
top: number;
|
||||
left: number;
|
||||
}
|
||||
|
||||
// In areas where zoom is applied to the element or its ancestors, we need to adjust the size of the element
|
||||
// e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level.
|
||||
// Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Size Multiplier: 1.5
|
||||
const zoom = DOM.getDomNodeZoomLevel(anchor);
|
||||
export interface ISize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
return {
|
||||
top: elementPosition.top * zoom,
|
||||
left: elementPosition.left * zoom,
|
||||
width: elementPosition.width * zoom,
|
||||
height: elementPosition.height * zoom
|
||||
};
|
||||
} else if (isAnchor(anchor)) {
|
||||
return {
|
||||
top: anchor.y,
|
||||
left: anchor.x,
|
||||
width: anchor.width || 1,
|
||||
height: anchor.height || 2
|
||||
};
|
||||
export interface IView extends IPosition, ISize { }
|
||||
|
||||
export const enum LayoutAnchorPosition {
|
||||
Before,
|
||||
After
|
||||
}
|
||||
|
||||
export enum LayoutAnchorMode {
|
||||
AVOID,
|
||||
ALIGN
|
||||
}
|
||||
|
||||
export interface ILayoutAnchor {
|
||||
offset: number;
|
||||
size: number;
|
||||
mode?: LayoutAnchorMode; // default: AVOID
|
||||
position: LayoutAnchorPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lays out a one dimensional view next to an anchor in a viewport.
|
||||
*
|
||||
* @returns The view offset within the viewport.
|
||||
*/
|
||||
export function layout(viewportSize: number, viewSize: number, anchor: ILayoutAnchor): number {
|
||||
const layoutAfterAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset : anchor.offset + anchor.size;
|
||||
const layoutBeforeAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset + anchor.size : anchor.offset;
|
||||
|
||||
if (anchor.position === LayoutAnchorPosition.Before) {
|
||||
if (viewSize <= viewportSize - layoutAfterAnchorBoundary) {
|
||||
return layoutAfterAnchorBoundary; // happy case, lay it out after the anchor
|
||||
}
|
||||
|
||||
if (viewSize <= layoutBeforeAnchorBoundary) {
|
||||
return layoutBeforeAnchorBoundary - viewSize; // ok case, lay it out before the anchor
|
||||
}
|
||||
|
||||
return Math.max(viewportSize - viewSize, 0); // sad case, lay it over the anchor
|
||||
} else {
|
||||
return {
|
||||
top: anchor.posy,
|
||||
left: anchor.posx,
|
||||
// We are about to position the context view where the mouse
|
||||
// cursor is. To prevent the view being exactly under the mouse
|
||||
// when showing and thus potentially triggering an action within,
|
||||
// we treat the mouse location like a small sized block element.
|
||||
width: 2,
|
||||
height: 2
|
||||
};
|
||||
if (viewSize <= layoutBeforeAnchorBoundary) {
|
||||
return layoutBeforeAnchorBoundary - viewSize; // happy case, lay it out before the anchor
|
||||
}
|
||||
|
||||
|
||||
if (viewSize <= viewportSize - layoutAfterAnchorBoundary && layoutBeforeAnchorBoundary < viewSize / 2) {
|
||||
return layoutAfterAnchorBoundary; // ok case, lay it out after the anchor
|
||||
}
|
||||
|
||||
|
||||
return 0; // sad case, lay it over the anchor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,14 +270,82 @@ export class ContextView extends Disposable {
|
||||
}
|
||||
|
||||
// Get anchor
|
||||
const anchor = getAnchorRect(this.delegate!.getAnchor());
|
||||
const anchor = this.delegate!.getAnchor();
|
||||
|
||||
// Compute around
|
||||
let around: IView;
|
||||
|
||||
// Get the element's position and size (to anchor the view)
|
||||
if (DOM.isHTMLElement(anchor)) {
|
||||
const elementPosition = DOM.getDomNodePagePosition(anchor);
|
||||
|
||||
// In areas where zoom is applied to the element or its ancestors, we need to adjust the size of the element
|
||||
// e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level.
|
||||
// Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Size Multiplier: 1.5
|
||||
const zoom = DOM.getDomNodeZoomLevel(anchor);
|
||||
|
||||
around = {
|
||||
top: elementPosition.top * zoom,
|
||||
left: elementPosition.left * zoom,
|
||||
width: elementPosition.width * zoom,
|
||||
height: elementPosition.height * zoom
|
||||
};
|
||||
} else if (isAnchor(anchor)) {
|
||||
around = {
|
||||
top: anchor.y,
|
||||
left: anchor.x,
|
||||
width: anchor.width || 1,
|
||||
height: anchor.height || 2
|
||||
};
|
||||
} else {
|
||||
around = {
|
||||
top: anchor.posy,
|
||||
left: anchor.posx,
|
||||
// We are about to position the context view where the mouse
|
||||
// cursor is. To prevent the view being exactly under the mouse
|
||||
// when showing and thus potentially triggering an action within,
|
||||
// we treat the mouse location like a small sized block element.
|
||||
width: 2,
|
||||
height: 2
|
||||
};
|
||||
}
|
||||
|
||||
const viewSizeWidth = DOM.getTotalWidth(this.view);
|
||||
const viewSizeHeight = DOM.getTotalHeight(this.view);
|
||||
|
||||
const anchorPosition = this.delegate!.anchorPosition ?? AnchorPosition.BELOW;
|
||||
const anchorAlignment = this.delegate!.anchorAlignment ?? AnchorAlignment.LEFT;
|
||||
const anchorAxisAlignment = this.delegate!.anchorAxisAlignment ?? AnchorAxisAlignment.VERTICAL;
|
||||
|
||||
let top: number;
|
||||
let left: number;
|
||||
|
||||
const activeWindow = DOM.getActiveWindow();
|
||||
const viewport = { top: activeWindow.pageYOffset, left: activeWindow.pageXOffset, width: activeWindow.innerWidth, height: activeWindow.innerHeight };
|
||||
const view = { width: DOM.getTotalWidth(this.view), height: DOM.getTotalHeight(this.view) };
|
||||
const anchorPosition = this.delegate!.anchorPosition;
|
||||
const anchorAlignment = this.delegate!.anchorAlignment;
|
||||
const anchorAxisAlignment = this.delegate!.anchorAxisAlignment;
|
||||
const { top, left } = layout2d(viewport, view, anchor, { anchorAlignment, anchorPosition, anchorAxisAlignment });
|
||||
if (anchorAxisAlignment === AnchorAxisAlignment.VERTICAL) {
|
||||
const verticalAnchor: ILayoutAnchor = { offset: around.top - activeWindow.pageYOffset, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
|
||||
const horizontalAnchor: ILayoutAnchor = { offset: around.left, size: around.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN };
|
||||
|
||||
top = layout(activeWindow.innerHeight, viewSizeHeight, verticalAnchor) + activeWindow.pageYOffset;
|
||||
|
||||
// if view intersects vertically with anchor, we must avoid the anchor
|
||||
if (Range.intersects({ start: top, end: top + viewSizeHeight }, { start: verticalAnchor.offset, end: verticalAnchor.offset + verticalAnchor.size })) {
|
||||
horizontalAnchor.mode = LayoutAnchorMode.AVOID;
|
||||
}
|
||||
|
||||
left = layout(activeWindow.innerWidth, viewSizeWidth, horizontalAnchor);
|
||||
} else {
|
||||
const horizontalAnchor: ILayoutAnchor = { offset: around.left, size: around.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
|
||||
const verticalAnchor: ILayoutAnchor = { offset: around.top, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN };
|
||||
|
||||
left = layout(activeWindow.innerWidth, viewSizeWidth, horizontalAnchor);
|
||||
|
||||
// if view intersects horizontally with anchor, we must avoid the anchor
|
||||
if (Range.intersects({ start: left, end: left + viewSizeWidth }, { start: horizontalAnchor.offset, end: horizontalAnchor.offset + horizontalAnchor.size })) {
|
||||
verticalAnchor.mode = LayoutAnchorMode.AVOID;
|
||||
}
|
||||
|
||||
top = layout(activeWindow.innerHeight, viewSizeHeight, verticalAnchor) + activeWindow.pageYOffset;
|
||||
}
|
||||
|
||||
this.view.classList.remove('top', 'bottom', 'left', 'right');
|
||||
this.view.classList.add(anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top');
|
||||
|
||||
@@ -11,6 +11,7 @@ import { StandardKeyboardEvent } from '../../keyboardEvent.js';
|
||||
import { StandardMouseEvent } from '../../mouseEvent.js';
|
||||
import { ActionBar, ActionsOrientation, IActionViewItemProvider } from '../actionbar/actionbar.js';
|
||||
import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions } from '../actionbar/actionViewItems.js';
|
||||
import { AnchorAlignment, layout, LayoutAnchorPosition } from '../contextview/contextview.js';
|
||||
import { DomScrollableElement } from '../scrollbar/scrollableElement.js';
|
||||
import { EmptySubmenuAction, IAction, IActionRunner, Separator, SubmenuAction } from '../../../common/actions.js';
|
||||
import { RunOnceScheduler } from '../../../common/async.js';
|
||||
@@ -25,7 +26,6 @@ import { DisposableStore } from '../../../common/lifecycle.js';
|
||||
import { isLinux, isMacintosh } from '../../../common/platform.js';
|
||||
import { ScrollbarVisibility, ScrollEvent } from '../../../common/scrollable.js';
|
||||
import * as strings from '../../../common/strings.js';
|
||||
import { AnchorAlignment, layout, LayoutAnchorPosition } from '../../../common/layout.js';
|
||||
|
||||
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
|
||||
export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g;
|
||||
@@ -859,7 +859,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
const ret = { top: 0, left: 0 };
|
||||
|
||||
// Start with horizontal
|
||||
ret.left = layout(windowDimensions.width, submenu.width, { position: expandDirection.horizontal === HorizontalDirection.Right ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, offset: entry.left, size: entry.width }).position;
|
||||
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) {
|
||||
@@ -872,7 +872,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
}
|
||||
|
||||
// Now that we have a horizontal position, try layout vertically
|
||||
ret.top = layout(windowDimensions.height, submenu.height, { position: LayoutAnchorPosition.Before, offset: entry.top, size: 0 }).position;
|
||||
ret.top = layout(windowDimensions.height, submenu.height, { position: LayoutAnchorPosition.Before, offset: entry.top, size: 0 });
|
||||
|
||||
// We didn't have enough room below, but we did above, so we shift down to align the menu
|
||||
if (ret.top + submenu.height === entry.top && ret.top + entry.height + submenu.height <= windowDimensions.height) {
|
||||
|
||||
@@ -59,5 +59,4 @@ export interface IDefaultAccount {
|
||||
readonly sessionId: string;
|
||||
readonly enterprise: boolean;
|
||||
readonly entitlementsData?: IEntitlementsData | null;
|
||||
readonly policyData?: IPolicyData;
|
||||
}
|
||||
|
||||
@@ -1,166 +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 { Range } from './range.js';
|
||||
|
||||
export interface IAnchor {
|
||||
x: number;
|
||||
y: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export const enum AnchorAlignment {
|
||||
LEFT, RIGHT
|
||||
}
|
||||
|
||||
export const enum AnchorPosition {
|
||||
BELOW, ABOVE
|
||||
}
|
||||
|
||||
export const enum AnchorAxisAlignment {
|
||||
VERTICAL, HORIZONTAL
|
||||
}
|
||||
|
||||
interface IPosition {
|
||||
readonly top: number;
|
||||
readonly left: number;
|
||||
}
|
||||
|
||||
interface ISize {
|
||||
readonly width: number;
|
||||
readonly height: number;
|
||||
}
|
||||
|
||||
export interface IRect extends IPosition, ISize { }
|
||||
|
||||
export const enum LayoutAnchorPosition {
|
||||
Before,
|
||||
After
|
||||
}
|
||||
|
||||
export enum LayoutAnchorMode {
|
||||
AVOID,
|
||||
ALIGN
|
||||
}
|
||||
|
||||
export interface ILayoutAnchor {
|
||||
offset: number;
|
||||
size: number;
|
||||
mode?: LayoutAnchorMode; // default: AVOID
|
||||
position: LayoutAnchorPosition;
|
||||
}
|
||||
|
||||
export interface ILayoutResult {
|
||||
position: number;
|
||||
result: 'ok' | 'flipped' | 'overlap';
|
||||
}
|
||||
|
||||
/**
|
||||
* Lays out a one dimensional view next to an anchor in a viewport.
|
||||
*
|
||||
* @returns The view offset within the viewport.
|
||||
*/
|
||||
export function layout(viewportSize: number, viewSize: number, anchor: ILayoutAnchor): ILayoutResult {
|
||||
const layoutAfterAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset : anchor.offset + anchor.size;
|
||||
const layoutBeforeAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset + anchor.size : anchor.offset;
|
||||
|
||||
if (anchor.position === LayoutAnchorPosition.Before) {
|
||||
if (viewSize <= viewportSize - layoutAfterAnchorBoundary) {
|
||||
return { position: layoutAfterAnchorBoundary, result: 'ok' }; // happy case, lay it out after the anchor
|
||||
}
|
||||
|
||||
if (viewSize <= layoutBeforeAnchorBoundary) {
|
||||
return { position: layoutBeforeAnchorBoundary - viewSize, result: 'flipped' }; // ok case, lay it out before the anchor
|
||||
}
|
||||
|
||||
return { position: Math.max(viewportSize - viewSize, 0), result: 'overlap' }; // sad case, lay it over the anchor
|
||||
} else {
|
||||
if (viewSize <= layoutBeforeAnchorBoundary) {
|
||||
return { position: layoutBeforeAnchorBoundary - viewSize, result: 'ok' }; // happy case, lay it out before the anchor
|
||||
}
|
||||
|
||||
if (viewSize <= viewportSize - layoutAfterAnchorBoundary && layoutBeforeAnchorBoundary < viewSize / 2) {
|
||||
return { position: layoutAfterAnchorBoundary, result: 'flipped' }; // ok case, lay it out after the anchor
|
||||
}
|
||||
|
||||
return { position: 0, result: 'overlap' }; // sad case, lay it over the anchor
|
||||
}
|
||||
}
|
||||
|
||||
interface ILayout2DOptions {
|
||||
readonly anchorAlignment?: AnchorAlignment; // default: left
|
||||
readonly anchorPosition?: AnchorPosition; // default: below
|
||||
readonly anchorAxisAlignment?: AnchorAxisAlignment; // default: vertical
|
||||
}
|
||||
|
||||
export interface ILayout2DResult {
|
||||
top: number;
|
||||
left: number;
|
||||
bottom: number;
|
||||
right: number;
|
||||
anchorAlignment: AnchorAlignment;
|
||||
anchorPosition: AnchorPosition;
|
||||
}
|
||||
|
||||
export function layout2d(viewport: IRect, view: ISize, anchor: IRect, options?: ILayout2DOptions): ILayout2DResult {
|
||||
let anchorAlignment = options?.anchorAlignment ?? AnchorAlignment.LEFT;
|
||||
let anchorPosition = options?.anchorPosition ?? AnchorPosition.ABOVE;
|
||||
const anchorAxisAlignment = options?.anchorAxisAlignment ?? AnchorAxisAlignment.VERTICAL;
|
||||
|
||||
let top: number;
|
||||
let left: number;
|
||||
|
||||
if (anchorAxisAlignment === AnchorAxisAlignment.VERTICAL) {
|
||||
const verticalAnchor: ILayoutAnchor = { offset: anchor.top - viewport.top, size: anchor.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.After : LayoutAnchorPosition.Before };
|
||||
const horizontalAnchor: ILayoutAnchor = { offset: anchor.left, size: anchor.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN };
|
||||
|
||||
const verticalLayoutResult = layout(viewport.height, view.height, verticalAnchor);
|
||||
top = verticalLayoutResult.position + viewport.top;
|
||||
|
||||
if (verticalLayoutResult.result === 'flipped') {
|
||||
anchorPosition = anchorPosition === AnchorPosition.BELOW ? AnchorPosition.ABOVE : AnchorPosition.BELOW;
|
||||
}
|
||||
|
||||
// if view intersects vertically with anchor, we must avoid the anchor
|
||||
if (Range.intersects({ start: top, end: top + view.height }, { start: verticalAnchor.offset, end: verticalAnchor.offset + verticalAnchor.size })) {
|
||||
horizontalAnchor.mode = LayoutAnchorMode.AVOID;
|
||||
}
|
||||
|
||||
const horizontalLayoutResult = layout(viewport.width, view.width, horizontalAnchor);
|
||||
left = horizontalLayoutResult.position;
|
||||
|
||||
if (horizontalLayoutResult.result === 'flipped') {
|
||||
anchorAlignment = anchorAlignment === AnchorAlignment.LEFT ? AnchorAlignment.RIGHT : AnchorAlignment.LEFT;
|
||||
}
|
||||
} else {
|
||||
const horizontalAnchor: ILayoutAnchor = { offset: anchor.left, size: anchor.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
|
||||
const verticalAnchor: ILayoutAnchor = { offset: anchor.top, size: anchor.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.After : LayoutAnchorPosition.Before, mode: LayoutAnchorMode.ALIGN };
|
||||
|
||||
const horizontalLayoutResult = layout(viewport.width, view.width, horizontalAnchor);
|
||||
left = horizontalLayoutResult.position;
|
||||
|
||||
if (horizontalLayoutResult.result === 'flipped') {
|
||||
anchorAlignment = anchorAlignment === AnchorAlignment.LEFT ? AnchorAlignment.RIGHT : AnchorAlignment.LEFT;
|
||||
}
|
||||
|
||||
// if view intersects horizontally with anchor, we must avoid the anchor
|
||||
if (Range.intersects({ start: left, end: left + view.width }, { start: horizontalAnchor.offset, end: horizontalAnchor.offset + horizontalAnchor.size })) {
|
||||
verticalAnchor.mode = LayoutAnchorMode.AVOID;
|
||||
}
|
||||
|
||||
const verticalLayoutResult = layout(viewport.height, view.height, verticalAnchor);
|
||||
top = verticalLayoutResult.position + viewport.top;
|
||||
|
||||
if (verticalLayoutResult.result === 'flipped') {
|
||||
anchorPosition = anchorPosition === AnchorPosition.BELOW ? AnchorPosition.ABOVE : AnchorPosition.BELOW;
|
||||
}
|
||||
}
|
||||
|
||||
const right = viewport.width - (left + view.width);
|
||||
const bottom = viewport.height - (top + view.height);
|
||||
|
||||
return { top, left, bottom, right, anchorAlignment, anchorPosition };
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from '../../nls.js';
|
||||
import { IDefaultAccount } from './defaultAccount.js';
|
||||
import { IPolicyData } from './defaultAccount.js';
|
||||
|
||||
/**
|
||||
* System-wide policy file path for Linux systems.
|
||||
@@ -96,5 +96,5 @@ export interface IPolicy {
|
||||
*
|
||||
* If `undefined`, the feature's setting is not locked and can be overridden by other means.
|
||||
*/
|
||||
readonly value?: (account: IDefaultAccount) => string | number | boolean | undefined;
|
||||
readonly value?: (policyData: IPolicyData) => string | number | boolean | undefined;
|
||||
}
|
||||
|
||||
+16
-15
@@ -4,26 +4,27 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import assert from 'assert';
|
||||
import { layout, LayoutAnchorPosition } from '../../common/layout.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js';
|
||||
import { layout, LayoutAnchorPosition } from '../../../../browser/ui/contextview/contextview.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../common/utils.js';
|
||||
|
||||
suite('Layout', function () {
|
||||
suite('Contextview', function () {
|
||||
|
||||
test('layout', () => {
|
||||
assert.strictEqual(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.Before }).position, 0);
|
||||
assert.strictEqual(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.Before }).position, 50);
|
||||
assert.strictEqual(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.Before }).position, 180);
|
||||
assert.strictEqual(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.Before }), 0);
|
||||
assert.strictEqual(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.Before }), 50);
|
||||
assert.strictEqual(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.Before }), 180);
|
||||
|
||||
assert.strictEqual(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.After }).position, 0);
|
||||
assert.strictEqual(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.After }).position, 30);
|
||||
assert.strictEqual(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.After }).position, 180);
|
||||
assert.strictEqual(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.Before }).position, 50);
|
||||
assert.strictEqual(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.Before }).position, 100);
|
||||
assert.strictEqual(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.Before }).position, 130);
|
||||
assert.strictEqual(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.After }), 0);
|
||||
assert.strictEqual(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.After }), 30);
|
||||
assert.strictEqual(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.After }), 180);
|
||||
|
||||
assert.strictEqual(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.After }).position, 50);
|
||||
assert.strictEqual(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.After }).position, 30);
|
||||
assert.strictEqual(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.After }).position, 130);
|
||||
assert.strictEqual(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.Before }), 50);
|
||||
assert.strictEqual(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.Before }), 100);
|
||||
assert.strictEqual(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.Before }), 130);
|
||||
|
||||
assert.strictEqual(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.After }), 50);
|
||||
assert.strictEqual(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.After }), 30);
|
||||
assert.strictEqual(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.After }), 130);
|
||||
});
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
@@ -37,6 +37,7 @@ import { EditorWorkerHost } from '../../common/services/editorWorkerHost.js';
|
||||
import { StringEdit } from '../../common/core/edits/stringEdit.js';
|
||||
import { OffsetRange } from '../../common/core/ranges/offsetRange.js';
|
||||
import { FileAccess } from '../../../base/common/network.js';
|
||||
import { isCompletionsEnabledWithTextResourceConfig } from '../../common/services/completionsEnablement.js';
|
||||
|
||||
/**
|
||||
* Stop the worker if it was not needed for 5 min.
|
||||
@@ -280,7 +281,9 @@ class WordBasedCompletionItemProvider implements languages.CompletionItemProvide
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (config.wordBasedSuggestions === 'offWithInlineSuggestions' && this.languageFeaturesService.inlineCompletionsProvider.has(model)) {
|
||||
if (config.wordBasedSuggestions === 'offWithInlineSuggestions'
|
||||
&& this.languageFeaturesService.inlineCompletionsProvider.has(model)
|
||||
&& isCompletionsEnabledWithTextResourceConfig(this._configurationService, model.uri, model.getLanguageId())) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import product from '../../../platform/product/common/product.js';
|
||||
import { isObject } from '../../../base/common/types.js';
|
||||
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
|
||||
import { ITextResourceConfigurationService } from './textResourceConfiguration.js';
|
||||
import { URI } from '../../../base/common/uri.js';
|
||||
|
||||
/**
|
||||
* Get the completions enablement setting name from product configuration.
|
||||
*/
|
||||
function getCompletionsEnablementSettingName(): string | undefined {
|
||||
return product.defaultChatAgent?.completionsEnablementSetting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if completions (e.g., Copilot) are enabled for a given language ID
|
||||
* using `IConfigurationService`.
|
||||
*
|
||||
* @param configurationService The configuration service to read settings from.
|
||||
* @param modeId The language ID to check. Defaults to '*' which checks the global setting.
|
||||
* @returns `true` if completions are enabled for the language, `false` otherwise.
|
||||
*/
|
||||
export function isCompletionsEnabled(configurationService: IConfigurationService, modeId: string = '*'): boolean {
|
||||
const settingName = getCompletionsEnablementSettingName();
|
||||
if (!settingName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isCompletionsEnabledFromObject(
|
||||
configurationService.getValue<Record<string, boolean>>(settingName),
|
||||
modeId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if completions (e.g., Copilot) are enabled for a given language ID
|
||||
* using `ITextResourceConfigurationService`.
|
||||
*
|
||||
* @param configurationService The text resource configuration service to read settings from.
|
||||
* @param modeId The language ID to check. Defaults to '*' which checks the global setting.
|
||||
* @returns `true` if completions are enabled for the language, `false` otherwise.
|
||||
*/
|
||||
export function isCompletionsEnabledWithTextResourceConfig(configurationService: ITextResourceConfigurationService, resource: URI, modeId: string = '*'): boolean {
|
||||
const settingName = getCompletionsEnablementSettingName();
|
||||
if (!settingName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pass undefined as resource to get the global setting
|
||||
return isCompletionsEnabledFromObject(
|
||||
configurationService.getValue<Record<string, boolean>>(resource, settingName),
|
||||
modeId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if completions are enabled for a given language ID using a pre-fetched
|
||||
* completions enablement object.
|
||||
*
|
||||
* @param completionsEnablementObject The object containing per-language enablement settings.
|
||||
* @param modeId The language ID to check. Defaults to '*' which checks the global setting.
|
||||
* @returns `true` if completions are enabled for the language, `false` otherwise.
|
||||
*/
|
||||
export function isCompletionsEnabledFromObject(completionsEnablementObject: Record<string, boolean> | undefined, modeId: string = '*'): boolean {
|
||||
if (!isObject(completionsEnablementObject)) {
|
||||
return false; // default to disabled if setting is not available
|
||||
}
|
||||
|
||||
if (typeof completionsEnablementObject[modeId] !== 'undefined') {
|
||||
return Boolean(completionsEnablementObject[modeId]); // go with setting if explicitly defined
|
||||
}
|
||||
|
||||
return Boolean(completionsEnablementObject['*']); // fallback to global setting otherwise
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { Command, InlineCompletionEndOfLifeReasonKind, InlineCompletionTriggerKi
|
||||
import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';
|
||||
import { ITextModel } from '../../../../common/model.js';
|
||||
import { offsetEditFromContentChanges } from '../../../../common/model/textModelStringEdit.js';
|
||||
import { isCompletionsEnabledFromObject } from '../../../../common/services/completionsEnablement.js';
|
||||
import { IFeatureDebounceInformation } from '../../../../common/services/languageFeatureDebounce.js';
|
||||
import { IModelContentChangedEvent } from '../../../../common/textModelEvents.js';
|
||||
import { formatRecordableLogEntry, IRecordableEditorLogEntry, IRecordableLogEntry, StructuredLogger } from '../structuredLogger.js';
|
||||
@@ -445,7 +446,7 @@ export class InlineCompletionsSource extends Disposable {
|
||||
}
|
||||
|
||||
|
||||
if (!isCompletionsEnabled(this._completionsEnabled, this._textModel.getLanguageId())) {
|
||||
if (!isCompletionsEnabledFromObject(this._completionsEnabled, this._textModel.getLanguageId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -571,18 +572,6 @@ function isSubset<T>(set1: Set<T>, set2: Set<T>): boolean {
|
||||
return [...set1].every(item => set2.has(item));
|
||||
}
|
||||
|
||||
function isCompletionsEnabled(completionsEnablementObject: Record<string, boolean> | undefined, modeId: string = '*'): boolean {
|
||||
if (completionsEnablementObject === undefined) {
|
||||
return false; // default to disabled if setting is not available
|
||||
}
|
||||
|
||||
if (typeof completionsEnablementObject[modeId] !== 'undefined') {
|
||||
return Boolean(completionsEnablementObject[modeId]); // go with setting if explicitly defined
|
||||
}
|
||||
|
||||
return Boolean(completionsEnablementObject['*']); // fallback to global setting otherwise
|
||||
}
|
||||
|
||||
class UpdateOperation implements IDisposable {
|
||||
constructor(
|
||||
public readonly request: UpdateRequest,
|
||||
|
||||
@@ -267,6 +267,8 @@ export async function withAsyncTestCodeEditorAndInlineCompletionsModel<T>(
|
||||
options.serviceCollection.set(IDefaultAccountService, {
|
||||
_serviceBrand: undefined,
|
||||
onDidChangeDefaultAccount: Event.None,
|
||||
onDidChangePolicyData: Event.None,
|
||||
policyData: null,
|
||||
getDefaultAccount: async () => null,
|
||||
setDefaultAccountProvider: () => { },
|
||||
getDefaultAccountAuthenticationProvider: () => { return { id: 'mockProvider', name: 'Mock Provider', enterprise: false }; },
|
||||
|
||||
@@ -100,7 +100,7 @@ import { IDataChannelService, NullDataChannelService } from '../../../platform/d
|
||||
import { IWebWorkerService } from '../../../platform/webWorker/browser/webWorkerService.js';
|
||||
import { StandaloneWebWorkerService } from './services/standaloneWebWorkerService.js';
|
||||
import { IDefaultAccountService } from '../../../platform/defaultAccount/common/defaultAccount.js';
|
||||
import { IDefaultAccount, IDefaultAccountAuthenticationProvider } from '../../../base/common/defaultAccount.js';
|
||||
import { IDefaultAccount, IDefaultAccountAuthenticationProvider, IPolicyData } from '../../../base/common/defaultAccount.js';
|
||||
|
||||
class SimpleModel implements IResolvedTextEditorModel {
|
||||
|
||||
@@ -1115,6 +1115,8 @@ class StandaloneDefaultAccountService implements IDefaultAccountService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
readonly onDidChangeDefaultAccount: Event<IDefaultAccount | null> = Event.None;
|
||||
readonly onDidChangePolicyData: Event<IPolicyData | null> = Event.None;
|
||||
readonly policyData: IPolicyData | null = null;
|
||||
|
||||
async getDefaultAccount(): Promise<IDefaultAccount | null> {
|
||||
return null;
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
import { Event } from '../../../base/common/event.js';
|
||||
import { IDefaultAccount, IDefaultAccountAuthenticationProvider } from '../../../base/common/defaultAccount.js';
|
||||
import { IDefaultAccount, IDefaultAccountAuthenticationProvider, IPolicyData } from '../../../base/common/defaultAccount.js';
|
||||
|
||||
export interface IDefaultAccountProvider {
|
||||
readonly defaultAccount: IDefaultAccount | null;
|
||||
readonly onDidChangeDefaultAccount: Event<IDefaultAccount | null>;
|
||||
readonly policyData: IPolicyData | null;
|
||||
readonly onDidChangePolicyData: Event<IPolicyData | null>;
|
||||
getDefaultAccountAuthenticationProvider(): IDefaultAccountAuthenticationProvider;
|
||||
refresh(): Promise<IDefaultAccount | null>;
|
||||
signIn(options?: { additionalScopes?: readonly string[];[key: string]: unknown }): Promise<IDefaultAccount | null>;
|
||||
@@ -20,6 +22,8 @@ export const IDefaultAccountService = createDecorator<IDefaultAccountService>('d
|
||||
export interface IDefaultAccountService {
|
||||
readonly _serviceBrand: undefined;
|
||||
readonly onDidChangeDefaultAccount: Event<IDefaultAccount | null>;
|
||||
readonly onDidChangePolicyData: Event<IPolicyData | null>;
|
||||
readonly policyData: IPolicyData | null;
|
||||
getDefaultAccount(): Promise<IDefaultAccount | null>;
|
||||
getDefaultAccountAuthenticationProvider(): IDefaultAccountAuthenticationProvider;
|
||||
setDefaultAccountProvider(provider: IDefaultAccountProvider): void;
|
||||
|
||||
@@ -235,6 +235,7 @@ export interface IExtensionContributions {
|
||||
readonly chatPromptFiles?: ReadonlyArray<IChatFileContribution>;
|
||||
readonly chatInstructions?: ReadonlyArray<IChatFileContribution>;
|
||||
readonly chatAgents?: ReadonlyArray<IChatFileContribution>;
|
||||
readonly chatSkills?: ReadonlyArray<IChatFileContribution>;
|
||||
readonly languageModelTools?: ReadonlyArray<IToolContribution>;
|
||||
readonly languageModelToolSets?: ReadonlyArray<IToolSetContribution>;
|
||||
readonly mcpServerDefinitionProviders?: ReadonlyArray<IMcpCollectionContribution>;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IStringDictionary } from '../../../base/common/collections.js';
|
||||
import { IDefaultAccount } from '../../../base/common/defaultAccount.js';
|
||||
import { IPolicyData } from '../../../base/common/defaultAccount.js';
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { Iterable } from '../../../base/common/iterator.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
@@ -14,7 +14,7 @@ import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
export type PolicyValue = string | number | boolean;
|
||||
export type PolicyDefinition = {
|
||||
type: 'string' | 'number' | 'boolean';
|
||||
value?: (account: IDefaultAccount) => string | number | boolean | undefined;
|
||||
value?: (policyData: IPolicyData) => string | number | boolean | undefined;
|
||||
};
|
||||
|
||||
export const IPolicyService = createDecorator<IPolicyService>('policy');
|
||||
|
||||
@@ -20,11 +20,6 @@
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
|
||||
.quick-input-widget.no-drag .quick-input-titlebar,
|
||||
.quick-input-widget.no-drag .quick-input-title,
|
||||
.quick-input-widget.no-drag .quick-input-header {
|
||||
cursor: default;
|
||||
}
|
||||
.quick-input-widget .monaco-inputbox .monaco-action-bar {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
@@ -37,8 +37,6 @@ import { TriStateCheckbox, createToggleActionViewItemProvider } from '../../../b
|
||||
import { defaultCheckboxStyles } from '../../theme/browser/defaultStyles.js';
|
||||
import { QuickInputTreeController } from './tree/quickInputTreeController.js';
|
||||
import { QuickTree } from './tree/quickTree.js';
|
||||
import { AnchorAlignment, AnchorPosition, layout2d } from '../../../base/common/layout.js';
|
||||
import { getAnchorRect } from '../../../base/browser/ui/contextview/contextview.js';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -537,7 +535,6 @@ export class QuickInputController extends Disposable {
|
||||
input.quickNavigate = options.quickNavigate;
|
||||
input.hideInput = !!options.hideInput;
|
||||
input.contextKey = options.contextKey;
|
||||
input.anchor = options.anchor;
|
||||
input.busy = true;
|
||||
Promise.all([picks, options.activeItem])
|
||||
.then(([items, _activeItem]) => {
|
||||
@@ -707,7 +704,6 @@ export class QuickInputController extends Disposable {
|
||||
|
||||
ui.container.style.display = '';
|
||||
this.updateLayout();
|
||||
this.dndController?.setEnabled(!controller.anchor);
|
||||
this.dndController?.layoutContainer();
|
||||
ui.inputBox.setFocus();
|
||||
this.quickInputTypeContext.set(controller.type);
|
||||
@@ -859,52 +855,16 @@ export class QuickInputController extends Disposable {
|
||||
private updateLayout() {
|
||||
if (this.ui && this.isVisible()) {
|
||||
const style = this.ui.container.style;
|
||||
let width = Math.min(this.dimension!.width * 0.62 /* golden cut */, QuickInputController.MAX_WIDTH);
|
||||
const width = Math.min(this.dimension!.width * 0.62 /* golden cut */, QuickInputController.MAX_WIDTH);
|
||||
style.width = width + 'px';
|
||||
|
||||
let listHeight = this.dimension && this.dimension.height * 0.4;
|
||||
|
||||
// Position
|
||||
if (this.controller?.anchor) {
|
||||
const container = this.layoutService.getContainer(dom.getActiveWindow()).getBoundingClientRect();
|
||||
const anchor = getAnchorRect(this.controller.anchor);
|
||||
width = 380;
|
||||
listHeight = this.dimension ? Math.min(this.dimension.height * 0.2, 200) : 200;
|
||||
|
||||
// Beware:
|
||||
// We need to add some extra pixels to the height to account for the input and padding.
|
||||
const containerHeight = Math.floor(listHeight) + 6 + 26 + 16;
|
||||
const { top, left, right, bottom, anchorAlignment, anchorPosition } = layout2d(container, { width, height: containerHeight }, anchor);
|
||||
|
||||
if (anchorAlignment === AnchorAlignment.RIGHT) {
|
||||
style.right = `${right}px`;
|
||||
style.left = 'initial';
|
||||
} else {
|
||||
style.left = `${left}px`;
|
||||
style.right = 'initial';
|
||||
}
|
||||
|
||||
if (anchorPosition === AnchorPosition.BELOW) {
|
||||
style.bottom = `${bottom}px`;
|
||||
style.top = 'initial';
|
||||
} else {
|
||||
style.top = `${top}px`;
|
||||
style.bottom = 'initial';
|
||||
}
|
||||
|
||||
style.width = `${width}px`;
|
||||
style.height = '';
|
||||
} else {
|
||||
style.top = `${this.viewState?.top ? Math.round(this.dimension!.height * this.viewState.top) : this.titleBarOffset}px`;
|
||||
style.left = `${Math.round((this.dimension!.width * (this.viewState?.left ?? 0.5 /* center */)) - (width / 2))}px`;
|
||||
style.right = '';
|
||||
style.bottom = '';
|
||||
style.height = '';
|
||||
}
|
||||
style.top = `${this.viewState?.top ? Math.round(this.dimension!.height * this.viewState.top) : this.titleBarOffset}px`;
|
||||
style.left = `${Math.round((this.dimension!.width * (this.viewState?.left ?? 0.5 /* center */)) - (width / 2))}px`;
|
||||
|
||||
this.ui.inputBox.layout();
|
||||
this.ui.list.layout(listHeight);
|
||||
this.ui.tree.layout(listHeight);
|
||||
this.ui.list.layout(this.dimension && this.dimension.height * 0.4);
|
||||
this.ui.tree.layout(this.dimension && this.dimension.height * 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -999,8 +959,6 @@ export interface IQuickInputControllerHost extends ILayoutService { }
|
||||
class QuickInputDragAndDropController extends Disposable {
|
||||
readonly dndViewState = observableValue<{ top?: number; left?: number; done: boolean } | undefined>(this, undefined);
|
||||
|
||||
private _enabled = true;
|
||||
|
||||
private readonly _snapThreshold = 20;
|
||||
private readonly _snapLineHorizontalRatio = 0.25;
|
||||
|
||||
@@ -1036,10 +994,6 @@ class QuickInputDragAndDropController extends Disposable {
|
||||
}
|
||||
|
||||
layoutContainer(dimension = this._layoutService.activeContainerDimension): void {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = this.dndViewState.get();
|
||||
const dragAreaRect = this._quickInputContainer.getBoundingClientRect();
|
||||
if (state?.top && state?.left) {
|
||||
@@ -1051,11 +1005,6 @@ class QuickInputDragAndDropController extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
setEnabled(enabled: boolean): void {
|
||||
this._enabled = enabled;
|
||||
this._quickInputContainer.classList.toggle('no-drag', !enabled);
|
||||
}
|
||||
|
||||
setAlignment(alignment: 'top' | 'center' | { top: number; left: number }, done = true): void {
|
||||
if (alignment === 'top') {
|
||||
this.dndViewState.set({
|
||||
@@ -1086,10 +1035,6 @@ class QuickInputDragAndDropController extends Disposable {
|
||||
|
||||
// Double click
|
||||
this._register(dom.addDisposableGenericMouseUpListener(dragArea, (event: MouseEvent) => {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const originEvent = new StandardMouseEvent(dom.getWindow(dragArea), event);
|
||||
if (originEvent.detail !== 2) {
|
||||
return;
|
||||
@@ -1106,10 +1051,6 @@ class QuickInputDragAndDropController extends Disposable {
|
||||
|
||||
// Mouse down
|
||||
this._register(dom.addDisposableGenericMouseDownListener(dragArea, (e: MouseEvent) => {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeWindow = dom.getWindow(this._layoutService.activeContainer);
|
||||
const originEvent = new StandardMouseEvent(activeWindow, e);
|
||||
|
||||
|
||||
@@ -197,11 +197,6 @@ export interface IPickOptions<T extends IQuickPickItem> {
|
||||
*/
|
||||
activeItem?: Promise<T> | T;
|
||||
|
||||
/**
|
||||
* an optional anchor for the picker
|
||||
*/
|
||||
anchor?: HTMLElement | { x: number; y: number };
|
||||
|
||||
onKeyMods?: (keyMods: IKeyMods) => void;
|
||||
onDidFocus?: (entry: T) => void;
|
||||
onDidTriggerItemButton?: (context: IQuickPickItemButtonContext<T>) => void;
|
||||
@@ -358,11 +353,6 @@ export interface IQuickInput extends IDisposable {
|
||||
*/
|
||||
ignoreFocusOut: boolean;
|
||||
|
||||
/**
|
||||
* An optional anchor for the quick input.
|
||||
*/
|
||||
anchor?: HTMLElement | { x: number; y: number };
|
||||
|
||||
/**
|
||||
* Shows the quick input.
|
||||
*/
|
||||
|
||||
@@ -547,7 +547,7 @@ export class BreadcrumbsControl {
|
||||
const pickerArrowSize = 8;
|
||||
let pickerArrowOffset: number;
|
||||
|
||||
const data = dom.getDomNodePagePosition(event.node.firstChild as HTMLElement);
|
||||
const data = dom.getDomNodePagePosition(event.node);
|
||||
const y = data.top + data.height + pickerArrowSize;
|
||||
if (y + maxHeight >= window.innerHeight) {
|
||||
maxHeight = window.innerHeight - y - 30 /* room for shadow and status bar*/;
|
||||
|
||||
@@ -491,7 +491,7 @@ export class OpenSessionTargetPickerAction extends Action2 {
|
||||
tooltip: localize('setSessionTarget', "Set Session Target"),
|
||||
category: CHAT_CATEGORY,
|
||||
f1: false,
|
||||
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.or(ChatContextKeys.chatSessionIsEmpty, ChatContextKeys.inAgentSessionsWelcome)),
|
||||
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.or(ChatContextKeys.chatSessionIsEmpty, ChatContextKeys.inAgentSessionsWelcome), ChatContextKeys.currentlyEditingInput.negate(), ChatContextKeys.currentlyEditing.negate()),
|
||||
menu: [
|
||||
{
|
||||
id: MenuId.ChatInput,
|
||||
@@ -526,7 +526,7 @@ export class OpenDelegationPickerAction extends Action2 {
|
||||
tooltip: localize('delegateSession', "Delegate Session"),
|
||||
category: CHAT_CATEGORY,
|
||||
f1: false,
|
||||
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.chatSessionIsEmpty.negate()),
|
||||
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.chatSessionIsEmpty.negate(), ChatContextKeys.currentlyEditingInput.negate(), ChatContextKeys.currentlyEditing.negate()),
|
||||
menu: [
|
||||
{
|
||||
id: MenuId.ChatInput,
|
||||
|
||||
@@ -155,7 +155,7 @@ export class PickAgentSessionAction extends Action2 {
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
|
||||
const agentSessionsPicker = instantiationService.createInstance(AgentSessionsPicker, undefined);
|
||||
const agentSessionsPicker = instantiationService.createInstance(AgentSessionsPicker);
|
||||
await agentSessionsPicker.pickAgentSession();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@ export class AgentSessionsPicker {
|
||||
private readonly sorter = new AgentSessionsSorter();
|
||||
|
||||
constructor(
|
||||
private readonly anchor: HTMLElement | undefined,
|
||||
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@@ -78,7 +77,6 @@ export class AgentSessionsPicker {
|
||||
const disposables = new DisposableStore();
|
||||
const picker = disposables.add(this.quickInputService.createQuickPick<ISessionPickItem>({ useSeparators: true }));
|
||||
|
||||
picker.anchor = this.anchor;
|
||||
picker.items = this.createPickerItems();
|
||||
picker.canAcceptInBackground = true;
|
||||
picker.placeholder = localize('chatAgentPickerPlaceholder', "Search agent sessions by name");
|
||||
|
||||
@@ -320,7 +320,7 @@ configurationRegistry.registerConfiguration({
|
||||
name: 'ChatToolsAutoApprove',
|
||||
category: PolicyCategory.InteractiveSession,
|
||||
minimumVersion: '1.99',
|
||||
value: (account) => account.policyData?.chat_preview_features_enabled === false ? false : undefined,
|
||||
value: (policyData) => policyData.chat_preview_features_enabled === false ? false : undefined,
|
||||
localization: {
|
||||
description: {
|
||||
key: 'autoApprove2.description',
|
||||
@@ -473,11 +473,11 @@ configurationRegistry.registerConfiguration({
|
||||
name: 'ChatMCP',
|
||||
category: PolicyCategory.InteractiveSession,
|
||||
minimumVersion: '1.99',
|
||||
value: (account) => {
|
||||
if (account.policyData?.mcp === false) {
|
||||
value: (policyData) => {
|
||||
if (policyData.mcp === false) {
|
||||
return McpAccessValue.None;
|
||||
}
|
||||
if (account.policyData?.mcpAccess === 'registry_only') {
|
||||
if (policyData.mcpAccess === 'registry_only') {
|
||||
return McpAccessValue.Registry;
|
||||
}
|
||||
return undefined;
|
||||
@@ -588,7 +588,7 @@ configurationRegistry.registerConfiguration({
|
||||
name: 'ChatAgentMode',
|
||||
category: PolicyCategory.InteractiveSession,
|
||||
minimumVersion: '1.99',
|
||||
value: (account) => account.policyData?.chat_agent_enabled === false ? false : undefined,
|
||||
value: (policyData) => policyData.chat_agent_enabled === false ? false : undefined,
|
||||
localization: {
|
||||
description: {
|
||||
key: 'chat.agent.enabled.description',
|
||||
@@ -665,7 +665,7 @@ configurationRegistry.registerConfiguration({
|
||||
name: 'McpGalleryServiceUrl',
|
||||
category: PolicyCategory.InteractiveSession,
|
||||
minimumVersion: '1.101',
|
||||
value: (account) => account.policyData?.mcpRegistryUrl,
|
||||
value: (policyData) => policyData.mcpRegistryUrl,
|
||||
localization: {
|
||||
description: {
|
||||
key: 'mcp.gallery.serviceUrl',
|
||||
|
||||
@@ -4,24 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js';
|
||||
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
|
||||
import product from '../../../../../platform/product/common/product.js';
|
||||
import { isObject } from '../../../../../base/common/types.js';
|
||||
|
||||
export function isNewUser(chatEntitlementService: IChatEntitlementService): boolean {
|
||||
return !chatEntitlementService.sentiment.installed || // chat not installed
|
||||
chatEntitlementService.entitlement === ChatEntitlement.Available; // not yet signed up to chat
|
||||
}
|
||||
|
||||
export function isCompletionsEnabled(configurationService: IConfigurationService, modeId: string = '*'): boolean {
|
||||
const result = configurationService.getValue<Record<string, boolean>>(product.defaultChatAgent.completionsEnablementSetting);
|
||||
if (!isObject(result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof result[modeId] !== 'undefined') {
|
||||
return Boolean(result[modeId]); // go with setting if explicitly defined
|
||||
}
|
||||
|
||||
return Boolean(result['*']); // fallback to global setting otherwise
|
||||
}
|
||||
|
||||
@@ -40,13 +40,14 @@ import { EditorResourceAccessor, SideBySideEditor } from '../../../../common/edi
|
||||
import { IChatEntitlementService, ChatEntitlementService, ChatEntitlement, IQuotaSnapshot, getChatPlanName } from '../../../../services/chat/common/chatEntitlementService.js';
|
||||
import { IEditorService } from '../../../../services/editor/common/editorService.js';
|
||||
import { IChatSessionsService } from '../../common/chatSessionsService.js';
|
||||
import { isNewUser, isCompletionsEnabled } from './chatStatus.js';
|
||||
import { isNewUser } from './chatStatus.js';
|
||||
import { IChatStatusItemService, ChatStatusEntry } from './chatStatusItemService.js';
|
||||
import product from '../../../../../platform/product/common/product.js';
|
||||
import { contrastBorder, inputValidationErrorBorder, inputValidationInfoBorder, inputValidationWarningBorder, registerColor, transparent } from '../../../../../platform/theme/common/colorRegistry.js';
|
||||
import { Color } from '../../../../../base/common/color.js';
|
||||
import { IViewsService } from '../../../../services/views/common/viewsService.js';
|
||||
import { ChatViewId } from '../chat.js';
|
||||
import { isCompletionsEnabled } from '../../../../../editor/common/services/completionsEnablement.js';
|
||||
|
||||
const defaultChat = product.defaultChatAgent;
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@ import { IChatSessionsService } from '../../common/chatSessionsService.js';
|
||||
import { ChatStatusDashboard } from './chatStatusDashboard.js';
|
||||
import { mainWindow } from '../../../../../base/browser/window.js';
|
||||
import { disposableWindowInterval } from '../../../../../base/browser/dom.js';
|
||||
import { isNewUser, isCompletionsEnabled } from './chatStatus.js';
|
||||
import { isNewUser } from './chatStatus.js';
|
||||
import product from '../../../../../platform/product/common/product.js';
|
||||
import { isCompletionsEnabled } from '../../../../../editor/common/services/completionsEnablement.js';
|
||||
|
||||
export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ function getDefaultContentSnippet(promptType: PromptsType, name: string | undefi
|
||||
`name: ${name ?? '${1:agent-name}'}`,
|
||||
`description: \${2:Describe what this custom agent does and when to use it.}`,
|
||||
`argument-hint: \${3:The inputs this agent expects, e.g., "a task to implement" or "a question to answer".}`,
|
||||
`# tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo'] # specify the tools this agent can use. if not set, all enabled tools are allowed`,
|
||||
`# tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo'] # specify the tools this agent can use. If not set, all enabled tools are allowed.`,
|
||||
`---`,
|
||||
`\${4:Define what this custom agent does, including its behavior, capabilities, and any specific instructions for its operation.}`,
|
||||
].join('\n');
|
||||
|
||||
+1
-1
@@ -723,7 +723,7 @@ export class ChatMcpAppModel extends Disposable {
|
||||
jsonrpc: '2.0',
|
||||
id,
|
||||
error: { code, message },
|
||||
} satisfies MCP.JSONRPCError);
|
||||
} satisfies MCP.JSONRPCErrorResponse);
|
||||
}
|
||||
|
||||
private async _sendNotification(message: McpApps.HostNotification): Promise<void> {
|
||||
|
||||
@@ -771,7 +771,7 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
box-sizing: border-box;
|
||||
cursor: text;
|
||||
background-color: var(--vscode-input-background);
|
||||
border: 1px solid var(--vscode-chat-requestBorder, var(--vscode-input-border, transparent));
|
||||
border: 1px solid var(--vscode-input-border, transparent);
|
||||
border-radius: 4px;
|
||||
padding: 0 6px 6px 6px;
|
||||
/* top padding is inside the editor widget */
|
||||
|
||||
+16
-31
@@ -140,46 +140,31 @@ export class ChatContextUsageWidget extends Disposable {
|
||||
const store = new DisposableStore();
|
||||
this._hoverDisposable.value = store;
|
||||
|
||||
const getOrCreateDetails = (): ChatContextUsageDetails => {
|
||||
if (!this._contextUsageDetails.value) {
|
||||
this._contextUsageDetails.value = this.instantiationService.createInstance(ChatContextUsageDetails);
|
||||
}
|
||||
if (this.currentData) {
|
||||
this._contextUsageDetails.value.update(this.currentData);
|
||||
const createDetails = (): ChatContextUsageDetails | undefined => {
|
||||
if (!this._isVisible.get() || !this.currentData) {
|
||||
return undefined;
|
||||
}
|
||||
this._contextUsageDetails.value = this.instantiationService.createInstance(ChatContextUsageDetails);
|
||||
this._contextUsageDetails.value.update(this.currentData);
|
||||
return this._contextUsageDetails.value;
|
||||
};
|
||||
|
||||
const resolveHoverOptions = (): IDelayedHoverOptions => {
|
||||
const details = getOrCreateDetails();
|
||||
return {
|
||||
content: details.domNode,
|
||||
appearance: { showPointer: true, compact: true },
|
||||
persistence: { hideOnHover: false },
|
||||
trapFocus: true
|
||||
};
|
||||
const hoverOptions: Omit<IDelayedHoverOptions, 'content'> = {
|
||||
appearance: { showPointer: true, compact: true },
|
||||
persistence: { hideOnHover: false },
|
||||
trapFocus: true
|
||||
};
|
||||
|
||||
store.add(this.hoverService.setupDelayedHover(
|
||||
this.domNode,
|
||||
resolveHoverOptions
|
||||
));
|
||||
store.add(this.hoverService.setupDelayedHover(this.domNode, () => ({
|
||||
...hoverOptions,
|
||||
content: createDetails()?.domNode ?? ''
|
||||
})));
|
||||
|
||||
// Helper to show sticky hover with focus
|
||||
const showStickyHover = () => {
|
||||
if (this.currentData) {
|
||||
// Force hide any existing hover to ensure we can show our sticky one
|
||||
this.hoverService.hideHover(true);
|
||||
|
||||
const details = getOrCreateDetails();
|
||||
const details = createDetails();
|
||||
if (details) {
|
||||
this.hoverService.showInstantHover(
|
||||
{
|
||||
content: details.domNode,
|
||||
target: this.domNode,
|
||||
appearance: { showPointer: true, compact: true },
|
||||
persistence: { hideOnHover: false, sticky: true },
|
||||
trapFocus: true,
|
||||
},
|
||||
{ ...hoverOptions, content: details.domNode, target: this.domNode, persistence: { hideOnHover: false, sticky: true } },
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,8 +59,6 @@ export class ChatViewTitleControl extends Disposable {
|
||||
}
|
||||
|
||||
private registerActions(): void {
|
||||
const that = this;
|
||||
|
||||
this._register(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -78,7 +76,7 @@ export class ChatViewTitleControl extends Disposable {
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
|
||||
const agentSessionsPicker = instantiationService.createInstance(AgentSessionsPicker, that.titleLabel.value?.element);
|
||||
const agentSessionsPicker = instantiationService.createInstance(AgentSessionsPicker);
|
||||
await agentSessionsPicker.pickAgentSession();
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { DisposableMap } from '../../../../../base/common/lifecycle.js';
|
||||
import { Disposable, DisposableMap } from '../../../../../base/common/lifecycle.js';
|
||||
import { joinPath, isEqualOrParent } from '../../../../../base/common/resources.js';
|
||||
import { localize } from '../../../../../nls.js';
|
||||
import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js';
|
||||
import { ExtensionIdentifier, IExtensionManifest } from '../../../../../platform/extensions/common/extensions.js';
|
||||
import { IWorkbenchContribution } from '../../../../common/contributions.js';
|
||||
import * as extensionsRegistry from '../../../../services/extensions/common/extensionsRegistry.js';
|
||||
import { IPromptsService, PromptsStorage } from './service/promptsService.js';
|
||||
@@ -15,6 +15,9 @@ import { PromptsType } from './promptTypes.js';
|
||||
import { UriComponents } from '../../../../../base/common/uri.js';
|
||||
import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js';
|
||||
import { CancellationToken } from '../../../../../base/common/cancellation.js';
|
||||
import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js';
|
||||
import { Registry } from '../../../../../platform/registry/common/platform.js';
|
||||
import { Extensions, IExtensionFeaturesRegistry, IExtensionFeatureTableRenderer, IRenderedData, IRowData, ITableData } from '../../../../services/extensionManagement/common/extensionFeatures.js';
|
||||
|
||||
interface IRawChatFileContribution {
|
||||
readonly path: string;
|
||||
@@ -162,3 +165,80 @@ CommandsRegistry.registerCommand('_listExtensionPromptFiles', async (accessor):
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
class ChatPromptFilesDataRenderer extends Disposable implements IExtensionFeatureTableRenderer {
|
||||
readonly type = 'table';
|
||||
|
||||
constructor(private readonly contributionPoint: ChatContributionPoint) {
|
||||
super();
|
||||
}
|
||||
|
||||
shouldRender(manifest: IExtensionManifest): boolean {
|
||||
return !!manifest.contributes?.[this.contributionPoint];
|
||||
}
|
||||
|
||||
render(manifest: IExtensionManifest): IRenderedData<ITableData> {
|
||||
const contributions = manifest.contributes?.[this.contributionPoint] ?? [];
|
||||
if (!contributions.length) {
|
||||
return { data: { headers: [], rows: [] }, dispose: () => { } };
|
||||
}
|
||||
|
||||
const headers = [
|
||||
localize('chatFilesName', "Name"),
|
||||
localize('chatFilesDescription', "Description"),
|
||||
localize('chatFilesPath', "Path"),
|
||||
];
|
||||
|
||||
const rows: IRowData[][] = contributions.map(d => {
|
||||
return [
|
||||
d.name ?? '-',
|
||||
d.description ?? '-',
|
||||
d.path,
|
||||
];
|
||||
});
|
||||
|
||||
return {
|
||||
data: {
|
||||
headers,
|
||||
rows
|
||||
},
|
||||
dispose: () => { }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IExtensionFeaturesRegistry>(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({
|
||||
id: ChatContributionPoint.chatPromptFiles,
|
||||
label: localize('chatPromptFiles', "Chat Prompt Files"),
|
||||
access: {
|
||||
canToggle: false
|
||||
},
|
||||
renderer: new SyncDescriptor(ChatPromptFilesDataRenderer, [ChatContributionPoint.chatPromptFiles]),
|
||||
});
|
||||
|
||||
Registry.as<IExtensionFeaturesRegistry>(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({
|
||||
id: ChatContributionPoint.chatInstructions,
|
||||
label: localize('chatInstructions', "Chat Instructions"),
|
||||
access: {
|
||||
canToggle: false
|
||||
},
|
||||
renderer: new SyncDescriptor(ChatPromptFilesDataRenderer, [ChatContributionPoint.chatInstructions]),
|
||||
});
|
||||
|
||||
Registry.as<IExtensionFeaturesRegistry>(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({
|
||||
id: ChatContributionPoint.chatAgents,
|
||||
label: localize('chatAgents', "Chat Agents"),
|
||||
access: {
|
||||
canToggle: false
|
||||
},
|
||||
renderer: new SyncDescriptor(ChatPromptFilesDataRenderer, [ChatContributionPoint.chatAgents]),
|
||||
});
|
||||
|
||||
Registry.as<IExtensionFeaturesRegistry>(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({
|
||||
id: ChatContributionPoint.chatSkills,
|
||||
label: localize('chatSkills', "Chat Skills"),
|
||||
access: {
|
||||
canToggle: false
|
||||
},
|
||||
renderer: new SyncDescriptor(ChatPromptFilesDataRenderer, [ChatContributionPoint.chatSkills]),
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ export class InlineChatAffordance extends Disposable {
|
||||
|
||||
this._store.add(autorun(r => {
|
||||
const value = debouncedSelection.read(r);
|
||||
if (!value || value.isEmpty() || !explicitSelection) {
|
||||
if (!value || value.isEmpty() || !explicitSelection || _editor.getModel()?.getValueInRange(value).match(/^\s+$/)) {
|
||||
selectionData.set(undefined, undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,6 +133,11 @@ export class McpServerRequestHandler extends Disposable {
|
||||
elicitation: opts.elicitationRequestHandler ? { create: {} } : undefined,
|
||||
},
|
||||
},
|
||||
extensions: {
|
||||
'io.modelcontextprotocol/ui': {
|
||||
mimeTypes: ['text/html;profile=mcp-app']
|
||||
}
|
||||
}
|
||||
},
|
||||
clientInfo: {
|
||||
name: productService.nameLong,
|
||||
@@ -321,22 +326,26 @@ export class McpServerRequestHandler extends Disposable {
|
||||
/**
|
||||
* Handle successful responses
|
||||
*/
|
||||
private handleResult(response: MCP.JSONRPCResponse): void {
|
||||
const request = this._pendingRequests.get(response.id);
|
||||
if (request) {
|
||||
this._pendingRequests.delete(response.id);
|
||||
request.promise.complete(response.result);
|
||||
private handleResult(response: MCP.JSONRPCResultResponse): void {
|
||||
if (response.id !== undefined) {
|
||||
const request = this._pendingRequests.get(response.id);
|
||||
if (request) {
|
||||
this._pendingRequests.delete(response.id);
|
||||
request.promise.complete(response.result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle error responses
|
||||
*/
|
||||
private handleError(response: MCP.JSONRPCError): void {
|
||||
const request = this._pendingRequests.get(response.id);
|
||||
if (request) {
|
||||
this._pendingRequests.delete(response.id);
|
||||
request.promise.error(new MpcResponseError(response.error.message, response.error.code, response.error.data));
|
||||
private handleError(response: MCP.JSONRPCErrorResponse): void {
|
||||
if (response.id !== undefined) {
|
||||
const request = this._pendingRequests.get(response.id);
|
||||
if (request) {
|
||||
this._pendingRequests.delete(response.id);
|
||||
request.promise.error(new MpcResponseError(response.error.message, response.error.code, response.error.data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +403,7 @@ export class McpServerRequestHandler extends Disposable {
|
||||
e = McpError.unknown(e);
|
||||
}
|
||||
|
||||
const errorResponse: MCP.JSONRPCError = {
|
||||
const errorResponse: MCP.JSONRPCErrorResponse = {
|
||||
jsonrpc: MCP.JSONRPC_VERSION,
|
||||
id: request.id,
|
||||
error: {
|
||||
|
||||
@@ -89,6 +89,7 @@ export class McpTaskManager extends Disposable {
|
||||
status: 'working',
|
||||
createdAt,
|
||||
ttl,
|
||||
lastUpdatedAt: new Date().toISOString(),
|
||||
pollInterval: 1000, // Suggest 1 second polling interval
|
||||
};
|
||||
|
||||
@@ -171,6 +172,8 @@ export class McpTaskManager extends Disposable {
|
||||
}
|
||||
|
||||
entry.task.status = status;
|
||||
entry.task.lastUpdatedAt = new Date().toISOString();
|
||||
|
||||
if (statusMessage !== undefined) {
|
||||
entry.task.statusMessage = statusMessage;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -220,7 +220,7 @@ suite('Workbench - MCP - ServerRequestHandler', () => {
|
||||
const sentMessages = transport.getSentMessages();
|
||||
const pingResponse = sentMessages.find(m =>
|
||||
'id' in m && m.id === pingRequest.id && 'result' in m
|
||||
) as MCP.JSONRPCResponse;
|
||||
) as MCP.JSONRPCResultResponse;
|
||||
|
||||
assert.ok(pingResponse, 'No ping response was sent');
|
||||
assert.deepStrictEqual(pingResponse.result, {});
|
||||
@@ -246,7 +246,7 @@ suite('Workbench - MCP - ServerRequestHandler', () => {
|
||||
const sentMessages = transport.getSentMessages();
|
||||
const rootsResponse = sentMessages.find(m =>
|
||||
'id' in m && m.id === rootsRequest.id && 'result' in m
|
||||
) as MCP.JSONRPCResponse;
|
||||
) as MCP.JSONRPCResultResponse;
|
||||
|
||||
assert.ok(rootsResponse, 'No roots/list response was sent');
|
||||
assert.strictEqual((rootsResponse.result as MCP.ListRootsResult).roots.length, 2);
|
||||
@@ -400,6 +400,7 @@ suite.skip('Workbench - MCP - McpTask', () => { // TODO@connor4312 https://githu
|
||||
taskId: 'task1',
|
||||
status: 'working',
|
||||
createdAt: new Date().toISOString(),
|
||||
lastUpdatedAt: new Date().toISOString(),
|
||||
ttl: null,
|
||||
...overrides
|
||||
};
|
||||
|
||||
@@ -209,6 +209,7 @@ export class AgentSessionsWelcomePage extends EditorPane {
|
||||
clearNode(sessionsSection);
|
||||
this.buildSessionsOrPrompts(sessionsSection);
|
||||
}
|
||||
this.layoutSessionsControl();
|
||||
}));
|
||||
|
||||
this.scrollableElement?.scanDomNode();
|
||||
|
||||
+19
-19
@@ -163,34 +163,34 @@
|
||||
|
||||
/*
|
||||
* Transform items into 2-column layout:
|
||||
* - Items 0,1 form visual row 1 (top: 0)
|
||||
* - Items 2,3 form visual row 2 (top: 52)
|
||||
* - Items 4,5 form visual row 3 (top: 104)
|
||||
* Left column (even): items stay in place or move up
|
||||
* Right column (odd): items move right and up
|
||||
* - Odd items (1, 3, 5, ...) stay in left column
|
||||
* - Even items (2, 4, 6, ...) move to right column
|
||||
* Each pair forms a visual row.
|
||||
* Left column items need to move up by floor((index-1)/2) rows
|
||||
* Right column items need to move right and up by (index/2) rows
|
||||
* Row height is 52px.
|
||||
*/
|
||||
|
||||
/* Item 1 (index 1): move to right column of row 1 */
|
||||
.agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(2) {
|
||||
transform: translateX(100%) translateY(-52px);
|
||||
}
|
||||
|
||||
/* Item 2 (index 2): move up to row 2 left column */
|
||||
/* Left column items (odd positions): move up to form 2-column layout */
|
||||
/* Item 3: move up 1 row */
|
||||
.agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(3) {
|
||||
transform: translateY(-52px);
|
||||
}
|
||||
|
||||
/* Item 3 (index 3): move to right column of row 2 */
|
||||
.agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(4) {
|
||||
transform: translateX(100%) translateY(-104px);
|
||||
}
|
||||
|
||||
/* Item 4 (index 4): move up to row 3 left column */
|
||||
/* Item 5: move up 2 rows */
|
||||
.agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(5) {
|
||||
transform: translateY(-104px);
|
||||
}
|
||||
|
||||
/* Item 5 (index 5): move to right column of row 3 */
|
||||
/* Right column items (even positions): move right and up */
|
||||
/* Item 2: move right, up 1 row */
|
||||
.agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(2) {
|
||||
transform: translateX(100%) translateY(-52px);
|
||||
}
|
||||
/* Item 4: move right, up 2 rows */
|
||||
.agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(4) {
|
||||
transform: translateX(100%) translateY(-104px);
|
||||
}
|
||||
/* Item 6: move right, up 3 rows */
|
||||
.agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(6) {
|
||||
transform: translateX(100%) translateY(-156px);
|
||||
}
|
||||
|
||||
@@ -111,12 +111,16 @@ export class DefaultAccountService extends Disposable implements IDefaultAccount
|
||||
declare _serviceBrand: undefined;
|
||||
|
||||
private defaultAccount: IDefaultAccount | null = null;
|
||||
get policyData(): IPolicyData | null { return this.defaultAccountProvider?.policyData ?? null; }
|
||||
|
||||
private readonly initBarrier = new Barrier();
|
||||
|
||||
private readonly _onDidChangeDefaultAccount = this._register(new Emitter<IDefaultAccount | null>());
|
||||
readonly onDidChangeDefaultAccount = this._onDidChangeDefaultAccount.event;
|
||||
|
||||
private readonly _onDidChangePolicyData = this._register(new Emitter<IPolicyData | null>());
|
||||
readonly onDidChangePolicyData = this._onDidChangePolicyData.event;
|
||||
|
||||
private readonly defaultAccountConfig: IDefaultAccountConfig;
|
||||
private defaultAccountProvider: IDefaultAccountProvider | null = null;
|
||||
|
||||
@@ -148,11 +152,15 @@ export class DefaultAccountService extends Disposable implements IDefaultAccount
|
||||
}
|
||||
|
||||
this.defaultAccountProvider = provider;
|
||||
if (this.defaultAccountProvider.policyData) {
|
||||
this._onDidChangePolicyData.fire(this.defaultAccountProvider.policyData);
|
||||
}
|
||||
provider.refresh().then(account => {
|
||||
this.defaultAccount = account;
|
||||
}).finally(() => {
|
||||
this.initBarrier.open();
|
||||
this._register(provider.onDidChangeDefaultAccount(account => this.setDefaultAccount(account)));
|
||||
this._register(provider.onDidChangePolicyData(policyData => this._onDidChangePolicyData.fire(policyData)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -178,6 +186,16 @@ export class DefaultAccountService extends Disposable implements IDefaultAccount
|
||||
}
|
||||
}
|
||||
|
||||
interface IAccountPolicyData {
|
||||
readonly accountId: string;
|
||||
readonly policyData: IPolicyData;
|
||||
}
|
||||
|
||||
interface IDefaultAccountData {
|
||||
defaultAccount: IDefaultAccount;
|
||||
policyData: IAccountPolicyData | null;
|
||||
}
|
||||
|
||||
type DefaultAccountStatusTelemetry = {
|
||||
status: string;
|
||||
initial: boolean;
|
||||
@@ -192,12 +210,18 @@ type DefaultAccountStatusTelemetryClassification = {
|
||||
|
||||
class DefaultAccountProvider extends Disposable implements IDefaultAccountProvider {
|
||||
|
||||
private _defaultAccount: IDefaultAccount | null = null;
|
||||
get defaultAccount(): IDefaultAccount | null { return this._defaultAccount ?? null; }
|
||||
private _defaultAccount: IDefaultAccountData | null = null;
|
||||
get defaultAccount(): IDefaultAccount | null { return this._defaultAccount?.defaultAccount ?? null; }
|
||||
|
||||
private _policyData: IAccountPolicyData | null = null;
|
||||
get policyData(): IPolicyData | null { return this._policyData?.policyData ?? null; }
|
||||
|
||||
private readonly _onDidChangeDefaultAccount = this._register(new Emitter<IDefaultAccount | null>());
|
||||
readonly onDidChangeDefaultAccount = this._onDidChangeDefaultAccount.event;
|
||||
|
||||
private readonly _onDidChangePolicyData = this._register(new Emitter<IPolicyData | null>());
|
||||
readonly onDidChangePolicyData = this._onDidChangePolicyData.event;
|
||||
|
||||
private readonly accountStatusContext: IContextKey<string>;
|
||||
private initialized = false;
|
||||
private readonly initPromise: Promise<void>;
|
||||
@@ -220,6 +244,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
) {
|
||||
super();
|
||||
this.accountStatusContext = CONTEXT_DEFAULT_ACCOUNT_STATE.bindTo(contextKeyService);
|
||||
this._policyData = this.getCachedPolicyData();
|
||||
this.initPromise = this.init()
|
||||
.finally(() => {
|
||||
this.telemetryService.publicLog2<DefaultAccountStatusTelemetry, DefaultAccountStatusTelemetryClassification>('defaultaccount:status', { status: this.defaultAccount ? 'available' : 'unavailable', initial: true });
|
||||
@@ -227,6 +252,22 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
});
|
||||
}
|
||||
|
||||
private getCachedPolicyData(): IAccountPolicyData | null {
|
||||
const cached = this.storageService.get(CACHED_POLICY_DATA_KEY, StorageScope.APPLICATION);
|
||||
if (cached) {
|
||||
try {
|
||||
const { accountId, policyData } = JSON.parse(cached);
|
||||
if (accountId && policyData) {
|
||||
this.logService.debug('[DefaultAccount] Initializing with cached policy data');
|
||||
return { accountId, policyData };
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error('[DefaultAccount] Failed to parse cached policy data', getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async init(): Promise<void> {
|
||||
if (isWeb && !this.environmentService.remoteAuthority) {
|
||||
this.logService.debug('[DefaultAccount] Running in web without remote, skipping initialization');
|
||||
@@ -323,7 +364,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchDefaultAccount(): Promise<IDefaultAccount | null> {
|
||||
private async fetchDefaultAccount(): Promise<IDefaultAccountData | null> {
|
||||
const defaultAccountProvider = this.getDefaultAccountAuthenticationProvider();
|
||||
this.logService.debug('[DefaultAccount] Default account provider ID:', defaultAccountProvider.id);
|
||||
|
||||
@@ -336,24 +377,47 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
return await this.getDefaultAccountForAuthenticationProvider(defaultAccountProvider);
|
||||
}
|
||||
|
||||
private setDefaultAccount(account: IDefaultAccount | null): void {
|
||||
private setDefaultAccount(account: IDefaultAccountData | null): void {
|
||||
if (equals(this._defaultAccount, account)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.trace('[DefaultAccount] Updating default account:', account);
|
||||
this._defaultAccount = account;
|
||||
this._onDidChangeDefaultAccount.fire(this._defaultAccount);
|
||||
if (this._defaultAccount) {
|
||||
if (account) {
|
||||
this._defaultAccount = account;
|
||||
this.setPolicyData(account.policyData);
|
||||
this._onDidChangeDefaultAccount.fire(this._defaultAccount.defaultAccount);
|
||||
this.accountStatusContext.set(DefaultAccountStatus.Available);
|
||||
this.logService.debug('[DefaultAccount] Account status set to Available');
|
||||
} else {
|
||||
this._defaultAccount = null;
|
||||
this.setPolicyData(null);
|
||||
this._onDidChangeDefaultAccount.fire(null);
|
||||
this.accountDataPollScheduler.cancel();
|
||||
this.accountStatusContext.set(DefaultAccountStatus.Unavailable);
|
||||
this.logService.debug('[DefaultAccount] Account status set to Unavailable');
|
||||
}
|
||||
}
|
||||
|
||||
private setPolicyData(accountPolicyData: IAccountPolicyData | null): void {
|
||||
if (equals(this._policyData, accountPolicyData)) {
|
||||
return;
|
||||
}
|
||||
this._policyData = accountPolicyData;
|
||||
this.cachePolicyData(accountPolicyData);
|
||||
this._onDidChangePolicyData.fire(this._policyData?.policyData ?? null);
|
||||
}
|
||||
|
||||
private cachePolicyData(accountPolicyData: IAccountPolicyData | null): void {
|
||||
if (accountPolicyData) {
|
||||
this.logService.debug('[DefaultAccount] Caching policy data for account:', accountPolicyData.accountId);
|
||||
this.storageService.store(CACHED_POLICY_DATA_KEY, JSON.stringify(accountPolicyData), StorageScope.APPLICATION, StorageTarget.MACHINE);
|
||||
} else {
|
||||
this.logService.debug('[DefaultAccount] Removing cached policy data');
|
||||
this.storageService.remove(CACHED_POLICY_DATA_KEY, StorageScope.APPLICATION);
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleAccountDataPoll(): void {
|
||||
if (!this._defaultAccount) {
|
||||
return;
|
||||
@@ -373,7 +437,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getDefaultAccountForAuthenticationProvider(authenticationProvider: IDefaultAccountAuthenticationProvider): Promise<IDefaultAccount | null> {
|
||||
private async getDefaultAccountForAuthenticationProvider(authenticationProvider: IDefaultAccountAuthenticationProvider): Promise<IDefaultAccountData | null> {
|
||||
try {
|
||||
this.logService.debug('[DefaultAccount] Getting Default Account from authenticated sessions for provider:', authenticationProvider.id);
|
||||
const sessions = await this.findMatchingProviderSession(authenticationProvider.id, this.defaultAccountConfig.authenticationProvider.scopes);
|
||||
@@ -390,7 +454,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
}
|
||||
}
|
||||
|
||||
private async getDefaultAccountFromAuthenticatedSessions(authenticationProvider: IDefaultAccountAuthenticationProvider, sessions: AuthenticationSession[]): Promise<IDefaultAccount | null> {
|
||||
private async getDefaultAccountFromAuthenticatedSessions(authenticationProvider: IDefaultAccountAuthenticationProvider, sessions: AuthenticationSession[]): Promise<IDefaultAccountData | null> {
|
||||
try {
|
||||
const accountId = sessions[0].account.id;
|
||||
const [entitlementsData, tokenEntitlementsData] = await Promise.all([
|
||||
@@ -398,7 +462,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
this.getTokenEntitlements(sessions),
|
||||
]);
|
||||
|
||||
let policyData = this.getCachedPolicyData(accountId);
|
||||
let policyData: Mutable<IPolicyData> | undefined = this._policyData?.accountId === accountId ? { ...this._policyData.policyData } : undefined;
|
||||
if (tokenEntitlementsData) {
|
||||
policyData = policyData ?? {};
|
||||
policyData.chat_agent_enabled = tokenEntitlementsData.chat_agent_enabled;
|
||||
@@ -411,18 +475,16 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
policyData.mcpAccess = mcpRegistryProvider.registry_access;
|
||||
}
|
||||
}
|
||||
this.cachePolicyData(accountId, policyData);
|
||||
}
|
||||
|
||||
const account: IDefaultAccount = {
|
||||
const defaultAccount: IDefaultAccount = {
|
||||
authenticationProvider,
|
||||
sessionId: sessions[0].id,
|
||||
enterprise: authenticationProvider.enterprise || sessions[0].account.label.includes('_'),
|
||||
entitlementsData,
|
||||
policyData,
|
||||
};
|
||||
this.logService.debug('[DefaultAccount] Successfully created default account for provider:', authenticationProvider.id);
|
||||
return account;
|
||||
return { defaultAccount, policyData: policyData ? { accountId, policyData } : null };
|
||||
} catch (error) {
|
||||
this.logService.error('[DefaultAccount] Failed to create default account for provider:', authenticationProvider.id, getErrorMessage(error));
|
||||
return null;
|
||||
@@ -515,28 +577,6 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private cachePolicyData(accountId: string, policyData: IPolicyData): void {
|
||||
this.logService.debug('[DefaultAccount] Caching policy data for account:', accountId);
|
||||
this.storageService.store(CACHED_POLICY_DATA_KEY, JSON.stringify({ accountId, policyData }), StorageScope.APPLICATION, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private getCachedPolicyData(accountId: string): Mutable<IPolicyData> | undefined {
|
||||
const cached = this.storageService.get(CACHED_POLICY_DATA_KEY, StorageScope.APPLICATION);
|
||||
if (cached) {
|
||||
try {
|
||||
const { accountId: cachedAccountId, policyData } = JSON.parse(cached);
|
||||
if (cachedAccountId === accountId) {
|
||||
this.logService.debug('[DefaultAccount] Using cached policy data for account:', accountId);
|
||||
return policyData;
|
||||
}
|
||||
this.logService.debug('[DefaultAccount] Cached policy data is for different account, ignoring');
|
||||
} catch (error) {
|
||||
this.logService.error('[DefaultAccount] Failed to parse cached policy data', getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async getEntitlements(sessions: AuthenticationSession[]): Promise<IEntitlementsData | undefined | null> {
|
||||
const entitlementUrl = this.getEntitlementUrl();
|
||||
if (!entitlementUrl) {
|
||||
@@ -744,7 +784,6 @@ class DefaultAccountProviderContribution extends Disposable implements IWorkbenc
|
||||
@IProductService productService: IProductService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IDefaultAccountService defaultAccountService: IDefaultAccountService,
|
||||
@ILogService logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
const defaultAccountProvider = this._register(instantiationService.createInstance(DefaultAccountProvider, toDefaultAccountConfig(productService.defaultChatAgent)));
|
||||
@@ -752,4 +791,4 @@ class DefaultAccountProviderContribution extends Disposable implements IWorkbenc
|
||||
}
|
||||
}
|
||||
|
||||
registerWorkbenchContribution2(DefaultAccountProviderContribution.ID, DefaultAccountProviderContribution, WorkbenchPhase.AfterRestored);
|
||||
registerWorkbenchContribution2(DefaultAccountProviderContribution.ID, DefaultAccountProviderContribution, WorkbenchPhase.BlockStartup);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IStringDictionary } from '../../../../base/common/collections.js';
|
||||
import { IDefaultAccount } from '../../../../base/common/defaultAccount.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { AbstractPolicyService, IPolicyService, PolicyDefinition } from '../../../../platform/policy/common/policy.js';
|
||||
import { IDefaultAccountService } from '../../../../platform/defaultAccount/common/defaultAccount.js';
|
||||
@@ -12,32 +11,26 @@ import { IDefaultAccountService } from '../../../../platform/defaultAccount/comm
|
||||
|
||||
export class AccountPolicyService extends AbstractPolicyService implements IPolicyService {
|
||||
|
||||
private account: IDefaultAccount | null = null;
|
||||
|
||||
constructor(
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IDefaultAccountService private readonly defaultAccountService: IDefaultAccountService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.defaultAccountService.getDefaultAccount()
|
||||
.then(account => {
|
||||
this.account = account;
|
||||
this._updatePolicyDefinitions(this.policyDefinitions);
|
||||
this._register(this.defaultAccountService.onDidChangeDefaultAccount(account => {
|
||||
this.account = account;
|
||||
this._updatePolicyDefinitions(this.policyDefinitions);
|
||||
}));
|
||||
});
|
||||
this._updatePolicyDefinitions(this.policyDefinitions);
|
||||
this._register(this.defaultAccountService.onDidChangePolicyData(() => {
|
||||
this._updatePolicyDefinitions(this.policyDefinitions);
|
||||
}));
|
||||
}
|
||||
|
||||
protected async _updatePolicyDefinitions(policyDefinitions: IStringDictionary<PolicyDefinition>): Promise<void> {
|
||||
this.logService.trace(`AccountPolicyService#_updatePolicyDefinitions: Got ${Object.keys(policyDefinitions).length} policy definitions`);
|
||||
const updated: string[] = [];
|
||||
const policyData = this.defaultAccountService.policyData;
|
||||
|
||||
for (const key in policyDefinitions) {
|
||||
const policy = policyDefinitions[key];
|
||||
const policyValue = this.account && policy.value ? policy.value(this.account) : undefined;
|
||||
const policyValue = policyData && policy.value ? policy.value(policyData) : undefined;
|
||||
if (policyValue !== undefined) {
|
||||
if (this.policies.get(key) !== policyValue) {
|
||||
this.policies.set(key, policyValue);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes
|
||||
import { Registry } from '../../../../../platform/registry/common/platform.js';
|
||||
import { Extensions, IConfigurationNode, IConfigurationRegistry } from '../../../../../platform/configuration/common/configurationRegistry.js';
|
||||
import { DefaultConfiguration, PolicyConfiguration } from '../../../../../platform/configuration/common/configurations.js';
|
||||
import { IDefaultAccount, IDefaultAccountAuthenticationProvider } from '../../../../../base/common/defaultAccount.js';
|
||||
import { IDefaultAccount, IDefaultAccountAuthenticationProvider, IPolicyData } from '../../../../../base/common/defaultAccount.js';
|
||||
import { PolicyCategory } from '../../../../../base/common/policy.js';
|
||||
import { TestProductService } from '../../../../test/common/workbenchTestServices.js';
|
||||
|
||||
@@ -30,9 +30,11 @@ const BASE_DEFAULT_ACCOUNT: IDefaultAccount = {
|
||||
class DefaultAccountProvider implements IDefaultAccountProvider {
|
||||
|
||||
readonly onDidChangeDefaultAccount = Event.None;
|
||||
readonly onDidChangePolicyData = Event.None;
|
||||
|
||||
constructor(
|
||||
readonly defaultAccount: IDefaultAccount,
|
||||
readonly policyData: IPolicyData = {},
|
||||
) { }
|
||||
|
||||
getDefaultAccountAuthenticationProvider(): IDefaultAccountAuthenticationProvider {
|
||||
@@ -81,7 +83,7 @@ suite('AccountPolicyService', () => {
|
||||
category: PolicyCategory.Extensions,
|
||||
minimumVersion: '1.0.0',
|
||||
localization: { description: { key: '', value: '' } },
|
||||
value: account => account.policyData?.chat_preview_features_enabled === false ? 'policyValueB' : undefined,
|
||||
value: policyData => policyData.chat_preview_features_enabled === false ? 'policyValueB' : undefined,
|
||||
}
|
||||
},
|
||||
'setting.C': {
|
||||
@@ -92,7 +94,7 @@ suite('AccountPolicyService', () => {
|
||||
category: PolicyCategory.Extensions,
|
||||
minimumVersion: '1.0.0',
|
||||
localization: { description: { key: '', value: '' } },
|
||||
value: account => account.policyData?.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined,
|
||||
value: policyData => policyData.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined,
|
||||
}
|
||||
},
|
||||
'setting.D': {
|
||||
@@ -103,7 +105,7 @@ suite('AccountPolicyService', () => {
|
||||
category: PolicyCategory.Extensions,
|
||||
minimumVersion: '1.0.0',
|
||||
localization: { description: { key: '', value: '' } },
|
||||
value: account => account.policyData?.chat_preview_features_enabled === false ? false : undefined,
|
||||
value: policyData => policyData.chat_preview_features_enabled === false ? false : undefined,
|
||||
}
|
||||
},
|
||||
'setting.E': {
|
||||
@@ -127,8 +129,8 @@ suite('AccountPolicyService', () => {
|
||||
|
||||
});
|
||||
|
||||
async function assertDefaultBehavior(defaultAccount: IDefaultAccount) {
|
||||
defaultAccountService.setDefaultAccountProvider(new DefaultAccountProvider(defaultAccount));
|
||||
async function assertDefaultBehavior(policyData: IPolicyData | undefined) {
|
||||
defaultAccountService.setDefaultAccountProvider(new DefaultAccountProvider(BASE_DEFAULT_ACCOUNT, policyData));
|
||||
await defaultAccountService.refresh();
|
||||
|
||||
await policyConfiguration.initialize();
|
||||
@@ -159,18 +161,16 @@ suite('AccountPolicyService', () => {
|
||||
|
||||
|
||||
test('should initialize with default account', async () => {
|
||||
const defaultAccount = { ...BASE_DEFAULT_ACCOUNT };
|
||||
await assertDefaultBehavior(defaultAccount);
|
||||
await assertDefaultBehavior(undefined);
|
||||
});
|
||||
|
||||
test('should initialize with default account and preview features enabled', async () => {
|
||||
const defaultAccount = { ...BASE_DEFAULT_ACCOUNT, policyData: { chat_preview_features_enabled: true } };
|
||||
await assertDefaultBehavior(defaultAccount);
|
||||
await assertDefaultBehavior({ chat_preview_features_enabled: true });
|
||||
});
|
||||
|
||||
test('should initialize with default account and preview features disabled', async () => {
|
||||
const defaultAccount = { ...BASE_DEFAULT_ACCOUNT, policyData: { chat_preview_features_enabled: false } };
|
||||
defaultAccountService.setDefaultAccountProvider(new DefaultAccountProvider(defaultAccount));
|
||||
const policyData: IPolicyData = { chat_preview_features_enabled: false };
|
||||
defaultAccountService.setDefaultAccountProvider(new DefaultAccountProvider(BASE_DEFAULT_ACCOUNT, policyData));
|
||||
await defaultAccountService.refresh();
|
||||
|
||||
await policyConfiguration.initialize();
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IFileService } from '../../../../../platform/files/common/files.js';
|
||||
import { InMemoryFileSystemProvider } from '../../../../../platform/files/common/inMemoryFilesystemProvider.js';
|
||||
import { FileService } from '../../../../../platform/files/common/fileService.js';
|
||||
import { VSBuffer } from '../../../../../base/common/buffer.js';
|
||||
import { IDefaultAccount, IDefaultAccountAuthenticationProvider } from '../../../../../base/common/defaultAccount.js';
|
||||
import { IDefaultAccount, IDefaultAccountAuthenticationProvider, IPolicyData } from '../../../../../base/common/defaultAccount.js';
|
||||
import { PolicyCategory } from '../../../../../base/common/policy.js';
|
||||
import { TestProductService } from '../../../../test/common/workbenchTestServices.js';
|
||||
|
||||
@@ -37,9 +37,11 @@ const BASE_DEFAULT_ACCOUNT: IDefaultAccount = {
|
||||
class DefaultAccountProvider implements IDefaultAccountProvider {
|
||||
|
||||
readonly onDidChangeDefaultAccount = Event.None;
|
||||
readonly onDidChangePolicyData = Event.None;
|
||||
|
||||
constructor(
|
||||
readonly defaultAccount: IDefaultAccount,
|
||||
readonly policyData: IPolicyData = {},
|
||||
) { }
|
||||
|
||||
getDefaultAccountAuthenticationProvider(): IDefaultAccountAuthenticationProvider {
|
||||
@@ -90,7 +92,7 @@ suite('MultiplexPolicyService', () => {
|
||||
category: PolicyCategory.Extensions,
|
||||
minimumVersion: '1.0.0',
|
||||
localization: { description: { key: '', value: '' } },
|
||||
value: account => account.policyData?.chat_preview_features_enabled === false ? 'policyValueB' : undefined,
|
||||
value: policyData => policyData.chat_preview_features_enabled === false ? 'policyValueB' : undefined,
|
||||
}
|
||||
},
|
||||
'setting.C': {
|
||||
@@ -101,7 +103,7 @@ suite('MultiplexPolicyService', () => {
|
||||
category: PolicyCategory.Extensions,
|
||||
minimumVersion: '1.0.0',
|
||||
localization: { description: { key: '', value: '' } },
|
||||
value: account => account.policyData?.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined,
|
||||
value: policyData => policyData.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined,
|
||||
}
|
||||
},
|
||||
'setting.D': {
|
||||
@@ -112,7 +114,7 @@ suite('MultiplexPolicyService', () => {
|
||||
category: PolicyCategory.Extensions,
|
||||
minimumVersion: '1.0.0',
|
||||
localization: { description: { key: '', value: '' } },
|
||||
value: account => account.policyData?.chat_preview_features_enabled === false ? false : undefined,
|
||||
value: policyData => policyData.chat_preview_features_enabled === false ? false : undefined,
|
||||
}
|
||||
},
|
||||
'setting.E': {
|
||||
@@ -186,8 +188,7 @@ suite('MultiplexPolicyService', () => {
|
||||
test('policy from file only', async () => {
|
||||
await clear();
|
||||
|
||||
const defaultAccount = { ...BASE_DEFAULT_ACCOUNT };
|
||||
defaultAccountService.setDefaultAccountProvider(new DefaultAccountProvider(defaultAccount));
|
||||
defaultAccountService.setDefaultAccountProvider(new DefaultAccountProvider(BASE_DEFAULT_ACCOUNT));
|
||||
await defaultAccountService.refresh();
|
||||
|
||||
await fileService.writeFile(policyFile,
|
||||
@@ -228,8 +229,8 @@ suite('MultiplexPolicyService', () => {
|
||||
test('policy from default account only', async () => {
|
||||
await clear();
|
||||
|
||||
const defaultAccount = { ...BASE_DEFAULT_ACCOUNT, policyData: { chat_preview_features_enabled: false } };
|
||||
defaultAccountService.setDefaultAccountProvider(new DefaultAccountProvider(defaultAccount));
|
||||
const policyData: IPolicyData = { chat_preview_features_enabled: false };
|
||||
defaultAccountService.setDefaultAccountProvider(new DefaultAccountProvider(BASE_DEFAULT_ACCOUNT, policyData));
|
||||
await defaultAccountService.refresh();
|
||||
|
||||
await fileService.writeFile(policyFile,
|
||||
@@ -269,8 +270,8 @@ suite('MultiplexPolicyService', () => {
|
||||
test('policy from file and default account', async () => {
|
||||
await clear();
|
||||
|
||||
const defaultAccount = { ...BASE_DEFAULT_ACCOUNT, policyData: { chat_preview_features_enabled: false } };
|
||||
defaultAccountService.setDefaultAccountProvider(new DefaultAccountProvider(defaultAccount));
|
||||
const policyData: IPolicyData = { chat_preview_features_enabled: false };
|
||||
defaultAccountService.setDefaultAccountProvider(new DefaultAccountProvider(BASE_DEFAULT_ACCOUNT, policyData));
|
||||
await defaultAccountService.refresh();
|
||||
|
||||
await fileService.writeFile(policyFile,
|
||||
|
||||
Reference in New Issue
Block a user