mirror of
https://github.com/Microsoft/vscode
synced 2024-10-05 19:02:54 +00:00
Merge branch 'microsoft:main' into display-type
This commit is contained in:
commit
8219ce7cec
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -261,4 +261,4 @@ fi
|
|||
|
||||
wait_for_async_execs
|
||||
|
||||
exec "$@"
|
||||
exec "$@" "--no-sandbox" "--use-gl=angle" "--use-angle=swiftshader"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
193
src/vs/editor/contrib/hover/browser/contentHoverRendered.ts
Normal file
193
src/vs/editor/contrib/hover/browser/contentHoverRendered.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
) { }
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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})`;
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}));
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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], []],
|
||||
));
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
18
src/vscode-dts/vscode.proposed.testRunInDebug.d.ts
vendored
Normal file
18
src/vscode-dts/vscode.proposed.testRunInDebug.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue