Merge branch 'microsoft:main' into display-type

This commit is contained in:
Rafael Sargento 2024-06-07 18:03:50 +01:00 committed by GitHub
commit 8219ce7cec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 960 additions and 411 deletions

View file

@ -119,7 +119,7 @@ export async function activate(context: vscode.ExtensionContext) {
map,
task,
kind === vscode.TestRunProfileKind.Debug
? await runner.debug(currentArgs, req.include)
? await runner.debug(task, currentArgs, req.include)
: await runner.run(currentArgs, req.include),
coverageDir,
cancellationToken

View file

@ -37,7 +37,7 @@ export abstract class VSCodeTestRunner {
return new TestOutputScanner(cp, args);
}
public async debug(baseArgs: ReadonlyArray<string>, filter?: ReadonlyArray<vscode.TestItem>) {
public async debug(testRun: vscode.TestRun, baseArgs: ReadonlyArray<string>, filter?: ReadonlyArray<vscode.TestItem>) {
const port = await this.findOpenPort();
const baseConfiguration = vscode.workspace
.getConfiguration('launch', this.repoLocation)
@ -95,7 +95,7 @@ export abstract class VSCodeTestRunner {
},
});
vscode.debug.startDebugging(this.repoLocation, { ...baseConfiguration, port });
vscode.debug.startDebugging(this.repoLocation, { ...baseConfiguration, port }, { testRun });
let exited = false;
let rootSession: vscode.DebugSession | undefined;

View file

@ -12,5 +12,6 @@
"../../../src/vscode-dts/vscode.d.ts",
"../../../src/vscode-dts/vscode.proposed.testObserver.d.ts",
"../../../src/vscode-dts/vscode.proposed.attributableCoverage.d.ts",
"../../../src/vscode-dts/vscode.proposed.testRunInDebug.d.ts",
]
}

View file

@ -2068,69 +2068,73 @@ export class CommandCenter {
let noStagedChanges = repository.indexGroup.resourceStates.length === 0;
let noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
if (promptToSaveFilesBeforeCommit !== 'never') {
let documents = workspace.textDocuments
.filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath));
if (!opts.empty) {
if (promptToSaveFilesBeforeCommit !== 'never') {
let documents = workspace.textDocuments
.filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath));
if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) {
documents = documents
.filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath)));
}
if (documents.length > 0) {
const message = documents.length === 1
? l10n.t('The following file has unsaved changes which won\'t be included in the commit if you proceed: {0}.\n\nWould you like to save it before committing?', path.basename(documents[0].uri.fsPath))
: l10n.t('There are {0} unsaved files.\n\nWould you like to save them before committing?', documents.length);
const saveAndCommit = l10n.t('Save All & Commit Changes');
const commit = l10n.t('Commit Changes');
const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit);
if (pick === saveAndCommit) {
await Promise.all(documents.map(d => d.save()));
// After saving the dirty documents, if there are any documents that are part of the
// index group we have to add them back in order for the saved changes to be committed
if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) {
documents = documents
.filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath)));
await repository.add(documents.map(d => d.uri));
}
noStagedChanges = repository.indexGroup.resourceStates.length === 0;
noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
} else if (pick !== commit) {
return; // do not commit on cancel
if (documents.length > 0) {
const message = documents.length === 1
? l10n.t('The following file has unsaved changes which won\'t be included in the commit if you proceed: {0}.\n\nWould you like to save it before committing?', path.basename(documents[0].uri.fsPath))
: l10n.t('There are {0} unsaved files.\n\nWould you like to save them before committing?', documents.length);
const saveAndCommit = l10n.t('Save All & Commit Changes');
const commit = l10n.t('Commit Changes');
const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit);
if (pick === saveAndCommit) {
await Promise.all(documents.map(d => d.save()));
// After saving the dirty documents, if there are any documents that are part of the
// index group we have to add them back in order for the saved changes to be committed
documents = documents
.filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath)));
await repository.add(documents.map(d => d.uri));
noStagedChanges = repository.indexGroup.resourceStates.length === 0;
noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
} else if (pick !== commit) {
return; // do not commit on cancel
}
}
}
}
// no changes, and the user has not configured to commit all in this case
if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty && !opts.all) {
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
if (!opts.amend) {
// no changes, and the user has not configured to commit all in this case
if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.all) {
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
if (!suggestSmartCommit) {
return;
if (!suggestSmartCommit) {
return;
}
// prompt the user if we want to commit all or not
const message = l10n.t('There are no staged changes to commit.\n\nWould you like to stage all your changes and commit them directly?');
const yes = l10n.t('Yes');
const always = l10n.t('Always');
const never = l10n.t('Never');
const pick = await window.showWarningMessage(message, { modal: true }, yes, always, never);
if (pick === always) {
config.update('enableSmartCommit', true, true);
} else if (pick === never) {
config.update('suggestSmartCommit', false, true);
return;
} else if (pick !== yes) {
return; // do not commit on cancel
}
}
if (opts.all === undefined) {
opts = { ...opts, all: noStagedChanges };
} else if (!opts.all && noStagedChanges) {
opts = { ...opts, all: true };
}
}
// prompt the user if we want to commit all or not
const message = l10n.t('There are no staged changes to commit.\n\nWould you like to stage all your changes and commit them directly?');
const yes = l10n.t('Yes');
const always = l10n.t('Always');
const never = l10n.t('Never');
const pick = await window.showWarningMessage(message, { modal: true }, yes, always, never);
if (pick === always) {
config.update('enableSmartCommit', true, true);
} else if (pick === never) {
config.update('suggestSmartCommit', false, true);
return;
} else if (pick !== yes) {
return; // do not commit on cancel
}
}
if (opts.all === undefined) {
opts = { ...opts, all: noStagedChanges };
} else if (!opts.all && noStagedChanges && !opts.empty) {
opts = { ...opts, all: true };
}
// enable signing of commits if configured

View file

@ -261,4 +261,4 @@ fi
wait_for_async_execs
exec "$@"
exec "$@" "--no-sandbox" "--use-gl=angle" "--use-angle=swiftshader"

View file

@ -74,8 +74,8 @@ parts:
apps:
@@NAME@@:
command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --no-sandbox
command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@
common-id: @@NAME@@.desktop
url-handler:
command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --open-url --no-sandbox
command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --open-url

View file

@ -294,6 +294,13 @@ function configureCommandlineSwitchesSync(cliArgs) {
`CalculateNativeWinOcclusion,${app.commandLine.getSwitchValue('disable-features')}`;
app.commandLine.appendSwitch('disable-features', featuresToDisable);
// Blink features to configure.
// `FontMatchingCTMigration` - Siwtch font matching on macOS to CoreText (Refs https://github.com/microsoft/vscode/issues/214390).
// TODO(deepak1556): Enable this feature again after updating to Electron 30.
const blinkFeaturesToDisable =
`FontMatchingCTMigration,${app.commandLine.getSwitchValue('disable-blink-features')}`;
app.commandLine.appendSwitch('disable-blink-features', blinkFeaturesToDisable);
// Support JS Flags
const jsFlags = getJSFlags(cliArgs);
if (jsFlags) {

View file

@ -5,15 +5,13 @@
import * as dom from 'vs/base/browser/dom';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { TokenizationRegistry } from 'vs/editor/common/languages';
import { HoverOperation, HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation';
import { HoverAnchor, HoverParticipantRegistry, HoverRangeAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IHoverWidget } from 'vs/editor/contrib/hover/browser/hoverTypes';
import { HoverAnchor, HoverParticipantRegistry, HoverRangeAnchor, IEditorHoverContext, IEditorHoverParticipant, IHoverPart, IHoverWidget } from 'vs/editor/contrib/hover/browser/hoverTypes';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { MarkdownHoverParticipant } from 'vs/editor/contrib/hover/browser/markdownHoverParticipant';
@ -21,10 +19,10 @@ import { InlayHintsHover } from 'vs/editor/contrib/inlayHints/browser/inlayHints
import { HoverVerbosityAction } from 'vs/editor/common/standalone/standaloneEnums';
import { ContentHoverWidget } from 'vs/editor/contrib/hover/browser/contentHoverWidget';
import { ContentHoverComputer } from 'vs/editor/contrib/hover/browser/contentHoverComputer';
import { ContentHoverVisibleData, HoverResult } from 'vs/editor/contrib/hover/browser/contentHoverTypes';
import { EditorHoverStatusBar } from 'vs/editor/contrib/hover/browser/contentHoverStatusBar';
import { HoverResult } from 'vs/editor/contrib/hover/browser/contentHoverTypes';
import { Emitter } from 'vs/base/common/event';
import { ColorHoverParticipant } from 'vs/editor/contrib/colorPicker/browser/colorHoverParticipant';
import { RenderedContentHover } from 'vs/editor/contrib/hover/browser/contentHoverRendered';
export class ContentHoverController extends Disposable implements IHoverWidget {
@ -177,9 +175,9 @@ export class ContentHoverController extends Disposable implements IHoverWidget {
}
this._currentResult = currentHoverResult;
if (this._currentResult) {
this._showHover(this._currentResult.anchor, this._currentResult.hoverParts);
this._showHover(this._currentResult);
} else {
this._contentHoverWidget.hide();
this._hideHover();
}
}
@ -221,18 +219,21 @@ export class ContentHoverController extends Disposable implements IHoverWidget {
this._setCurrentResult(hoverResult);
}
private _showHover(anchor: HoverAnchor, hoverParts: IHoverPart[]): void {
const fragment = document.createDocumentFragment();
const disposables = this._renderHoverPartsInFragment(fragment, hoverParts);
const fragmentHasContent = fragment.hasChildNodes();
if (fragmentHasContent) {
this._doShowHover(fragment, hoverParts, anchor, disposables);
private _showHover(hoverResult: HoverResult): void {
const context = this._getHoverContext();
const renderedHover = new RenderedContentHover(this._editor, hoverResult, this._participants, this._computer, context, this._keybindingService);
if (renderedHover.domNodeHasChildren) {
this._contentHoverWidget.show(renderedHover);
} else {
disposables.dispose();
renderedHover.dispose();
}
}
private _getHoverContext(fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IEditorHoverRenderContext {
private _hideHover(): void {
this._contentHoverWidget.hide();
}
private _getHoverContext(): IEditorHoverContext {
const hide = () => {
this.hide();
};
@ -243,122 +244,9 @@ export class ContentHoverController extends Disposable implements IHoverWidget {
const setMinimumDimensions = (dimensions: dom.Dimension) => {
this._contentHoverWidget.setMinimumDimensions(dimensions);
};
const context: IEditorHoverRenderContext = { fragment, statusBar, hide, onContentsChanged, setMinimumDimensions };
return context;
return { hide, onContentsChanged, setMinimumDimensions };
}
private _renderHoverPartsInFragment(fragment: DocumentFragment, hoverParts: IHoverPart[]): DisposableStore {
const disposables = new DisposableStore();
const statusBar = new EditorHoverStatusBar(this._keybindingService);
const context = this._getHoverContext(fragment, statusBar);
disposables.add(this._renderHoverPartsUsingContext(context, hoverParts));
disposables.add(this._renderStatusBar(fragment, statusBar));
return disposables;
}
private _renderHoverPartsUsingContext(context: IEditorHoverRenderContext, hoverParts: IHoverPart[]): IDisposable {
const disposables = new DisposableStore();
for (const participant of this._participants) {
const hoverPartsForParticipant = hoverParts.filter(hoverPart => hoverPart.owner === participant);
const hasHoverPartsForParticipant = hoverPartsForParticipant.length > 0;
if (!hasHoverPartsForParticipant) {
continue;
}
disposables.add(participant.renderHoverParts(context, hoverPartsForParticipant));
}
return disposables;
}
private _renderStatusBar(fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IDisposable {
if (!statusBar.hasContent) {
return Disposable.None;
}
fragment.appendChild(statusBar.hoverElement);
return statusBar;
}
private _doShowHover(fragment: DocumentFragment, hoverParts: IHoverPart[], anchor: HoverAnchor, disposables: DisposableStore): void {
const { showAtPosition, showAtSecondaryPosition, highlightRange } = ContentHoverController.computeHoverRanges(this._editor, anchor.range, hoverParts);
this._addEditorDecorations(highlightRange, disposables);
const initialMousePosX = anchor.initialMousePosX;
const initialMousePosY = anchor.initialMousePosY;
const preferAbove = this._editor.getOption(EditorOption.hover).above;
const stoleFocus = this._computer.shouldFocus;
const hoverSource = this._computer.source;
const isBeforeContent = hoverParts.some(m => m.isBeforeContent);
const contentHoverVisibleData = new ContentHoverVisibleData(
initialMousePosX,
initialMousePosY,
showAtPosition,
showAtSecondaryPosition,
preferAbove,
stoleFocus,
hoverSource,
isBeforeContent,
disposables
);
this._contentHoverWidget.showAt(fragment, contentHoverVisibleData);
}
private _addEditorDecorations(highlightRange: Range | undefined, disposables: DisposableStore) {
if (!highlightRange) {
return;
}
const highlightDecoration = this._editor.createDecorationsCollection();
highlightDecoration.set([{
range: highlightRange,
options: ContentHoverController._DECORATION_OPTIONS
}]);
disposables.add(toDisposable(() => {
highlightDecoration.clear();
}));
}
private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
description: 'content-hover-highlight',
className: 'hoverHighlight'
});
public static computeHoverRanges(editor: ICodeEditor, anchorRange: Range, hoverParts: IHoverPart[]) {
let startColumnBoundary = 1;
if (editor.hasModel()) {
// Ensure the range is on the current view line
const viewModel = editor._getViewModel();
const coordinatesConverter = viewModel.coordinatesConverter;
const anchorViewRange = coordinatesConverter.convertModelRangeToViewRange(anchorRange);
const anchorViewRangeStart = new Position(anchorViewRange.startLineNumber, viewModel.getLineMinColumn(anchorViewRange.startLineNumber));
startColumnBoundary = coordinatesConverter.convertViewPositionToModelPosition(anchorViewRangeStart).column;
}
// The anchor range is always on a single line
const anchorLineNumber = anchorRange.startLineNumber;
let renderStartColumn = anchorRange.startColumn;
let highlightRange = hoverParts[0].range;
let forceShowAtRange = null;
for (const hoverPart of hoverParts) {
highlightRange = Range.plusRange(highlightRange, hoverPart.range);
const hoverRangeIsWithinAnchorLine = hoverPart.range.startLineNumber === anchorLineNumber && hoverPart.range.endLineNumber === anchorLineNumber;
if (hoverRangeIsWithinAnchorLine) {
// this message has a range that is completely sitting on the line of the anchor
renderStartColumn = Math.max(Math.min(renderStartColumn, hoverPart.range.startColumn), startColumnBoundary);
}
if (hoverPart.forceShowAtRange) {
forceShowAtRange = hoverPart.range;
}
}
const showAtPosition = forceShowAtRange ? forceShowAtRange.getStartPosition() : new Position(anchorLineNumber, anchorRange.startColumn);
const showAtSecondaryPosition = forceShowAtRange ? forceShowAtRange.getStartPosition() : new Position(anchorLineNumber, renderStartColumn);
return {
showAtPosition,
showAtSecondaryPosition,
highlightRange
};
}
public showsOrWillShow(mouseEvent: IEditorMouseEvent): boolean {
const isContentWidgetResizing = this._contentHoverWidget.isResizing;

View file

@ -0,0 +1,193 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorHoverContext, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ContentHoverComputer } from 'vs/editor/contrib/hover/browser/contentHoverComputer';
import { EditorHoverStatusBar } from 'vs/editor/contrib/hover/browser/contentHoverStatusBar';
import { HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { HoverResult } from 'vs/editor/contrib/hover/browser/contentHoverTypes';
export class RenderedContentHover extends Disposable {
public closestMouseDistance: number | undefined;
public initialMousePosX: number | undefined;
public initialMousePosY: number | undefined;
public readonly showAtPosition: Position;
public readonly showAtSecondaryPosition: Position;
public readonly shouldFocus: boolean;
public readonly source: HoverStartSource;
public readonly shouldAppearBeforeContent: boolean;
private readonly _renderedHoverParts: RenderedContentHoverParts;
constructor(
editor: ICodeEditor,
hoverResult: HoverResult,
participants: IEditorHoverParticipant<IHoverPart>[],
computer: ContentHoverComputer,
context: IEditorHoverContext,
keybindingService: IKeybindingService
) {
super();
const anchor = hoverResult.anchor;
const parts = hoverResult.hoverParts;
this._renderedHoverParts = this._register(new RenderedContentHoverParts(
editor,
participants,
parts,
context,
keybindingService
));
const { showAtPosition, showAtSecondaryPosition } = RenderedContentHover.computeHoverPositions(editor, anchor.range, parts);
this.shouldAppearBeforeContent = parts.some(m => m.isBeforeContent);
this.showAtPosition = showAtPosition;
this.showAtSecondaryPosition = showAtSecondaryPosition;
this.initialMousePosX = anchor.initialMousePosX;
this.initialMousePosY = anchor.initialMousePosY;
this.shouldFocus = computer.shouldFocus;
this.source = computer.source;
}
public get domNode(): DocumentFragment {
return this._renderedHoverParts.domNode;
}
public get domNodeHasChildren(): boolean {
return this._renderedHoverParts.domNodeHasChildren;
}
public static computeHoverPositions(editor: ICodeEditor, anchorRange: Range, hoverParts: IHoverPart[]): { showAtPosition: Position; showAtSecondaryPosition: Position } {
let startColumnBoundary = 1;
if (editor.hasModel()) {
// Ensure the range is on the current view line
const viewModel = editor._getViewModel();
const coordinatesConverter = viewModel.coordinatesConverter;
const anchorViewRange = coordinatesConverter.convertModelRangeToViewRange(anchorRange);
const anchorViewMinColumn = viewModel.getLineMinColumn(anchorViewRange.startLineNumber);
const anchorViewRangeStart = new Position(anchorViewRange.startLineNumber, anchorViewMinColumn);
startColumnBoundary = coordinatesConverter.convertViewPositionToModelPosition(anchorViewRangeStart).column;
}
// The anchor range is always on a single line
const anchorStartLineNumber = anchorRange.startLineNumber;
let secondaryPositionColumn = anchorRange.startColumn;
let forceShowAtRange: Range | undefined;
for (const hoverPart of hoverParts) {
const hoverPartRange = hoverPart.range;
const hoverPartRangeOnAnchorStartLine = hoverPartRange.startLineNumber === anchorStartLineNumber;
const hoverPartRangeOnAnchorEndLine = hoverPartRange.endLineNumber === anchorStartLineNumber;
const hoverPartRangeIsOnAnchorLine = hoverPartRangeOnAnchorStartLine && hoverPartRangeOnAnchorEndLine;
if (hoverPartRangeIsOnAnchorLine) {
// this message has a range that is completely sitting on the line of the anchor
const hoverPartStartColumn = hoverPartRange.startColumn;
const minSecondaryPositionColumn = Math.min(secondaryPositionColumn, hoverPartStartColumn);
secondaryPositionColumn = Math.max(minSecondaryPositionColumn, startColumnBoundary);
}
if (hoverPart.forceShowAtRange) {
forceShowAtRange = hoverPartRange;
}
}
let showAtPosition: Position;
let showAtSecondaryPosition: Position;
if (forceShowAtRange) {
const forceShowAtPosition = forceShowAtRange.getStartPosition();
showAtPosition = forceShowAtPosition;
showAtSecondaryPosition = forceShowAtPosition;
} else {
showAtPosition = anchorRange.getStartPosition();
showAtSecondaryPosition = new Position(anchorStartLineNumber, secondaryPositionColumn);
}
return {
showAtPosition,
showAtSecondaryPosition,
};
}
}
class RenderedContentHoverParts extends Disposable {
private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
description: 'content-hover-highlight',
className: 'hoverHighlight'
});
private readonly _participants: IEditorHoverParticipant<IHoverPart>[];
private readonly _fragment: DocumentFragment;
constructor(
editor: ICodeEditor,
participants: IEditorHoverParticipant<IHoverPart>[],
hoverParts: IHoverPart[],
context: IEditorHoverContext,
keybindingService: IKeybindingService
) {
super();
this._participants = participants;
this._fragment = document.createDocumentFragment();
const statusBar = new EditorHoverStatusBar(keybindingService);
const hoverContext: IEditorHoverRenderContext = { fragment: this._fragment, statusBar, ...context };
this._register(this._renderHoverParts(hoverContext, hoverParts));
this._register(this._renderStatusBar(this._fragment, statusBar));
this._register(this._addEditorDecorations(editor, hoverParts));
}
private _addEditorDecorations(editor: ICodeEditor, hoverParts: IHoverPart[]): IDisposable {
if (hoverParts.length === 0) {
return Disposable.None;
}
let highlightRange = hoverParts[0].range;
for (const hoverPart of hoverParts) {
const hoverPartRange = hoverPart.range;
highlightRange = Range.plusRange(highlightRange, hoverPartRange);
}
const highlightDecoration = editor.createDecorationsCollection();
highlightDecoration.set([{
range: highlightRange,
options: RenderedContentHoverParts._DECORATION_OPTIONS
}]);
return toDisposable(() => {
highlightDecoration.clear();
});
}
private _renderHoverParts(context: IEditorHoverRenderContext, hoverParts: IHoverPart[]): IDisposable {
const disposables = new DisposableStore();
for (const participant of this._participants) {
const hoverPartsForParticipant = hoverParts.filter(hoverPart => hoverPart.owner === participant);
const hasHoverPartsForParticipant = hoverPartsForParticipant.length > 0;
if (!hasHoverPartsForParticipant) {
continue;
}
disposables.add(participant.renderHoverParts(context, hoverPartsForParticipant));
}
return disposables;
}
private _renderStatusBar(fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IDisposable {
if (!statusBar.hasContent) {
return Disposable.None;
}
fragment.appendChild(statusBar.hoverElement);
return statusBar;
}
public get domNode(): DocumentFragment {
return this._fragment;
}
public get domNodeHasChildren(): boolean {
return this._fragment.hasChildNodes();
}
}

View file

@ -3,9 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Position } from 'vs/editor/common/core/position';
import { HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation';
import { HoverAnchor, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes';
export class HoverResult {
@ -40,20 +37,3 @@ export class FilteredHoverResult extends HoverResult {
return this.original.filter(anchor);
}
}
export class ContentHoverVisibleData {
public closestMouseDistance: number | undefined = undefined;
constructor(
public initialMousePosX: number | undefined,
public initialMousePosY: number | undefined,
public readonly showAtPosition: Position,
public readonly showAtSecondaryPosition: Position,
public readonly preferAbove: boolean,
public readonly stoleFocus: boolean,
public readonly source: HoverStartSource,
public readonly isBeforeContent: boolean,
public readonly disposables: DisposableStore
) { }
}

View file

@ -15,8 +15,8 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { getHoverAccessibleViewHint, HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget';
import { PositionAffinity } from 'vs/editor/common/model';
import { ContentHoverVisibleData } from 'vs/editor/contrib/hover/browser/contentHoverTypes';
import { Emitter } from 'vs/base/common/event';
import { RenderedContentHover } from 'vs/editor/contrib/hover/browser/contentHoverRendered';
const HORIZONTAL_SCROLLING_BY = 30;
const CONTAINER_HEIGHT_PADDING = 6;
@ -26,7 +26,7 @@ export class ContentHoverWidget extends ResizableContentWidget {
public static ID = 'editor.contrib.resizableContentHoverWidget';
private static _lastDimensions: dom.Dimension = new dom.Dimension(0, 0);
private _visibleData: ContentHoverVisibleData | undefined;
private _renderedHover: RenderedContentHover | undefined;
private _positionPreference: ContentWidgetPositionPreference | undefined;
private _minimumSize: dom.Dimension;
private _contentWidth: number | undefined;
@ -39,7 +39,7 @@ export class ContentHoverWidget extends ResizableContentWidget {
public readonly onDidResize = this._onDidResize.event;
public get isVisibleFromKeyboard(): boolean {
return (this._visibleData?.source === HoverStartSource.Keyboard);
return (this._renderedHover?.source === HoverStartSource.Keyboard);
}
public get isVisible(): boolean {
@ -86,13 +86,13 @@ export class ContentHoverWidget extends ResizableContentWidget {
this._register(focusTracker.onDidBlur(() => {
this._hoverFocusedKey.set(false);
}));
this._setHoverData(undefined);
this._setRenderedHover(undefined);
this._editor.addContentWidget(this);
}
public override dispose(): void {
super.dispose();
this._visibleData?.disposables.dispose();
this._renderedHover?.dispose();
this._editor.removeContentWidget(this);
}
@ -162,7 +162,7 @@ export class ContentHoverWidget extends ResizableContentWidget {
}
private _findAvailableSpaceVertically(): number | undefined {
const position = this._visibleData?.showAtPosition;
const position = this._renderedHover?.showAtPosition;
if (!position) {
return;
}
@ -223,23 +223,20 @@ export class ContentHoverWidget extends ResizableContentWidget {
public isMouseGettingCloser(posx: number, posy: number): boolean {
if (!this._visibleData) {
if (!this._renderedHover) {
return false;
}
if (
typeof this._visibleData.initialMousePosX === 'undefined'
|| typeof this._visibleData.initialMousePosY === 'undefined'
) {
this._visibleData.initialMousePosX = posx;
this._visibleData.initialMousePosY = posy;
if (this._renderedHover.initialMousePosX === undefined || this._renderedHover.initialMousePosY === undefined) {
this._renderedHover.initialMousePosX = posx;
this._renderedHover.initialMousePosY = posy;
return false;
}
const widgetRect = dom.getDomNodePagePosition(this.getDomNode());
if (typeof this._visibleData.closestMouseDistance === 'undefined') {
this._visibleData.closestMouseDistance = computeDistanceFromPointToRectangle(
this._visibleData.initialMousePosX,
this._visibleData.initialMousePosY,
if (this._renderedHover.closestMouseDistance === undefined) {
this._renderedHover.closestMouseDistance = computeDistanceFromPointToRectangle(
this._renderedHover.initialMousePosX,
this._renderedHover.initialMousePosY,
widgetRect.left,
widgetRect.top,
widgetRect.width,
@ -255,20 +252,20 @@ export class ContentHoverWidget extends ResizableContentWidget {
widgetRect.width,
widgetRect.height
);
if (distance > this._visibleData.closestMouseDistance + 4 /* tolerance of 4 pixels */) {
if (distance > this._renderedHover.closestMouseDistance + 4 /* tolerance of 4 pixels */) {
// The mouse is getting farther away
return false;
}
this._visibleData.closestMouseDistance = Math.min(this._visibleData.closestMouseDistance, distance);
this._renderedHover.closestMouseDistance = Math.min(this._renderedHover.closestMouseDistance, distance);
return true;
}
private _setHoverData(hoverData: ContentHoverVisibleData | undefined): void {
this._visibleData?.disposables.dispose();
this._visibleData = hoverData;
this._hoverVisibleKey.set(!!hoverData);
this._hover.containerDomNode.classList.toggle('hidden', !hoverData);
private _setRenderedHover(renderedHover: RenderedContentHover | undefined): void {
this._renderedHover?.dispose();
this._renderedHover = renderedHover;
this._hoverVisibleKey.set(!!renderedHover);
this._hover.containerDomNode.classList.toggle('hidden', !renderedHover);
}
private _updateFont(): void {
@ -298,10 +295,10 @@ export class ContentHoverWidget extends ResizableContentWidget {
this._setHoverWidgetMaxDimensions(width, height);
}
private _render(node: DocumentFragment, hoverData: ContentHoverVisibleData) {
this._setHoverData(hoverData);
private _render(renderedHover: RenderedContentHover) {
this._setRenderedHover(renderedHover);
this._updateFont();
this._updateContent(node);
this._updateContent(renderedHover.domNode);
this._updateMaxDimensions();
this.onContentsChanged();
// Simply force a synchronous render on the editor
@ -310,30 +307,30 @@ export class ContentHoverWidget extends ResizableContentWidget {
}
override getPosition(): IContentWidgetPosition | null {
if (!this._visibleData) {
if (!this._renderedHover) {
return null;
}
return {
position: this._visibleData.showAtPosition,
secondaryPosition: this._visibleData.showAtSecondaryPosition,
positionAffinity: this._visibleData.isBeforeContent ? PositionAffinity.LeftOfInjectedText : undefined,
position: this._renderedHover.showAtPosition,
secondaryPosition: this._renderedHover.showAtSecondaryPosition,
positionAffinity: this._renderedHover.shouldAppearBeforeContent ? PositionAffinity.LeftOfInjectedText : undefined,
preference: [this._positionPreference ?? ContentWidgetPositionPreference.ABOVE]
};
}
public showAt(node: DocumentFragment, hoverData: ContentHoverVisibleData): void {
public show(renderedHover: RenderedContentHover): void {
if (!this._editor || !this._editor.hasModel()) {
return;
}
this._render(node, hoverData);
this._render(renderedHover);
const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode);
const widgetPosition = hoverData.showAtPosition;
const widgetPosition = renderedHover.showAtPosition;
this._positionPreference = this._findPositionPreference(widgetHeight, widgetPosition) ?? ContentWidgetPositionPreference.ABOVE;
// See https://github.com/microsoft/vscode/issues/140339
// TODO: Doing a second layout of the hover after force rendering the editor
this.onContentsChanged();
if (hoverData.stoleFocus) {
if (renderedHover.shouldFocus) {
this._hover.containerDomNode.focus();
}
this._onDidResize.fire();
@ -350,16 +347,16 @@ export class ContentHoverWidget extends ResizableContentWidget {
}
public hide(): void {
if (!this._visibleData) {
if (!this._renderedHover) {
return;
}
const stoleFocus = this._visibleData.stoleFocus || this._hoverFocusedKey.get();
this._setHoverData(undefined);
const hoverStoleFocus = this._renderedHover.shouldFocus || this._hoverFocusedKey.get();
this._setRenderedHover(undefined);
this._resizableNode.maxSize = new dom.Dimension(Infinity, Infinity);
this._resizableNode.clearSashHoverState();
this._hoverFocusedKey.set(false);
this._editor.layoutContentWidget(this);
if (stoleFocus) {
if (hoverStoleFocus) {
this._editor.focus();
}
}
@ -406,9 +403,9 @@ export class ContentHoverWidget extends ResizableContentWidget {
this._updateMinimumWidth();
this._resizableNode.layout(height, width);
if (this._visibleData?.showAtPosition) {
if (this._renderedHover?.showAtPosition) {
const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode);
this._positionPreference = this._findPositionPreference(widgetHeight, this._visibleData.showAtPosition);
this._positionPreference = this._findPositionPreference(widgetHeight, this._renderedHover.showAtPosition);
}
this._layoutContentWidget();
}

View file

@ -94,15 +94,7 @@ export interface IEditorHoverColorPickerWidget {
layout(): void;
}
export interface IEditorHoverRenderContext {
/**
* The fragment where dom elements should be attached.
*/
readonly fragment: DocumentFragment;
/**
* The status bar for actions for this hover.
*/
readonly statusBar: IEditorHoverStatusBar;
export interface IEditorHoverContext {
/**
* The contents rendered inside the fragment have been changed, which means that the hover should relayout.
*/
@ -117,6 +109,17 @@ export interface IEditorHoverRenderContext {
hide(): void;
}
export interface IEditorHoverRenderContext extends IEditorHoverContext {
/**
* The fragment where dom elements should be attached.
*/
readonly fragment: DocumentFragment;
/**
* The status bar for actions for this hover.
*/
readonly statusBar: IEditorHoverStatusBar;
}
export interface IEditorHoverParticipant<T extends IHoverPart = IHoverPart> {
readonly hoverOrdinal: number;
suggestHoverAnchor?(mouseEvent: IEditorMouseEvent): HoverAnchor | null;

View file

@ -7,7 +7,7 @@ import assert from 'assert';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController';
import { RenderedContentHover } from 'vs/editor/contrib/hover/browser/contentHoverRendered';
import { IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes';
import { TestCodeEditorInstantiationOptions, withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
@ -18,7 +18,7 @@ suite('Content Hover', () => {
test('issue #151235: Gitlens hover shows up in the wrong place', () => {
const text = 'just some text';
withTestCodeEditor(text, {}, (editor) => {
const actual = ContentHoverController.computeHoverRanges(
const actual = RenderedContentHover.computeHoverPositions(
editor,
new Range(5, 5, 5, 5),
[<IHoverPart>{ range: new Range(4, 1, 5, 6) }]
@ -27,8 +27,7 @@ suite('Content Hover', () => {
actual,
{
showAtPosition: new Position(5, 5),
showAtSecondaryPosition: new Position(5, 5),
highlightRange: new Range(4, 1, 5, 6)
showAtSecondaryPosition: new Position(5, 5)
}
);
});
@ -38,7 +37,7 @@ suite('Content Hover', () => {
const text = 'just some text';
const opts: TestCodeEditorInstantiationOptions = { wordWrap: 'wordWrapColumn', wordWrapColumn: 6 };
withTestCodeEditor(text, opts, (editor) => {
const actual = ContentHoverController.computeHoverRanges(
const actual = RenderedContentHover.computeHoverPositions(
editor,
new Range(1, 8, 1, 8),
[<IHoverPart>{ range: new Range(1, 1, 1, 15) }]
@ -47,8 +46,7 @@ suite('Content Hover', () => {
actual,
{
showAtPosition: new Position(1, 8),
showAtSecondaryPosition: new Position(1, 6),
highlightRange: new Range(1, 1, 1, 15)
showAtSecondaryPosition: new Position(1, 6)
}
);
});

View file

@ -329,6 +329,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
compact: options.compact,
compoundRoot: parentSession?.compoundRoot,
saveBeforeRestart: saveBeforeStart,
testRun: options.testRun,
suppressDebugStatusbar: options.suppressDebugStatusbar,
suppressDebugToolbar: options.suppressDebugToolbar,

View file

@ -230,10 +230,9 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider {
get acceptInputCommand(): Command | undefined { return this.features.acceptInputCommand; }
get actionButton(): ISCMActionButtonDescriptor | undefined { return this.features.actionButton ?? undefined; }
get statusBarCommands(): Command[] | undefined { return this.features.statusBarCommands; }
get count(): number | undefined { return this.features.count; }
private readonly _countObs = observableValue<number | undefined>(this, undefined);
get countObs() { return this._countObs; }
private readonly _count = observableValue<number | undefined>(this, undefined);
get count() { return this._count; }
private readonly _statusBarCommandsObs = observableValue<readonly Command[] | undefined>(this, undefined);
get statusBarCommandsObs() { return this._statusBarCommandsObs; }
@ -288,7 +287,7 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider {
}
if (typeof features.count !== 'undefined') {
this._countObs.set(features.count, undefined);
this._count.set(features.count, undefined);
}
if (typeof features.statusBarCommands !== 'undefined') {

View file

@ -84,7 +84,7 @@ import { IExtHostTask } from 'vs/workbench/api/common/extHostTask';
import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
import { IExtHostTerminalShellIntegration } from 'vs/workbench/api/common/extHostTerminalShellIntegration';
import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
import { IExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors';
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline';
@ -205,7 +205,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostWebviewPanels = rpcProtocol.set(ExtHostContext.ExtHostWebviewPanels, new ExtHostWebviewPanels(rpcProtocol, extHostWebviews, extHostWorkspace));
const extHostCustomEditors = rpcProtocol.set(ExtHostContext.ExtHostCustomEditors, new ExtHostCustomEditors(rpcProtocol, extHostDocuments, extensionStoragePaths, extHostWebviews, extHostWebviewPanels));
const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews));
const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, new ExtHostTesting(rpcProtocol, extHostLogService, extHostCommands, extHostDocumentsAndEditors));
const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, accessor.get(IExtHostTesting));
const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol));
const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol));
rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService));

View file

@ -31,6 +31,7 @@ import { ExtHostManagedSockets, IExtHostManagedSockets } from 'vs/workbench/api/
import { ExtHostAuthentication, IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication';
import { ExtHostLanguageModels, IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels';
import { IExtHostTerminalShellIntegration, ExtHostTerminalShellIntegration } from 'vs/workbench/api/common/extHostTerminalShellIntegration';
import { ExtHostTesting, IExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
registerSingleton(IExtHostLocalizationService, ExtHostLocalizationService, InstantiationType.Delayed);
registerSingleton(ILoggerService, ExtHostLoggerService, InstantiationType.Delayed);
@ -40,6 +41,7 @@ registerSingleton(IExtHostAuthentication, ExtHostAuthentication, InstantiationTy
registerSingleton(IExtHostLanguageModels, ExtHostLanguageModels, InstantiationType.Eager);
registerSingleton(IExtHostConfiguration, ExtHostConfiguration, InstantiationType.Eager);
registerSingleton(IExtHostConsumerFileSystem, ExtHostConsumerFileSystem, InstantiationType.Eager);
registerSingleton(IExtHostTesting, ExtHostTesting, InstantiationType.Eager);
registerSingleton(IExtHostDebugService, WorkerExtHostDebugService, InstantiationType.Eager);
registerSingleton(IExtHostDecorations, ExtHostDecorations, InstantiationType.Eager);
registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors, InstantiationType.Eager);

View file

@ -55,7 +55,7 @@ import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/c
import { IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatTask, IChatTaskDto, IChatUserActionEvent, ChatAgentVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables';
import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata, ILanguageModelChatSelector, ILanguageModelsChangeEvent } from 'vs/workbench/contrib/chat/common/languageModels';
import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from 'vs/workbench/contrib/debug/common/debug';
import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugTestRunReference, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from 'vs/workbench/contrib/debug/common/debug';
import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
@ -1566,6 +1566,7 @@ export interface IStartDebuggingOptions {
suppressDebugStatusbar?: boolean;
suppressDebugView?: boolean;
suppressSaveBeforeStart?: boolean;
testRun?: IDebugTestRunReference;
}
export interface MainThreadDebugServiceShape extends IDisposable {

View file

@ -30,6 +30,7 @@ import { ThemeIcon as ThemeIconUtils } from 'vs/base/common/themables';
import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
import { coalesce } from 'vs/base/common/arrays';
import { IExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
export const IExtHostDebugService = createDecorator<IExtHostDebugService>('IExtHostDebugService');
@ -123,6 +124,7 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I
@IExtHostEditorTabs protected _editorTabs: IExtHostEditorTabs,
@IExtHostVariableResolverProvider private _variableResolver: IExtHostVariableResolverProvider,
@IExtHostCommands private _commands: IExtHostCommands,
@IExtHostTesting private _testing: IExtHostTesting,
) {
super();
@ -466,6 +468,8 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I
}
public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise<boolean> {
const testRunMeta = options.testRun && this._testing.getMetadataForRun(options.testRun);
return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, {
parentSessionID: options.parentSession ? options.parentSession.id : undefined,
lifecycleManagedByParent: options.lifecycleManagedByParent,
@ -473,6 +477,10 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I
noDebug: options.noDebug,
compact: options.compact,
suppressSaveBeforeStart: options.suppressSaveBeforeStart,
testRun: testRunMeta && {
runId: testRunMeta.runId,
taskId: testRunMeta.taskId,
},
// Check debugUI for back-compat, #147264
suppressDebugStatusbar: options.suppressDebugStatusbar ?? (options as any).debugUI?.simple,
@ -1247,8 +1255,9 @@ export class WorkerExtHostDebugService extends ExtHostDebugServiceBase {
@IExtHostConfiguration configurationService: IExtHostConfiguration,
@IExtHostEditorTabs editorTabs: IExtHostEditorTabs,
@IExtHostVariableResolverProvider variableResolver: IExtHostVariableResolverProvider,
@IExtHostCommands commands: IExtHostCommands
@IExtHostCommands commands: IExtHostCommands,
@IExtHostTesting testing: IExtHostTesting,
) {
super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands);
super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands, testing);
}
}

View file

@ -16,10 +16,11 @@ import { MarshalledId } from 'vs/base/common/marshallingIds';
import { isDefined } from 'vs/base/common/types';
import { generateUuid } from 'vs/base/common/uuid';
import { IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostTestingShape, ILocationDto, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ExtHostTestItemCollection, TestItemImpl, TestItemRootImpl, toItemFromContext } from 'vs/workbench/api/common/extHostTestItem';
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
@ -45,7 +46,14 @@ let followupCounter = 0;
const testResultInternalIDs = new WeakMap<vscode.TestRunResult, string>();
export const IExtHostTesting = createDecorator<IExtHostTesting>('IExtHostTesting');
export interface IExtHostTesting extends ExtHostTesting {
readonly _serviceBrand: undefined;
}
export class ExtHostTesting extends Disposable implements ExtHostTestingShape {
declare readonly _serviceBrand: undefined;
private readonly resultsChangedEmitter = this._register(new Emitter<void>());
protected readonly controllers = new Map</* controller ID */ string, ControllerInfo>();
private readonly proxy: MainThreadTestingShape;
@ -61,8 +69,8 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape {
constructor(
@IExtHostRpcService rpc: IExtHostRpcService,
@ILogService private readonly logService: ILogService,
private readonly commands: ExtHostCommands,
private readonly editors: ExtHostDocumentsAndEditors,
@IExtHostCommands private readonly commands: IExtHostCommands,
@IExtHostDocumentsAndEditors private readonly editors: IExtHostDocumentsAndEditors,
) {
super();
this.proxy = rpc.getProxy(MainContext.MainThreadTesting);
@ -111,6 +119,8 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape {
});
}
//#region public API
/**
* Implements vscode.test.registerTestProvider
*/
@ -236,6 +246,9 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape {
return { dispose: () => { this.followupProviders.delete(provider); } };
}
//#endregion
//#region RPC methods
/**
* @inheritdoc
*/
@ -412,6 +425,32 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape {
return this.commands.executeCommand(command.command, ...(command.arguments || []));
}
/**
* Cancels an ongoing test run.
*/
public $cancelExtensionTestRun(runId: string | undefined) {
if (runId === undefined) {
this.runTracker.cancelAllRuns();
} else {
this.runTracker.cancelRunById(runId);
}
}
//#endregion
public getMetadataForRun(run: vscode.TestRun) {
for (const tracker of this.runTracker.trackers) {
const taskId = tracker.getTaskIdForRun(run);
if (!taskId) {
return undefined;
}
return { taskId, runId: tracker.id };
}
return undefined;
}
private async runControllerTestRequest(req: ICallProfileRunHandler | ICallProfileRunHandler, isContinuous: boolean, token: CancellationToken): Promise<IStartControllerTestsResult> {
const lookup = this.controllers.get(req.controllerId);
if (!lookup) {
@ -467,17 +506,6 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape {
}
}
}
/**
* Cancels an ongoing test run.
*/
public $cancelExtensionTestRun(runId: string | undefined) {
if (runId === undefined) {
this.runTracker.cancelAllRuns();
} else {
this.runTracker.cancelRunById(runId);
}
}
}
// Deadline after being requested by a user that a test run is forcibly cancelled.
@ -543,6 +571,17 @@ class TestRunTracker extends Disposable {
}));
}
/** Gets the task ID from a test run object. */
public getTaskIdForRun(run: vscode.TestRun) {
for (const [taskId, { run: r }] of this.tasks) {
if (r === run) {
return taskId;
}
}
return undefined;
}
/** Requests cancellation of the run. On the second call, forces cancellation. */
public cancel() {
if (this.state === TestRunTrackerState.Running) {

View file

@ -27,6 +27,7 @@ import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/c
import type * as vscode from 'vscode';
import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration';
import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { IExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
export class ExtHostDebugService extends ExtHostDebugServiceBase {
@ -44,8 +45,9 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
@IExtHostEditorTabs editorTabs: IExtHostEditorTabs,
@IExtHostVariableResolverProvider variableResolver: IExtHostVariableResolverProvider,
@IExtHostCommands commands: IExtHostCommands,
@IExtHostTesting testing: IExtHostTesting,
) {
super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands);
super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands, testing);
}
protected override createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined {

View file

@ -42,7 +42,7 @@ export function agentToMarkdown(agent: IChatAgentData, isClickable: boolean, acc
const isAllowed = chatAgentNameService.getAgentNameRestriction(agent);
let name = `${isAllowed ? agent.name : getFullyQualifiedId(agent)}`;
const isDupe = isAllowed && chatAgentService.getAgentsByName(agent.name).length > 1;
const isDupe = isAllowed && chatAgentService.agentHasDupeName(agent.id);
if (isDupe) {
name += ` (${agent.publisherDisplayName})`;
}

View file

@ -117,7 +117,7 @@ class AgentCompletions extends Disposable {
return {
suggestions: agents.map((agent, i): CompletionItem => {
const { label: agentLabel, isDupe } = getAgentCompletionDetails(agent, agents, this.chatAgentNameService);
const { label: agentLabel, isDupe } = this.getAgentCompletionDetails(agent);
return {
// Leading space is important because detail has no space at the start by design
label: isDupe ?
@ -216,7 +216,7 @@ class AgentCompletions extends Disposable {
const justAgents: CompletionItem[] = agents
.filter(a => !a.isDefault)
.map(agent => {
const { label: agentLabel, isDupe } = getAgentCompletionDetails(agent, agents, this.chatAgentNameService);
const { label: agentLabel, isDupe } = this.getAgentCompletionDetails(agent);
const detail = agent.description;
return {
@ -236,7 +236,7 @@ class AgentCompletions extends Disposable {
return {
suggestions: justAgents.concat(
agents.flatMap(agent => agent.slashCommands.map((c, i) => {
const { label: agentLabel, isDupe } = getAgentCompletionDetails(agent, agents, this.chatAgentNameService);
const { label: agentLabel, isDupe } = this.getAgentCompletionDetails(agent);
const withSlash = `${chatSubcommandLeader}${c.name}`;
return {
label: { label: withSlash, description: agentLabel, detail: isDupe ? ` (${agent.publisherDisplayName})` : undefined },
@ -254,6 +254,13 @@ class AgentCompletions extends Disposable {
}
}));
}
private getAgentCompletionDetails(agent: IChatAgentData): { label: string; isDupe: boolean } {
const isAllowed = this.chatAgentNameService.getAgentNameRestriction(agent);
const agentLabel = `${chatAgentLeader}${isAllowed ? agent.name : getFullyQualifiedId(agent)}`;
const isDupe = isAllowed && this.chatAgentService.agentHasDupeName(agent.id);
return { label: agentLabel, isDupe };
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AgentCompletions, LifecyclePhase.Eventually);
@ -401,11 +408,3 @@ class VariableCompletions extends Disposable {
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(VariableCompletions, LifecyclePhase.Eventually);
function getAgentCompletionDetails(agent: IChatAgentData, otherAgents: IChatAgentData[], chatAgentNameService: IChatAgentNameService): { label: string; isDupe: boolean } {
const isAllowed = chatAgentNameService.getAgentNameRestriction(agent);
const agentLabel = `${chatAgentLeader}${isAllowed ? agent.name : getFullyQualifiedId(agent)}`;
const isDupe = isAllowed && !!otherAgents.find(other => other.name === agent.name && other.id !== agent.id);
return { label: agentLabel, isDupe };
}

View file

@ -174,6 +174,7 @@ export interface IChatAgentService {
getAgents(): IChatAgentData[];
getActivatedAgents(): Array<IChatAgent>;
getAgentsByName(name: string): IChatAgentData[];
agentHasDupeName(id: string): boolean;
/**
* Get the default agent (only if activated)
@ -345,6 +346,16 @@ export class ChatAgentService implements IChatAgentService {
return this.getAgents().filter(a => a.name === name);
}
agentHasDupeName(id: string): boolean {
const agent = this.getAgent(id);
if (!agent) {
return false;
}
return this.getAgentsByName(agent.name)
.filter(a => a.extensionId.value !== agent.extensionId.value).length > 0;
}
async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> {
const data = this._agents.get(id);
if (!data?.impl) {

View file

@ -70,6 +70,7 @@ suite('VoiceChat', () => {
getAgentByFullyQualifiedId(id: string): IChatAgentData | undefined { throw new Error('Method not implemented.'); }
registerAgentCompletionProvider(id: string, provider: (query: string, token: CancellationToken) => Promise<IChatAgentCompletionItem[]>): IDisposable { throw new Error('Method not implemented.'); }
getAgentCompletionItems(id: string, query: string, token: CancellationToken): Promise<IChatAgentCompletionItem[]> { throw new Error('Method not implemented.'); }
agentHasDupeName(id: string): boolean { throw new Error('Method not implemented.'); }
}
class TestSpeechService implements ISpeechService {

View file

@ -51,6 +51,7 @@ import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@ -112,6 +113,7 @@ export class DebugService implements IDebugService {
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@ITestService private readonly testService: ITestService,
) {
this.breakpointsToSendOnResourceSaved = new Set<URI>();
@ -839,6 +841,21 @@ export class DebugService implements IDebugService {
}
};
// For debug sessions spawned by test runs, cancel the test run and stop
// the session, then start the test run again; tests have no notion of restarts.
if (session.correlatedTestRun) {
if (!session.correlatedTestRun.completedAt) {
this.testService.cancelTestRun(session.correlatedTestRun.id);
await Event.toPromise(session.correlatedTestRun.onComplete);
// todo@connor4312 is there any reason to wait for the debug session to
// terminate? I don't think so, test extension should already handle any
// state conflicts...
}
this.testService.runResolvedTests(session.correlatedTestRun.request);
return;
}
if (session.capabilities.supportsRestartRequest) {
const taskResult = await runTasks();
if (taskResult === TaskRunResult.Success) {

View file

@ -42,6 +42,9 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b
import { getActiveWindow } from 'vs/base/browser/dom';
import { mainWindow } from 'vs/base/browser/window';
import { isDefined } from 'vs/base/common/types';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
const TRIGGERED_BREAKPOINT_MAX_DELAY = 1500;
@ -66,6 +69,11 @@ export class DebugSession implements IDebugSession, IDisposable {
private stoppedDetails: IRawStoppedDetails[] = [];
private readonly statusQueue = this.rawListeners.add(new ThreadStatusScheduler());
/** Test run this debug session was spawned by */
public readonly correlatedTestRun?: LiveTestResult;
/** Whether we terminated the correlated run yet. Used so a 2nd terminate request goes through to the underlying session. */
private didTerminateTestRun?: boolean;
private readonly _onDidChangeState = new Emitter<void>();
private readonly _onDidEndAdapter = new Emitter<AdapterEndEvent | undefined>();
@ -106,7 +114,9 @@ export class DebugSession implements IDebugSession, IDisposable {
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICustomEndpointTelemetryService private readonly customEndpointTelemetryService: ICustomEndpointTelemetryService,
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
@ILogService private readonly logService: ILogService
@ILogService private readonly logService: ILogService,
@ITestService private readonly testService: ITestService,
@ITestResultService testResultService: ITestResultService,
) {
this._options = options || {};
this.parentSession = this._options.parentSession;
@ -126,6 +136,16 @@ export class DebugSession implements IDebugSession, IDisposable {
}));
}
// Cast here, it's not possible to reference a hydrated result in this code path.
this.correlatedTestRun = options?.testRun
? (testResultService.getResult(options.testRun.runId) as LiveTestResult)
: this.parentSession?.correlatedTestRun;
if (this.correlatedTestRun) {
// Listen to the test completing because the user might have taken the cancel action rather than stopping the session.
toDispose.add(this.correlatedTestRun.onComplete(() => this.terminate()));
}
const compoundRoot = this._options.compoundRoot;
if (compoundRoot) {
toDispose.add(compoundRoot.onDidSessionStop(() => this.terminate()));
@ -387,6 +407,9 @@ export class DebugSession implements IDebugSession, IDisposable {
this.cancelAllRequests();
if (this._options.lifecycleManagedByParent && this.parentSession) {
await this.parentSession.terminate(restart);
} else if (this.correlatedTestRun && !this.correlatedTestRun.completedAt && !this.didTerminateTestRun) {
this.didTerminateTestRun = true;
this.testService.cancelTestRun(this.correlatedTestRun.id);
} else if (this.raw) {
if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') {
await this.raw.terminate(restart);

View file

@ -27,6 +27,7 @@ import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompou
import { IDataBreakpointOptions, IFunctionBreakpointOptions, IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { ITaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks';
import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export const VIEWLET_ID = 'workbench.view.debug';
@ -219,6 +220,11 @@ export interface LoadedSourceEvent {
export type IDebugSessionReplMode = 'separate' | 'mergeWithParent';
export interface IDebugTestRunReference {
runId: string;
taskId: string;
}
export interface IDebugSessionOptions {
noDebug?: boolean;
parentSession?: IDebugSession;
@ -231,6 +237,11 @@ export interface IDebugSessionOptions {
suppressDebugToolbar?: boolean;
suppressDebugStatusbar?: boolean;
suppressDebugView?: boolean;
/**
* Set if the debug session is correlated with a test run. Stopping/restarting
* the session will instead stop/restart the test run.
*/
testRun?: IDebugTestRunReference;
}
export interface IDataBreakpointInfoResponse {
@ -353,6 +364,8 @@ export interface IDebugSession extends ITreeElement {
readonly suppressDebugStatusbar: boolean;
readonly suppressDebugView: boolean;
readonly lifecycleManagedByParent: boolean;
/** Test run this debug session was spawned by */
readonly correlatedTestRun?: LiveTestResult;
setSubId(subId: string | undefined): void;

View file

@ -41,7 +41,7 @@ export function createTestSession(model: DebugModel, name = 'mockSession', optio
}
};
}
} as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService());
} as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService(), undefined!, undefined!);
}
function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame; secondStackFrame: StackFrame } {
@ -445,7 +445,7 @@ suite('Debug - CallStack', () => {
override get state(): State {
return State.Stopped;
}
}(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService());
}(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService(), undefined!, undefined!);
disposables.add(session);
const runningSession = createTestSession(model);

View file

@ -22,9 +22,103 @@ import { ITitleService } from 'vs/workbench/services/title/browser/titleService'
import { IEditorGroupContextKeyProvider, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { getRepositoryResourceCount } from 'vs/workbench/contrib/scm/browser/util';
import { autorunWithStore, derived, derivedObservableWithCache, IObservable, observableFromEvent } from 'vs/base/common/observable';
import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils';
function getCount(repository: ISCMRepository): number {
return repository.provider.count ?? getRepositoryResourceCount(repository.provider);
export class SCMActivityCountBadgeController extends Disposable implements IWorkbenchContribution {
private readonly _countBadgeConfig = observableConfigValue<'all' | 'focused' | 'off'>('scm.countBadge', 'all', this.configurationService);
private readonly _repositories = observableFromEvent(
Event.any(this.scmService.onDidAddRepository, this.scmService.onDidRemoveRepository),
() => this.scmService.repositories);
private readonly _focusedRepository = observableFromEvent(
this.scmViewService.onDidFocusRepository,
() => this.scmViewService.focusedRepository ? Object.create(this.scmViewService.focusedRepository) : undefined);
private readonly _activeEditor = observableFromEvent(
this.editorService.onDidActiveEditorChange,
() => this.editorService.activeEditor);
private readonly _activeEditorRepository = derived(reader => {
const activeResource = EditorResourceAccessor.getOriginalUri(this._activeEditor.read(reader));
if (!activeResource) {
return undefined;
}
return this.scmService.getRepository(activeResource);
});
private readonly _activeRepository = derivedObservableWithCache<ISCMRepository | undefined>(this, (reader, lastValue) => {
const focusedRepository = this._focusedRepository.read(reader);
if (focusedRepository && focusedRepository.id !== lastValue?.id) {
return focusedRepository;
}
const activeEditorRepository = this._activeEditorRepository.read(reader);
if (activeEditorRepository && activeEditorRepository.id !== lastValue?.id) {
return activeEditorRepository;
}
return lastValue;
});
private readonly _countBadgeRepositories = derived(reader => {
switch (this._countBadgeConfig.read(reader)) {
case 'all': {
const repositories = this._repositories.read(reader);
return [...Iterable.map(repositories, r => ({ ...r.provider, resourceCount: this._getRepositoryResourceCount(r) }))];
}
case 'focused': {
const repository = this._activeRepository.read(reader);
return repository ? [{ ...repository.provider, resourceCount: this._getRepositoryResourceCount(repository) }] : [];
}
case 'off':
return [];
default:
throw new Error('Invalid countBadge setting');
}
});
private readonly _countBadge = derived(reader => {
let total = 0;
for (const repository of this._countBadgeRepositories.read(reader)) {
const count = repository.count?.read(reader);
const resourceCount = repository.resourceCount.read(reader);
total = total + (count ?? resourceCount);
}
return total;
});
constructor(
@IActivityService private readonly activityService: IActivityService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorService private readonly editorService: IEditorService,
@ISCMService private readonly scmService: ISCMService,
@ISCMViewService private readonly scmViewService: ISCMViewService
) {
super();
this._register(autorunWithStore((reader, store) => {
this._renderActivityCount(this._countBadge.read(reader), store);
}));
}
private _getRepositoryResourceCount(repository: ISCMRepository): IObservable<number> {
return observableFromEvent(repository.provider.onDidChangeResources, () => getRepositoryResourceCount(repository.provider));
}
private _renderActivityCount(count: number, store: DisposableStore): void {
if (count === 0) {
return;
}
const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num));
store.add(this.activityService.showViewActivity(VIEW_PANE_ID, { badge }));
}
}
export class SCMStatusController implements IWorkbenchContribution {
@ -40,16 +134,11 @@ export class SCMStatusController implements IWorkbenchContribution {
@ISCMService private readonly scmService: ISCMService,
@ISCMViewService private readonly scmViewService: ISCMViewService,
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IActivityService private readonly activityService: IActivityService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IEditorService private readonly editorService: IEditorService
) {
this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables);
this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables);
const onDidChangeSCMCountBadge = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.countBadge'));
onDidChangeSCMCountBadge(this.renderActivityCount, this, this.disposables);
for (const repository of this.scmService.repositories) {
this.onDidAddRepository(repository);
}
@ -58,7 +147,6 @@ export class SCMStatusController implements IWorkbenchContribution {
this.focusRepository(this.scmViewService.focusedRepository);
editorService.onDidActiveEditorChange(() => this.tryFocusRepositoryBasedOnActiveEditor(), this, this.disposables);
this.renderActivityCount();
}
private tryFocusRepositoryBasedOnActiveEditor(repositories: Iterable<ISCMRepository> = this.scmService.repositories): boolean {
@ -78,17 +166,13 @@ export class SCMStatusController implements IWorkbenchContribution {
}
private onDidAddRepository(repository: ISCMRepository): void {
const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources);
const changeDisposable = onDidChange(() => this.renderActivityCount());
const onDidRemove = Event.filter(this.scmService.onDidRemoveRepository, e => e === repository);
const removeDisposable = onDidRemove(() => {
disposable.dispose();
this.repositoryDisposables.delete(disposable);
this.renderActivityCount();
});
const disposable = combinedDisposable(changeDisposable, removeDisposable);
const disposable = combinedDisposable(removeDisposable);
this.repositoryDisposables.add(disposable);
this.tryFocusRepositoryBasedOnActiveEditor(Iterable.single(repository));
@ -115,7 +199,6 @@ export class SCMStatusController implements IWorkbenchContribution {
}
this.renderStatusBar(repository);
this.renderActivityCount();
}
private renderStatusBar(repository: ISCMRepository | undefined): void {
@ -166,25 +249,6 @@ export class SCMStatusController implements IWorkbenchContribution {
this.statusBarDisposable = disposables;
}
private renderActivityCount(): void {
const countBadgeType = this.configurationService.getValue<'all' | 'focused' | 'off'>('scm.countBadge');
let count = 0;
if (countBadgeType === 'all') {
count = Iterable.reduce(this.scmService.repositories, (r, repository) => r + getCount(repository), 0);
} else if (countBadgeType === 'focused' && this.focusedRepository) {
count = getCount(this.focusedRepository);
}
if (count > 0) {
const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num));
this.badgeDisposable.value = this.activityService.showViewActivity(VIEW_PANE_ID, { badge });
} else {
this.badgeDisposable.value = undefined;
}
}
dispose(): void {
this.focusDisposable.dispose();
this.statusBarDisposable.dispose();

View file

@ -10,7 +10,7 @@ import { DirtyDiffWorkbenchController } from './dirtydiffDecorator';
import { VIEWLET_ID, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { SCMActiveRepositoryContextKeyController, SCMActiveResourceContextKeyController, SCMStatusController } from './activity';
import { SCMActiveRepositoryContextKeyController, SCMActiveResourceContextKeyController, SCMActivityCountBadgeController, SCMStatusController } from './activity';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
@ -112,6 +112,9 @@ viewsRegistry.registerViews([{
containerIcon: sourceControlViewIcon
}], viewContainer);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(SCMActivityCountBadgeController, LifecyclePhase.Restored);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(SCMActiveResourceContextKeyController, LifecyclePhase.Restored);
@ -386,6 +389,22 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'scm.clearInput',
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.has('scmRepository'),
primary: KeyCode.Escape,
handler: async (accessor) => {
const scmService = accessor.get(ISCMService);
const contextKeyService = accessor.get(IContextKeyService);
const context = contextKeyService.getContext(getActiveElement());
const repositoryId = context.getValue<string | undefined>('scmRepository');
const repository = repositoryId ? scmService.getRepository(repositoryId) : undefined;
repository?.input.setValue('', true);
}
});
const viewNextCommitCommand = {
description: { description: localize('scm view next commit', "Source Control: View Next Commit"), args: [] },
weight: KeybindingWeight.WorkbenchContrib,

View file

@ -117,7 +117,7 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer<ISCMReposit
}));
templateData.elementDisposables.add(autorun(reader => {
const count = repository.provider.countObs.read(reader) ?? getRepositoryResourceCount(repository.provider);
const count = repository.provider.count.read(reader) ?? getRepositoryResourceCount(repository.provider);
templateData.countContainer.setAttribute('data-count', String(count));
templateData.count.setCount(count);
}));

View file

@ -73,8 +73,7 @@ export interface ISCMProvider extends IDisposable {
readonly rootUri?: URI;
readonly inputBoxTextModel: ITextModel;
readonly count?: number;
readonly countObs: IObservable<number | undefined>;
readonly count: IObservable<number | undefined>;
readonly commitTemplate: IObservable<string>;
readonly historyProvider?: ISCMHistoryProvider;
readonly onDidChangeHistoryProvider: Event<void>;

View file

@ -23,7 +23,7 @@ import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/se
import { ITaskEvent, TaskEventKind, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks';
import { ITaskService, TaskCommandsRegistered, TaskExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions';
import { RunAutomaticTasks, ManageAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
@ -431,15 +431,24 @@ schema.oneOf = [...(schemaVersion2.oneOf || []), ...(schemaVersion1.oneOf || [])
const jsonRegistry = <jsonContributionRegistry.IJSONContributionRegistry>Registry.as(jsonContributionRegistry.Extensions.JSONContribution);
jsonRegistry.registerSchema(tasksSchemaId, schema);
ProblemMatcherRegistry.onMatcherChanged(() => {
updateProblemMatchers();
jsonRegistry.notifySchemaChanged(tasksSchemaId);
});
export class TaskRegistryContribution extends Disposable implements IWorkbenchContribution {
static ID = 'taskRegistryContribution';
constructor() {
super();
this._register(ProblemMatcherRegistry.onMatcherChanged(() => {
updateProblemMatchers();
jsonRegistry.notifySchemaChanged(tasksSchemaId);
}));
this._register(TaskDefinitionRegistry.onDefinitionsChanged(() => {
updateTaskDefinitions();
jsonRegistry.notifySchemaChanged(tasksSchemaId);
}));
}
}
registerWorkbenchContribution2(TaskRegistryContribution.ID, TaskRegistryContribution, WorkbenchPhase.AfterRestored);
TaskDefinitionRegistry.onDefinitionsChanged(() => {
updateTaskDefinitions();
jsonRegistry.notifySchemaChanged(tasksSchemaId);
});
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({

View file

@ -116,14 +116,44 @@ __vsc_escape_value() {
for (( i=0; i < "${#str}"; ++i )); do
byte="${str:$i:1}"
# Escape backslashes, semi-colons specially, then special ASCII chars below space (0x20)
# Escape backslashes, semi-colons specially, then special ASCII chars below space (0x20).
# This is done in an unwrapped loop instead of using printf as the latter is very slow.
if [ "$byte" = "\\" ]; then
token="\\\\"
elif [ "$byte" = ";" ]; then
token="\\x3b"
elif (( $(builtin printf '%d' "'$byte") < 31 )); then
token=$(builtin printf '\\x%02x' "'$byte")
elif [ "$byte" = $'\x00' ]; then token="\\x00"
elif [ "$byte" = $'\x01' ]; then token="\\x01"
elif [ "$byte" = $'\x02' ]; then token="\\x02"
elif [ "$byte" = $'\x03' ]; then token="\\x03"
elif [ "$byte" = $'\x04' ]; then token="\\x04"
elif [ "$byte" = $'\x05' ]; then token="\\x05"
elif [ "$byte" = $'\x06' ]; then token="\\x06"
elif [ "$byte" = $'\x07' ]; then token="\\x07"
elif [ "$byte" = $'\x08' ]; then token="\\x08"
elif [ "$byte" = $'\x09' ]; then token="\\x09"
elif [ "$byte" = $'\x0a' ]; then token="\\x0a"
elif [ "$byte" = $'\x0b' ]; then token="\\x0b"
elif [ "$byte" = $'\x0c' ]; then token="\\x0c"
elif [ "$byte" = $'\x0d' ]; then token="\\x0d"
elif [ "$byte" = $'\x0e' ]; then token="\\x0e"
elif [ "$byte" = $'\x0f' ]; then token="\\x0f"
elif [ "$byte" = $'\x10' ]; then token="\\x10"
elif [ "$byte" = $'\x11' ]; then token="\\x11"
elif [ "$byte" = $'\x12' ]; then token="\\x12"
elif [ "$byte" = $'\x13' ]; then token="\\x13"
elif [ "$byte" = $'\x14' ]; then token="\\x14"
elif [ "$byte" = $'\x15' ]; then token="\\x15"
elif [ "$byte" = $'\x16' ]; then token="\\x16"
elif [ "$byte" = $'\x17' ]; then token="\\x17"
elif [ "$byte" = $'\x18' ]; then token="\\x18"
elif [ "$byte" = $'\x19' ]; then token="\\x19"
elif [ "$byte" = $'\x1a' ]; then token="\\x1a"
elif [ "$byte" = $'\x1b' ]; then token="\\x1b"
elif [ "$byte" = $'\x1c' ]; then token="\\x1c"
elif [ "$byte" = $'\x1d' ]; then token="\\x1d"
elif [ "$byte" = $'\x1e' ]; then token="\\x1e"
elif [ "$byte" = $'\x1f' ]; then token="\\x1f"
else
token="$byte"
fi
@ -166,6 +196,11 @@ unset VSCODE_NONCE
builtin printf "\e]633;P;ContinuationPrompt=$(echo "$PS2" | sed 's/\x1b/\\\\x1b/g')\a"
__vsc_report_prompt() {
# HACK: Git bash is too slow at reporting the prompt, so skip for now
if [ "$__vsc_is_windows" = "1" ]; then
return
fi
# Expand the original PS1 similarly to how bash would normally
# See https://stackoverflow.com/a/37137981 for technique
if ((BASH_VERSINFO[0] >= 5 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4))); then

View file

@ -54,7 +54,6 @@
.profiles-editor .monaco-list-row.focused .profile-tree-item-actions-container,
.profiles-editor .monaco-list-row.selected .profile-tree-item-actions-container,
.profiles-editor .monaco-list-row:hover .profile-tree-item-actions-container {
flex: 1;
display: flex;
align-items: center;
}
@ -86,6 +85,7 @@
}
.profiles-editor .sidebar-container .profiles-list .profile-list-item .profile-tree-item-actions-container {
flex: 1;
justify-content: flex-end;
margin-right: 10px;
}
@ -197,11 +197,11 @@
.profiles-editor .contents-container .profile-content-tree-header,
.profiles-editor .contents-container .profile-content-tree {
margin-left: 6px;
max-width: 450px;
}
.profiles-editor .contents-container .profile-content-tree-header {
display: flex;
display: grid;
grid-template-columns: 30px repeat(2, 1fr) 100px;
height: 24px;
align-items: center;
margin-bottom: 2px;
@ -214,28 +214,33 @@
align-items: center;
}
.profiles-editor .contents-container .profile-content-tree-header :first-child {
width: 200px;
}
.profiles-editor .contents-container .profile-tree-item-container > .profile-resource-type-label,
.profiles-editor .contents-container .profile-tree-item-container.new-profile-resource-type-container > .profile-resource-type-label-container {
width: 170px;
.profiles-editor .contents-container .profile-tree-item-container.existing-profile-resource-type-container {
display: grid;
grid-template-columns: repeat(2, 1fr) 100px;
}
.profiles-editor .contents-container .profile-content-tree-header > .inherit-label,
.profiles-editor .contents-container .profile-tree-item-container > .inherit-container {
width: 130px;
justify-content: center;
align-items: center;
}
.profiles-editor .contents-container .profile-tree-item-container > .inherit-container {
padding-left: 50px;
}
.profiles-editor .contents-container .profile-content-tree-header > .actions-label {
display: flex;
justify-content: center;
align-items: center;
}
.profiles-editor .contents-container .profile-content-tree-header > .actions-label {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
.profiles-editor .contents-container .profile-tree-item-container.profile-resource-child-container > .profile-tree-item-actions-container {
width: 100px;
}
.profiles-editor .contents-container .profile-tree-item-container.new-profile-resource-type-container > .profile-resource-type-label-container {
width: 170px;
}
.profiles-editor .contents-container .profile-tree-item-container.new-profile-resource-type-container > .profile-select-container {
@ -249,7 +254,7 @@
}
.profiles-editor .contents-container .profile-tree-item-container.profile-resource-child-container > .monaco-icon-label {
width: 295px;
flex: 1
}
.profiles-editor .contents-container .profile-tree-item-container .profile-tree-item-actions-container {

View file

@ -31,10 +31,17 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IUserDataProfilesEditor } from 'vs/workbench/contrib/userDataProfile/common/userDataProfile';
import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { IProductService } from 'vs/platform/product/common/productService';
type IProfileTemplateQuickPickItem = IQuickPickItem & IProfileTemplateInfo;
export const OpenProfileMenu = new MenuId('OpenProfile');
const CONFIG_ENABLE_NEW_PROFILES_UI = 'workbench.experimental.enableNewProfilesUI';
const CONTEXT_ENABLE_NEW_PROFILES_UI = ContextKeyExpr.equals('config.workbench.experimental.enableNewProfilesUI', true);
export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution {
@ -54,6 +61,10 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IProductService private readonly productService: IProductService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
) {
super();
@ -73,6 +84,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
this.hasProfilesContext.set(this.userDataProfilesService.profiles.length > 1);
this._register(this.userDataProfilesService.onDidChangeProfiles(e => this.hasProfilesContext.set(this.userDataProfilesService.profiles.length > 1)));
this.registerConfiguration();
this.registerEditor();
this.registerActions();
@ -83,6 +95,29 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
this.reportWorkspaceProfileInfo();
}
private openProfilesEditor(): Promise<IUserDataProfilesEditor | undefined> {
return this.editorGroupsService.activeGroup.openEditor(new UserDataProfilesEditorInput(this.instantiationService));
}
private isNewProfilesUIEnabled(): boolean {
return this.configurationService.getValue(CONFIG_ENABLE_NEW_PROFILES_UI) === true;
}
private registerConfiguration(): void {
Registry.as<IConfigurationRegistry>(Extensions.Configuration)
.registerConfiguration({
...workbenchConfigurationNodeBase,
properties: {
[CONFIG_ENABLE_NEW_PROFILES_UI]: {
type: 'boolean',
description: localize('enable new profiles UI', "Enables the new profiles UI."),
default: this.productService.quality !== 'stable',
scope: ConfigurationScope.APPLICATION,
}
}
});
}
private registerEditor(): void {
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
EditorPaneDescriptor.create(
@ -120,6 +155,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
const getProfilesTitle = () => {
return localize('profiles', "Profile ({0})", this.userDataProfileService.currentProfile.name);
};
const when = ContextKeyExpr.or(CONTEXT_ENABLE_NEW_PROFILES_UI.negate(), HAS_PROFILES_CONTEXT);
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
get title() {
return getProfilesTitle();
@ -127,7 +163,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
submenu: ProfilesMenu,
group: '2_configuration',
order: 1,
when: HAS_PROFILES_CONTEXT,
when,
});
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
get title() {
@ -136,7 +172,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
submenu: ProfilesMenu,
group: '2_configuration',
order: 1,
when: HAS_PROFILES_CONTEXT,
when,
});
}
@ -269,6 +305,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
private readonly currentprofileActionsDisposable = this._register(new MutableDisposable<DisposableStore>());
private registerCurrentProfilesActions(): void {
this.currentprofileActionsDisposable.value = new DisposableStore();
this.currentprofileActionsDisposable.value.add(this.registerEditCurrentProfileAction());
this.currentprofileActionsDisposable.value.add(this.registerShowCurrentProfileContentsAction());
this.currentprofileActionsDisposable.value.add(this.registerExportCurrentProfileAction());
this.currentprofileActionsDisposable.value.add(this.registerImportProfileAction());
}
@ -287,12 +325,14 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
{
id: MenuId.GlobalActivity,
group: '2_configuration',
order: 1
order: 1,
when: CONTEXT_ENABLE_NEW_PROFILES_UI,
},
{
id: MenuId.MenubarPreferencesMenu,
group: '2_configuration',
order: 1
order: 1,
when: CONTEXT_ENABLE_NEW_PROFILES_UI,
},
]
});
@ -308,12 +348,69 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
id: 'workbench.profiles.actions.manageProfiles',
category: Categories.Preferences,
title: localize2('open profiles', "Open Profiles (UI)"),
precondition: CONTEXT_ENABLE_NEW_PROFILES_UI,
},
}));
return disposables;
}
private registerEditCurrentProfileAction(): IDisposable {
const that = this;
return registerAction2(class RenameCurrentProfileAction extends Action2 {
constructor() {
const precondition = ContextKeyExpr.and(ContextKeyExpr.notEquals(CURRENT_PROFILE_CONTEXT.key, that.userDataProfilesService.defaultProfile.id), IS_CURRENT_PROFILE_TRANSIENT_CONTEXT.toNegated());
super({
id: `workbench.profiles.actions.editCurrentProfile`,
title: localize2('edit profile', "Edit Profile..."),
precondition,
f1: true,
menu: [
{
id: ProfilesMenu,
group: '2_manage_current',
when: ContextKeyExpr.and(precondition, CONTEXT_ENABLE_NEW_PROFILES_UI.negate()),
order: 2
}
]
});
}
run(accessor: ServicesAccessor) {
if (that.isNewProfilesUIEnabled()) {
return that.openProfilesEditor();
} else {
return that.userDataProfileImportExportService.editProfile(that.userDataProfileService.currentProfile);
}
}
});
}
private registerShowCurrentProfileContentsAction(): IDisposable {
const id = 'workbench.profiles.actions.showProfileContents';
return registerAction2(class ShowProfileContentsAction extends Action2 {
constructor() {
super({
id,
title: localize2('show profile contents', "Show Profile Contents"),
category: PROFILES_CATEGORY,
menu: [
{
id: ProfilesMenu,
group: '2_manage_current',
order: 3,
when: CONTEXT_ENABLE_NEW_PROFILES_UI.negate()
}
]
});
}
async run(accessor: ServicesAccessor) {
const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService);
return userDataProfileImportExportService.showProfileContents();
}
});
}
private registerExportCurrentProfileAction(): IDisposable {
const that = this;
const disposables = new DisposableStore();
@ -322,22 +419,37 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
constructor() {
super({
id,
title: localize2('export profile', "Export Profile ({0})...", that.userDataProfileService.currentProfile.name),
title: localize2('export profile', "Export Profile..."),
category: PROFILES_CATEGORY,
precondition: IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT.toNegated(),
f1: true,
menu: {
id: MenuId.MenubarShare
}
menu: [
{
id: ProfilesMenu,
group: '4_import_export_profiles',
order: 1,
when: CONTEXT_ENABLE_NEW_PROFILES_UI.negate(),
}, {
id: MenuId.CommandPalette
}
]
});
}
async run(accessor: ServicesAccessor) {
const editorGroupsService = accessor.get(IEditorGroupsService);
const instantiationService = accessor.get(IInstantiationService);
return editorGroupsService.activeGroup.openEditor(new UserDataProfilesEditorInput(instantiationService));
if (that.isNewProfilesUIEnabled()) {
return that.openProfilesEditor();
} else {
return that.userDataProfileImportExportService.exportProfile2();
}
}
}));
disposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarShare, {
command: {
id,
title: localize2('export profile in share', "Export Profile ({0})...", that.userDataProfileService.currentProfile.name),
precondition: PROFILES_ENABLEMENT_CONTEXT,
},
}));
return disposables;
}
@ -352,7 +464,17 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
title: localize2('import profile', "Import Profile..."),
category: PROFILES_CATEGORY,
precondition: IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT.toNegated(),
f1: true,
menu: [
{
id: ProfilesMenu,
group: '4_import_export_profiles',
when: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, CONTEXT_ENABLE_NEW_PROFILES_UI.negate()),
order: 2
}, {
id: MenuId.CommandPalette,
when: PROFILES_ENABLEMENT_CONTEXT,
}
]
});
}
@ -454,6 +576,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
}
private registerCreateProfileAction(): void {
const that = this;
this._register(registerAction2(class CreateProfileAction extends Action2 {
constructor() {
super({
@ -462,13 +585,23 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
category: PROFILES_CATEGORY,
precondition: PROFILES_ENABLEMENT_CONTEXT,
f1: true,
menu: [
{
id: ProfilesMenu,
group: '3_manage_profiles',
when: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, CONTEXT_ENABLE_NEW_PROFILES_UI.negate()),
order: 1
}
]
});
}
async run(accessor: ServicesAccessor) {
const editorGroupsService = accessor.get(IEditorGroupsService);
const instantiationService = accessor.get(IInstantiationService);
return editorGroupsService.activeGroup.openEditor(new UserDataProfilesEditorInput(instantiationService));
if (that.isNewProfilesUIEnabled()) {
return that.openProfilesEditor();
} else {
return that.userDataProfileImportExportService.createProfile();
}
}
}));
}
@ -482,6 +615,14 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
category: PROFILES_CATEGORY,
f1: true,
precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, HAS_PROFILES_CONTEXT),
menu: [
{
id: ProfilesMenu,
group: '3_manage_profiles',
when: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, CONTEXT_ENABLE_NEW_PROFILES_UI.negate()),
order: 2
}
]
});
}

View file

@ -460,6 +460,7 @@ class ProfileWidget extends Disposable {
private copyFromOptions: (ISelectOptionItem & { id?: string; source?: IUserDataProfile | URI })[] = [];
private readonly contentsTreeHeader: HTMLElement;
private readonly inheritLabelElement: HTMLElement;
private readonly resourcesTree: WorkbenchAsyncDataTree<AbstractUserDataProfileElement, ProfileResourceTreeElement>;
private _templates: IProfileTemplateInfo[] = [];
@ -572,9 +573,11 @@ class ProfileWidget extends Disposable {
}));
this.contentsTreeHeader = append(body, $('.profile-content-tree-header'));
this.inheritLabelElement = $('.inherit-label', undefined, localize('default profile', "Use Default Profile"));
append(this.contentsTreeHeader,
$(''),
$('.inherit-label', undefined, localize('default profile', "Use Default Profile")),
$(''),
this.inheritLabelElement,
$('.actions-label', undefined, localize('actions', "Actions")),
);
const delegate = new ProfileResourceTreeElementDelegate();
@ -728,6 +731,23 @@ class ProfileWidget extends Disposable {
if (primaryTitleButtons?.length || secondatyTitleButtons?.length) {
this.buttonContainer.classList.remove('hide');
if (secondatyTitleButtons?.length) {
for (const action of secondatyTitleButtons) {
const button = disposables.add(new Button(this.buttonContainer, {
...defaultButtonStyles,
secondary: true
}));
button.label = action.label;
button.enabled = action.enabled;
disposables.add(button.onDidClick(() => this.editorProgressService.showWhile(action.run())));
disposables.add(action.onDidChange((e) => {
if (!isUndefined(e.enabled)) {
button.enabled = action.enabled;
}
}));
}
}
if (primaryTitleButtons?.length) {
for (const action of primaryTitleButtons) {
const button = disposables.add(new Button(this.buttonContainer, {
@ -750,23 +770,6 @@ class ProfileWidget extends Disposable {
}
}
if (secondatyTitleButtons?.length) {
for (const action of secondatyTitleButtons) {
const button = disposables.add(new Button(this.buttonContainer, {
...defaultButtonStyles,
secondary: true
}));
button.label = action.label;
button.enabled = action.enabled;
disposables.add(button.onDidClick(() => this.editorProgressService.showWhile(action.run())));
disposables.add(action.onDidChange((e) => {
if (!isUndefined(e.enabled)) {
button.enabled = action.enabled;
}
}));
}
}
} else {
this.buttonContainer.classList.add('hide');
}
@ -811,7 +814,8 @@ class ProfileWidget extends Disposable {
this.copyFromSelectBox.setEnabled(false);
}
} else if (profileElement instanceof UserDataProfileElement) {
this.contentsTreeHeader.classList.toggle('hide', profileElement.profile.isDefault);
this.contentsTreeHeader.classList.remove('hide');
this.inheritLabelElement.textContent = profileElement.profile.isDefault ? '' : localize('default profile', "Use Default Profile");
this.useAsDefaultProfileContainer.classList.remove('hide');
this.useAsDefaultProfileCheckbox.checked = profileElement.isNewWindowProfile;
this.copyFromContainer.classList.add('hide');

View file

@ -825,7 +825,7 @@ export class UserDataProfilesEditorModel extends EditorModel {
this.newProfileElement = disposables.add(this.instantiationService.createInstance(NewProfileElement,
copyFrom ? '' : localize('untitled', "Untitled"),
copyFrom,
[[createAction], [previewProfileAction, cancelAction]],
[[createAction], [cancelAction, previewProfileAction]],
[[], []],
[[cancelAction], []],
));

View file

@ -118,6 +118,7 @@ export const allApiProposals = Object.freeze({
terminalSelection: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalSelection.d.ts',
terminalShellIntegration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalShellIntegration.d.ts',
testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts',
testRunInDebug: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testRunInDebug.d.ts',
textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts',
timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts',
tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts',

View file

@ -80,6 +80,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import type { IHoverWidget } from 'vs/base/browser/ui/hover/hover';
import { IAccessibleViewInformationService } from 'vs/workbench/services/accessibility/common/accessibleViewInformationService';
import { ILogService } from 'vs/platform/log/common/log';
interface IUserDataProfileTemplate {
readonly name: string;
@ -103,6 +104,7 @@ function isUserDataProfileTemplate(thing: unknown): thing is IUserDataProfileTem
&& (isUndefined(candidate.extensions) || typeof candidate.extensions === 'string'));
}
const EXPORT_PROFILE_PREVIEW_VIEW = 'workbench.views.profiles.export.preview';
const IMPORT_PROFILE_PREVIEW_VIEW = 'workbench.views.profiles.import.preview';
export class UserDataProfileImportExportService extends Disposable implements IUserDataProfileImportExportService, IURLHandler {
@ -141,6 +143,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IContextViewService private readonly contextViewService: IContextViewService,
@IHoverService private readonly hoverService: IHoverService,
@ILogService private readonly logService: ILogService,
) {
super();
this.registerProfileContentHandler(Schemas.file, this.fileUserDataProfileContentHandler = instantiationService.createInstance(FileUserDataProfileContentHandler));
@ -190,6 +193,15 @@ export class UserDataProfileImportExportService extends Disposable implements IU
this.profileContentHandlers.delete(id);
}
async exportProfile2(): Promise<void> {
if (this.isProfileExportInProgressContextKey.get()) {
this.logService.warn('Profile export already in progress.');
return;
}
return this.showProfileContents();
}
async importProfile(uri: URI, options?: IProfileImportOptions): Promise<void> {
if (this.isProfileImportInProgressContextKey.get()) {
this.notificationService.warn('Profile import already in progress.');
@ -226,6 +238,10 @@ export class UserDataProfileImportExportService extends Disposable implements IU
return this.saveProfile(undefined, from);
}
editProfile(profile: IUserDataProfile): Promise<void> {
return this.saveProfile(profile);
}
async createFromProfile(from: IUserDataProfile, options: IUserDataProfileCreateOptions, token: CancellationToken): Promise<IUserDataProfile | undefined> {
const disposables = new DisposableStore();
let creationPromise: CancelablePromise<void>;
@ -643,6 +659,36 @@ export class UserDataProfileImportExportService extends Disposable implements IU
}
}
async showProfileContents(): Promise<void> {
const view = this.viewsService.getViewWithId(EXPORT_PROFILE_PREVIEW_VIEW);
if (view) {
this.viewsService.openView(view.id, true);
return;
}
const disposables = new DisposableStore();
try {
const userDataProfilesExportState = disposables.add(this.instantiationService.createInstance(UserDataProfileExportState, this.userDataProfileService.currentProfile, undefined));
const barrier = new Barrier();
const exportAction = new BarrierAction(barrier, new Action('export', localize('export', "Export"), undefined, true, async () => {
exportAction.enabled = false;
try {
await this.doExportProfile(userDataProfilesExportState, EXPORT_PROFILE_PREVIEW_VIEW);
} catch (error) {
exportAction.enabled = true;
this.notificationService.error(error);
throw error;
}
}), this.notificationService);
const closeAction = new BarrierAction(barrier, new Action('close', localize('close', "Close")), this.notificationService);
await this.showProfilePreviewView(EXPORT_PROFILE_PREVIEW_VIEW, userDataProfilesExportState.profile.name, exportAction, closeAction, true, userDataProfilesExportState);
disposables.add(this.userDataProfileService.onDidChangeCurrentProfile(e => barrier.open()));
await barrier.wait();
await this.hideProfilePreviewView(EXPORT_PROFILE_PREVIEW_VIEW);
} finally {
disposables.dispose();
}
}
async exportProfile(profile: IUserDataProfile): Promise<void> {
const disposables = new DisposableStore();
try {

View file

@ -101,8 +101,11 @@ export interface IUserDataProfileImportExportService {
resolveProfileTemplate(uri: URI): Promise<IUserDataProfileTemplate | null>;
exportProfile(profile: IUserDataProfile): Promise<void>;
exportProfile2(): Promise<void>;
importProfile(uri: URI, options?: IProfileImportOptions): Promise<void>;
showProfileContents(): Promise<void>;
createProfile(from?: IUserDataProfile | URI): Promise<void>;
editProfile(profile: IUserDataProfile): Promise<void>;
createFromProfile(from: IUserDataProfile, options: IUserDataProfileCreateOptions, token: CancellationToken): Promise<IUserDataProfile | undefined>;
createProfileFromTemplate(profileTemplate: IUserDataProfileTemplate, options: IUserDataProfileCreateOptions, token: CancellationToken): Promise<IUserDataProfile | undefined>;
createTroubleshootProfile(): Promise<void>;

View file

@ -286,6 +286,23 @@ declare module 'vscode' {
/**
* The exit code reported by the shell. `undefined` means the shell did not report an exit
* code or the shell reported a command started before the command finished.
*
* @example
* const execution = shellIntegration.executeCommand({
* command: 'echo',
* args: ['Hello world']
* });
* window.onDidEndTerminalShellExecution(event => {
* if (event.execution === execution) {
* if (event.exitCode === undefined) {
* console.log('Command finished but exit code is unknown');
* } else if (event.exitCode === 0) {
* console.log('Command succeeded');
* } else {
* console.log('Command failed');
* }
* }
* });
*/
readonly exitCode: number | undefined;
}

View file

@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode' {
// https://github.com/microsoft/vscode/issues/214486
export interface DebugSessionOptions {
/**
* Signals to the editor that the debug session was started from a test run
* request. This is used to link the lifecycle of the debug session and
* test run in UI actions.
*/
testRun?: TestRun;
}
}