Archived
Merge from vscode 1a81711a85e38ccf784110568ebf3784ab9094a5 (#9161)
* Merge from vscode 1a81711a85e38ccf784110568ebf3784ab9094a5 * small spacing fix
This commit is contained in:
@@ -140,6 +140,9 @@ function configureCommandlineSwitchesSync(cliArgs) {
|
||||
// provided by Electron
|
||||
'disable-color-correct-rendering'
|
||||
];
|
||||
if (process.platform === 'linux') {
|
||||
SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility');
|
||||
}
|
||||
|
||||
// Read argv config
|
||||
const argvConfig = readArgvConfigSync();
|
||||
|
||||
@@ -5,6 +5,53 @@
|
||||
|
||||
import { pad } from './strings';
|
||||
|
||||
const minute = 60;
|
||||
const hour = minute * 60;
|
||||
const day = hour * 24;
|
||||
const week = day * 7;
|
||||
const month = day * 30;
|
||||
const year = day * 365;
|
||||
|
||||
// TODO[ECA]: Localize strings
|
||||
export function fromNow(date: number | Date) {
|
||||
if (typeof date !== 'number') {
|
||||
date = date.getTime();
|
||||
}
|
||||
|
||||
const seconds = Math.round((new Date().getTime() - date) / 1000);
|
||||
if (seconds < 30) {
|
||||
return 'now';
|
||||
}
|
||||
|
||||
let value: number;
|
||||
let unit: string;
|
||||
if (seconds < minute) {
|
||||
value = seconds;
|
||||
unit = 'sec';
|
||||
} else if (seconds < hour) {
|
||||
value = Math.floor(seconds / minute);
|
||||
unit = 'min';
|
||||
} else if (seconds < day) {
|
||||
value = Math.floor(seconds / hour);
|
||||
unit = 'hr';
|
||||
} else if (seconds < week) {
|
||||
value = Math.floor(seconds / day);
|
||||
unit = 'day';
|
||||
} else if (seconds < month) {
|
||||
value = Math.floor(seconds / week);
|
||||
unit = 'wk';
|
||||
} else if (seconds < year) {
|
||||
value = Math.floor(seconds / month);
|
||||
unit = 'mo';
|
||||
} else {
|
||||
value = Math.floor(seconds / year);
|
||||
unit = 'yr';
|
||||
}
|
||||
|
||||
return `${value} ${unit}${value === 1 ? '' : 's'}`;
|
||||
|
||||
}
|
||||
|
||||
export function toLocalISOString(date: Date): string {
|
||||
return date.getFullYear() +
|
||||
'-' + pad(date.getMonth() + 1, 2) +
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchConstructionOptions, create, URI, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLinkProvider, IApplicationLink } from 'vs/workbench/workbench.web.api';
|
||||
import { IWorkbenchConstructionOptions, create, URI, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLink } from 'vs/workbench/workbench.web.api';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { streamToBuffer } from 'vs/base/common/buffer';
|
||||
@@ -279,39 +279,6 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
||||
}
|
||||
}
|
||||
|
||||
class ApplicationLinkProvider {
|
||||
|
||||
private links: IApplicationLink[] | undefined = undefined;
|
||||
|
||||
constructor(workspace: IWorkspace) {
|
||||
this.computeLink(workspace);
|
||||
}
|
||||
|
||||
private computeLink(workspace: IWorkspace): void {
|
||||
if (!workspace) {
|
||||
return; // not for empty workspaces
|
||||
}
|
||||
|
||||
const workspaceUri = isWorkspaceToOpen(workspace) ? workspace.workspaceUri : isFolderToOpen(workspace) ? workspace.folderUri : undefined;
|
||||
if (workspaceUri) {
|
||||
this.links = [{
|
||||
uri: URI.from({
|
||||
scheme: product.quality === 'stable' ? 'vscode' : 'vscode-insiders',
|
||||
authority: Schemas.vscodeRemote,
|
||||
path: posix.join(posix.sep, workspaceUri.authority, workspaceUri.path),
|
||||
query: workspaceUri.query,
|
||||
fragment: workspaceUri.fragment,
|
||||
}),
|
||||
label: localize('openInDesktop', "Open in Desktop")
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
get provider(): IApplicationLinkProvider {
|
||||
return () => this.links;
|
||||
}
|
||||
}
|
||||
|
||||
(function () {
|
||||
|
||||
// Find config by checking for DOM
|
||||
@@ -375,12 +342,30 @@ class ApplicationLinkProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// Application links ("Open in Desktop")
|
||||
let applicationLinks: IApplicationLink[] | undefined = undefined;
|
||||
if (workspace) {
|
||||
const workspaceUri = isWorkspaceToOpen(workspace) ? workspace.workspaceUri : isFolderToOpen(workspace) ? workspace.folderUri : undefined;
|
||||
if (workspaceUri) {
|
||||
applicationLinks = [{
|
||||
uri: URI.from({
|
||||
scheme: product.quality === 'stable' ? 'vscode' : 'vscode-insiders',
|
||||
authority: Schemas.vscodeRemote,
|
||||
path: posix.join(posix.sep, workspaceUri.authority, workspaceUri.path),
|
||||
query: workspaceUri.query,
|
||||
fragment: workspaceUri.fragment,
|
||||
}),
|
||||
label: localize('openInDesktop', "Open in Desktop")
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
// Finally create workbench
|
||||
create(document.body, {
|
||||
...config,
|
||||
workspaceProvider: new WorkspaceProvider(workspace, payload),
|
||||
urlCallbackProvider: new PollingURLCallbackProvider(),
|
||||
credentialsProvider: new LocalStorageCredentialsProvider(),
|
||||
applicationLinkProvider: new ApplicationLinkProvider(workspace).provider
|
||||
applicationLinks: applicationLinks
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -265,6 +265,34 @@ export interface HoverProvider {
|
||||
provideHover(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<Hover>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are
|
||||
* evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget.
|
||||
*/
|
||||
export interface EvaluatableExpression {
|
||||
/**
|
||||
* The range to which this expression applies.
|
||||
*/
|
||||
range: IRange;
|
||||
/*
|
||||
* This expression overrides the expression extracted from the range.
|
||||
*/
|
||||
expression?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hover provider interface defines the contract between extensions and
|
||||
* the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature.
|
||||
*/
|
||||
export interface EvaluatableExpressionProvider {
|
||||
/**
|
||||
* Provide a hover for the given position and document. Multiple hovers at the same
|
||||
* position will be merged by the editor. A hover can have a range which defaults
|
||||
* to the word range at the position when omitted.
|
||||
*/
|
||||
provideEvaluatableExpression(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
|
||||
}
|
||||
|
||||
export const enum CompletionItemKind {
|
||||
Method,
|
||||
Function,
|
||||
@@ -1595,6 +1623,11 @@ export const SignatureHelpProviderRegistry = new LanguageFeatureRegistry<Signatu
|
||||
*/
|
||||
export const HoverProviderRegistry = new LanguageFeatureRegistry<HoverProvider>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const EvaluatableExpressionProviderRegistry = new LanguageFeatureRegistry<EvaluatableExpressionProvider>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
Vendored
+25
@@ -5242,6 +5242,31 @@ declare namespace monaco.languages {
|
||||
provideHover(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<Hover>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are
|
||||
* evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget.
|
||||
*/
|
||||
export interface EvaluatableExpression {
|
||||
/**
|
||||
* The range to which this expression applies.
|
||||
*/
|
||||
range: IRange;
|
||||
expression?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hover provider interface defines the contract between extensions and
|
||||
* the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature.
|
||||
*/
|
||||
export interface EvaluatableExpressionProvider {
|
||||
/**
|
||||
* Provide a hover for the given position and document. Multiple hovers at the same
|
||||
* position will be merged by the editor. A hover can have a range which defaults
|
||||
* to the word range at the position when omitted.
|
||||
*/
|
||||
provideEvaluatableExpression(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
|
||||
}
|
||||
|
||||
export enum CompletionItemKind {
|
||||
Method = 0,
|
||||
Function = 1,
|
||||
|
||||
@@ -119,7 +119,9 @@ export class MenuId {
|
||||
static readonly DataExplorerContext = new MenuId('DataExplorerContext'); // {{SQL CARBON EDIT}}
|
||||
static readonly DataExplorerAction = new MenuId('DataExplorerAction'); // {{SQL CARBON EDIT}}
|
||||
static readonly ExplorerWidgetContext = new MenuId('ExplorerWidgetContext'); // {{SQL CARBON EDIT}}
|
||||
|
||||
static readonly TimelineItemContext = new MenuId('TimelineItemContext');
|
||||
static readonly TimelineTitle = new MenuId('TimelineTitle');
|
||||
static readonly TimelineTitleContext = new MenuId('TimelineTitleContext');
|
||||
|
||||
readonly id: number;
|
||||
readonly _debugName: string;
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface IConstructorSignature0<T> {
|
||||
}
|
||||
|
||||
export interface IConstructorSignature1<A1, T> {
|
||||
new(first: A1, ...services: BrandedService[]): T;
|
||||
new <Services extends BrandedService[]>(first: A1, ...services: Services): T;
|
||||
}
|
||||
|
||||
export interface IConstructorSignature2<A1, A2, T> {
|
||||
|
||||
@@ -98,7 +98,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
async triggerAutoSync(): Promise<void> {
|
||||
if (this.enabled) {
|
||||
return this.syncDelayer.trigger(() => {
|
||||
this.logService.info('Auto Sync: Triggerred.');
|
||||
this.logService.info('Auto Sync: Triggered.');
|
||||
return this.sync(false, true);
|
||||
}, this.successiveFailures
|
||||
? 1000 * 1 * Math.min(this.successiveFailures, 60) /* Delay by number of seconds as number of failures up to 1 minute */
|
||||
|
||||
Vendored
+96
-3
@@ -876,7 +876,65 @@ declare module 'vscode' {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Debug:
|
||||
//#region locate evaluatable expressions for debug hover: https://github.com/microsoft/vscode/issues/89084
|
||||
|
||||
/**
|
||||
* An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime.
|
||||
* The result of this evaluation is shown in a tooltip-like widget.
|
||||
* If only a range is specified, the expression will be extracted from the underlying document.
|
||||
* An optional expression can be used to override the extracted expression.
|
||||
* In this case the range is still used to highlight the range in the document.
|
||||
*/
|
||||
export class EvaluatableExpression {
|
||||
/*
|
||||
* The range is used to extract the evaluatable expression from the underlying document and to highlight it.
|
||||
*/
|
||||
readonly range: Range;
|
||||
/*
|
||||
* If specified the expression overrides the extracted expression.
|
||||
*/
|
||||
readonly expression?: string;
|
||||
|
||||
/**
|
||||
* Creates a new evaluatable expression object.
|
||||
*
|
||||
* @param range The range in the underlying document from which the evaluatable expression is extracted.
|
||||
* @param expression If specified overrides the extracted expression.
|
||||
*/
|
||||
constructor(range: Range, expression?: string);
|
||||
}
|
||||
|
||||
/**
|
||||
* The evaluatable expression provider interface defines the contract between extensions and
|
||||
* the debug hover.
|
||||
*/
|
||||
export interface EvaluatableExpressionProvider {
|
||||
|
||||
/**
|
||||
* Provide an evaluatable expression for the given document and position.
|
||||
* The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression.
|
||||
*
|
||||
* @param document The document in which the command was invoked.
|
||||
* @param position The position where the command was invoked.
|
||||
* @param token A cancellation token.
|
||||
* @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be
|
||||
* signaled by returning `undefined` or `null`.
|
||||
*/
|
||||
provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
|
||||
}
|
||||
|
||||
export namespace languages {
|
||||
/**
|
||||
* Register a provider that locates evaluatable expressions in text documents.
|
||||
*
|
||||
* If multiple providers are registered for a language an arbitrary provider will be used.
|
||||
*
|
||||
* @param selector A selector that defines the documents this provider is applicable to.
|
||||
* @param provider An evaluatable expression provider.
|
||||
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
|
||||
*/
|
||||
export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable;
|
||||
}
|
||||
|
||||
// deprecated
|
||||
|
||||
@@ -1553,6 +1611,40 @@ declare module 'vscode' {
|
||||
uri?: Uri;
|
||||
}
|
||||
|
||||
export interface TimelineCursor {
|
||||
/**
|
||||
* A provider-defined cursor specifing the range of timeline items to be returned. Must be serializable.
|
||||
*/
|
||||
cursor?: any;
|
||||
|
||||
/**
|
||||
* A flag to specify whether the timeline items requested are before or after (default) the provided cursor.
|
||||
*/
|
||||
before?: boolean;
|
||||
|
||||
/**
|
||||
* The maximum number of timeline items that should be returned.
|
||||
*/
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface Timeline {
|
||||
/**
|
||||
* A provider-defined cursor specifing the range of timeline items returned. Must be serializable.
|
||||
*/
|
||||
cursor?: any;
|
||||
|
||||
/**
|
||||
* A flag which indicates whether there are any more items that weren't returned.
|
||||
*/
|
||||
more?: boolean;
|
||||
|
||||
/**
|
||||
* An array of [timeline items](#TimelineItem).
|
||||
*/
|
||||
items: TimelineItem[];
|
||||
}
|
||||
|
||||
export interface TimelineProvider {
|
||||
/**
|
||||
* An optional event to signal that the timeline for a source has changed.
|
||||
@@ -1575,10 +1667,11 @@ declare module 'vscode' {
|
||||
*
|
||||
* @param uri The [uri](#Uri) of the file to provide the timeline for.
|
||||
* @param token A cancellation token.
|
||||
* @return An array of timeline items or a thenable that resolves to such. The lack of a result
|
||||
* @param cursor TBD
|
||||
* @return The [timeline result](#TimelineResult) or a thenable that resolves to such. The lack of a result
|
||||
* can be signaled by returning `undefined`, `null`, or an empty array.
|
||||
*/
|
||||
provideTimeline(uri: Uri, token: CancellationToken): ProviderResult<TimelineItem[]>;
|
||||
provideTimeline(uri: Uri, cursor: TimelineCursor, token: CancellationToken): ProviderResult<Timeline>;
|
||||
}
|
||||
|
||||
export namespace workspace {
|
||||
|
||||
@@ -213,6 +213,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
}));
|
||||
}
|
||||
|
||||
// --- debug hover
|
||||
|
||||
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void {
|
||||
this._registrations.set(handle, modes.EvaluatableExpressionProviderRegistry.register(selector, <modes.EvaluatableExpressionProvider>{
|
||||
provideEvaluatableExpression: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined> => {
|
||||
return this._proxy.$provideEvaluatableExpression(handle, model.uri, position, token);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// --- occurrences
|
||||
|
||||
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { MainContext, MainThreadTimelineShape, IExtHostContext, ExtHostTimelineShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ITimelineService, TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTimeline)
|
||||
export class MainThreadTimeline implements MainThreadTimelineShape {
|
||||
@@ -24,10 +24,6 @@ export class MainThreadTimeline implements MainThreadTimelineShape {
|
||||
this._proxy = context.getProxy(ExtHostContext.ExtHostTimeline);
|
||||
}
|
||||
|
||||
$getTimeline(uri: URI, token: CancellationToken): Promise<TimelineItem[]> {
|
||||
return this._timelineService.getTimeline(uri, token);
|
||||
}
|
||||
|
||||
$registerTimelineProvider(provider: TimelineProviderDescriptor): void {
|
||||
this.logService.trace(`MainThreadTimeline#registerTimelineProvider: id=${provider.id}`);
|
||||
|
||||
@@ -43,8 +39,8 @@ export class MainThreadTimeline implements MainThreadTimelineShape {
|
||||
this._timelineService.registerTimelineProvider({
|
||||
...provider,
|
||||
onDidChange: onDidChange.event,
|
||||
provideTimeline(uri: URI, token: CancellationToken) {
|
||||
return proxy.$getTimeline(provider.id, uri, token);
|
||||
provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) {
|
||||
return proxy.$getTimeline(provider.id, uri, cursor, token, options);
|
||||
},
|
||||
dispose() {
|
||||
emitters.delete(provider.id);
|
||||
|
||||
@@ -134,7 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol));
|
||||
const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol));
|
||||
const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol));
|
||||
const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol));
|
||||
const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands));
|
||||
|
||||
// Check that no named customers are missing
|
||||
// {{SQL CARBON EDIT}} filter out the services we don't expose
|
||||
@@ -348,6 +348,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerHoverProvider(extension, checkSelector(selector), provider, extension.identifier);
|
||||
},
|
||||
registerEvaluatableExpressionProvider(selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerEvaluatableExpressionProvider(extension, checkSelector(selector), provider, extension.identifier);
|
||||
},
|
||||
registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider);
|
||||
},
|
||||
@@ -928,6 +931,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
DocumentLink: extHostTypes.DocumentLink,
|
||||
DocumentSymbol: extHostTypes.DocumentSymbol,
|
||||
EndOfLine: extHostTypes.EndOfLine,
|
||||
EvaluatableExpression: extHostTypes.EvaluatableExpression,
|
||||
EventEmitter: Emitter,
|
||||
ExtensionKind: extHostTypes.ExtensionKind,
|
||||
CustomExecution: extHostTypes.CustomExecution,
|
||||
|
||||
@@ -49,7 +49,7 @@ import { SaveReason } from 'vs/workbench/common/editor';
|
||||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
|
||||
@@ -357,6 +357,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
||||
$registerImplementationSupport(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto): void;
|
||||
@@ -806,8 +807,6 @@ export interface MainThreadTimelineShape extends IDisposable {
|
||||
$registerTimelineProvider(provider: TimelineProviderDescriptor): void;
|
||||
$unregisterTimelineProvider(source: string): void;
|
||||
$emitTimelineChangeEvent(e: TimelineChangeEvent): void;
|
||||
|
||||
$getTimeline(uri: UriComponents, token: CancellationToken): Promise<TimelineItem[]>;
|
||||
}
|
||||
|
||||
// -- extension host
|
||||
@@ -1213,6 +1212,7 @@ export interface ExtHostLanguageFeaturesShape {
|
||||
$provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IDefinitionLinkDto[]>;
|
||||
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IDefinitionLinkDto[]>;
|
||||
$provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>;
|
||||
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined>;
|
||||
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>;
|
||||
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<ILocationDto[] | undefined>;
|
||||
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>;
|
||||
@@ -1461,7 +1461,7 @@ export interface ExtHostTunnelServiceShape {
|
||||
}
|
||||
|
||||
export interface ExtHostTimelineShape {
|
||||
$getTimeline(source: string, uri: UriComponents, token: CancellationToken): Promise<TimelineItemWithSource[]>;
|
||||
$getTimeline(source: string, uri: UriComponents, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
|
||||
@@ -276,6 +276,27 @@ class HoverAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
class EvaluatableExpressionAdapter {
|
||||
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.EvaluatableExpressionProvider,
|
||||
) { }
|
||||
|
||||
public provideEvaluatableExpression(resource: URI, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined> {
|
||||
|
||||
const doc = this._documents.getDocument(resource);
|
||||
const pos = typeConvert.Position.to(position);
|
||||
|
||||
return asPromise(() => this._provider.provideEvaluatableExpression(doc, pos, token)).then(value => {
|
||||
if (value) {
|
||||
return typeConvert.EvaluatableExpression.from(value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentHighlightAdapter {
|
||||
|
||||
constructor(
|
||||
@@ -1329,7 +1350,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov
|
||||
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
|
||||
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
|
||||
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
|
||||
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter;
|
||||
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter;
|
||||
|
||||
class AdapterData {
|
||||
constructor(
|
||||
@@ -1549,6 +1570,18 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
return this._withAdapter(handle, HoverAdapter, adapter => adapter.provideHover(URI.revive(resource), position, token), undefined);
|
||||
}
|
||||
|
||||
// --- debug hover
|
||||
|
||||
registerEvaluatableExpressionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider, extensionId?: ExtensionIdentifier): vscode.Disposable {
|
||||
const handle = this._addNewAdapter(new EvaluatableExpressionAdapter(this._documents, provider), extension);
|
||||
this._proxy.$registerEvaluatableExpressionProvider(handle, this._transformDocumentSelector(selector));
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined> {
|
||||
return this._withAdapter(handle, EvaluatableExpressionAdapter, adapter => adapter.provideEvaluatableExpression(URI.revive(resource), position, token), undefined);
|
||||
}
|
||||
|
||||
// --- occurrences
|
||||
|
||||
registerDocumentHighlightProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
|
||||
|
||||
@@ -7,61 +7,91 @@ import * as vscode from 'vscode';
|
||||
import { UriComponents, URI } from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { TimelineItemWithSource, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { Timeline, TimelineCursor, TimelineItem, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { ThemeIcon } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export interface IExtHostTimeline extends ExtHostTimelineShape {
|
||||
readonly _serviceBrand: undefined;
|
||||
$getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise<TimelineItemWithSource[]>;
|
||||
$getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
}
|
||||
|
||||
export const IExtHostTimeline = createDecorator<IExtHostTimeline>('IExtHostTimeline');
|
||||
|
||||
export class ExtHostTimeline implements IExtHostTimeline {
|
||||
private static handlePool = 0;
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private _proxy: MainThreadTimelineShape;
|
||||
|
||||
private _providers = new Map<string, TimelineProvider>();
|
||||
|
||||
private _itemsBySourceByUriMap = new Map<string | undefined, Map<string, Map<string, vscode.TimelineItem>>>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
commands: ExtHostCommands,
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadTimeline);
|
||||
|
||||
commands.registerArgumentProcessor({
|
||||
processArgument: arg => {
|
||||
if (arg && arg.$mid === 11) {
|
||||
const uri = arg.uri === undefined ? undefined : URI.revive(arg.uri);
|
||||
return this._itemsBySourceByUriMap.get(getUriKey(uri))?.get(arg.source)?.get(arg.handle);
|
||||
}
|
||||
|
||||
return arg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async $getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise<TimelineItemWithSource[]> {
|
||||
async $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined> {
|
||||
const provider = this._providers.get(id);
|
||||
return provider?.provideTimeline(URI.revive(uri), token) ?? [];
|
||||
return provider?.provideTimeline(URI.revive(uri), cursor, token, options);
|
||||
}
|
||||
|
||||
registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable {
|
||||
registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, _extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable {
|
||||
const timelineDisposables = new DisposableStore();
|
||||
|
||||
const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables);
|
||||
const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables).bind(this);
|
||||
|
||||
let disposable: IDisposable | undefined;
|
||||
if (provider.onDidChange) {
|
||||
disposable = provider.onDidChange(this.emitTimelineChangeEvent(provider.id), this);
|
||||
}
|
||||
|
||||
const itemsBySourceByUriMap = this._itemsBySourceByUriMap;
|
||||
return this.registerTimelineProviderCore({
|
||||
...provider,
|
||||
scheme: scheme,
|
||||
onDidChange: undefined,
|
||||
async provideTimeline(uri: URI, token: CancellationToken) {
|
||||
async provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) {
|
||||
timelineDisposables.clear();
|
||||
|
||||
const results = await provider.provideTimeline(uri, token);
|
||||
// For now, only allow the caching of a single Uri
|
||||
if (options?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) {
|
||||
itemsBySourceByUriMap.clear();
|
||||
}
|
||||
|
||||
const result = await provider.provideTimeline(uri, cursor, token);
|
||||
// Intentional == we don't know how a provider will respond
|
||||
// eslint-disable-next-line eqeqeq
|
||||
return results != null
|
||||
? results.map(item => convertTimelineItem(item))
|
||||
: [];
|
||||
if (result == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// TODO: Determine if we should cache dependent on who calls us (internal vs external)
|
||||
const convertItem = convertTimelineItem(uri, options?.cacheResults ?? false);
|
||||
return {
|
||||
...result,
|
||||
source: provider.id,
|
||||
items: result.items.map(convertItem)
|
||||
};
|
||||
},
|
||||
dispose() {
|
||||
disposable?.dispose();
|
||||
@@ -70,39 +100,72 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
});
|
||||
}
|
||||
|
||||
private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore): (item: vscode.TimelineItem) => TimelineItemWithSource {
|
||||
return (item: vscode.TimelineItem) => {
|
||||
const { iconPath, ...props } = item;
|
||||
private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore) {
|
||||
return (uri: URI, cacheResults: boolean) => {
|
||||
let itemsMap: Map<string, vscode.TimelineItem> | undefined;
|
||||
if (cacheResults) {
|
||||
const uriKey = getUriKey(uri);
|
||||
|
||||
let icon;
|
||||
let iconDark;
|
||||
let themeIcon;
|
||||
if (item.iconPath) {
|
||||
if (iconPath instanceof ThemeIcon) {
|
||||
themeIcon = { id: iconPath.id };
|
||||
let sourceMap = this._itemsBySourceByUriMap.get(uriKey);
|
||||
if (sourceMap === undefined) {
|
||||
sourceMap = new Map();
|
||||
this._itemsBySourceByUriMap.set(uriKey, sourceMap);
|
||||
}
|
||||
else if (URI.isUri(iconPath)) {
|
||||
icon = iconPath;
|
||||
iconDark = iconPath;
|
||||
}
|
||||
else {
|
||||
({ light: icon, dark: iconDark } = iconPath as { light: URI; dark: URI });
|
||||
|
||||
itemsMap = sourceMap.get(source);
|
||||
if (itemsMap === undefined) {
|
||||
itemsMap = new Map();
|
||||
sourceMap.set(source, itemsMap);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...props,
|
||||
source: source,
|
||||
command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined,
|
||||
icon: icon,
|
||||
iconDark: iconDark,
|
||||
themeIcon: themeIcon
|
||||
return (item: vscode.TimelineItem): TimelineItem => {
|
||||
const { iconPath, ...props } = item;
|
||||
|
||||
const handle = `${source}|${item.id ?? `${item.timestamp}-${ExtHostTimeline.handlePool++}`}`;
|
||||
itemsMap?.set(handle, item);
|
||||
|
||||
let icon;
|
||||
let iconDark;
|
||||
let themeIcon;
|
||||
if (item.iconPath) {
|
||||
if (iconPath instanceof ThemeIcon) {
|
||||
themeIcon = { id: iconPath.id };
|
||||
}
|
||||
else if (URI.isUri(iconPath)) {
|
||||
icon = iconPath;
|
||||
iconDark = iconPath;
|
||||
}
|
||||
else {
|
||||
({ light: icon, dark: iconDark } = iconPath as { light: URI; dark: URI });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...props,
|
||||
handle: handle,
|
||||
source: source,
|
||||
command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined,
|
||||
icon: icon,
|
||||
iconDark: iconDark,
|
||||
themeIcon: themeIcon
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private emitTimelineChangeEvent(id: string) {
|
||||
return (e: vscode.TimelineChangeEvent) => {
|
||||
// Clear caches
|
||||
if (e?.uri === undefined) {
|
||||
for (const sourceMap of this._itemsBySourceByUriMap.values()) {
|
||||
sourceMap.get(id)?.clear();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._itemsBySourceByUriMap.get(getUriKey(e.uri))?.clear();
|
||||
}
|
||||
|
||||
this._proxy.$emitTimelineChangeEvent({ ...e, id: id });
|
||||
};
|
||||
}
|
||||
@@ -123,9 +186,18 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
this._providers.set(provider.id, provider);
|
||||
|
||||
return toDisposable(() => {
|
||||
for (const sourceMap of this._itemsBySourceByUriMap.values()) {
|
||||
sourceMap.get(provider.id)?.clear();
|
||||
}
|
||||
|
||||
this._providers.delete(provider.id);
|
||||
this._proxy.$unregisterTimelineProvider(provider.id);
|
||||
provider.dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getUriKey(uri: URI | undefined): string | undefined {
|
||||
return uri?.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ export namespace MarkdownString {
|
||||
} else if (htmlContent.isMarkdownString(markup)) {
|
||||
res = markup;
|
||||
} else if (typeof markup === 'string') {
|
||||
res = { value: <string>markup };
|
||||
res = { value: markup };
|
||||
} else {
|
||||
res = { value: '' };
|
||||
}
|
||||
@@ -737,6 +737,20 @@ export namespace Hover {
|
||||
return new types.Hover(info.contents.map(MarkdownString.to), Range.to(info.range));
|
||||
}
|
||||
}
|
||||
|
||||
export namespace EvaluatableExpression {
|
||||
export function from(expression: vscode.EvaluatableExpression): modes.EvaluatableExpression {
|
||||
return <modes.EvaluatableExpression>{
|
||||
range: Range.from(expression.range),
|
||||
expression: expression.expression
|
||||
};
|
||||
}
|
||||
|
||||
export function to(info: modes.EvaluatableExpression): types.EvaluatableExpression {
|
||||
return new types.EvaluatableExpression(Range.to(info.range), info.expression);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace DocumentHighlight {
|
||||
export function from(documentHighlight: vscode.DocumentHighlight): modes.DocumentHighlight {
|
||||
return {
|
||||
|
||||
@@ -2283,6 +2283,17 @@ export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInli
|
||||
}
|
||||
}
|
||||
|
||||
@es5ClassCompat
|
||||
export class EvaluatableExpression implements vscode.EvaluatableExpression {
|
||||
readonly range: vscode.Range;
|
||||
readonly expression?: string;
|
||||
|
||||
constructor(range: vscode.Range, expression?: string) {
|
||||
this.range = range;
|
||||
this.expression = expression;
|
||||
}
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
Trace = 1,
|
||||
Debug = 2,
|
||||
|
||||
@@ -57,6 +57,8 @@ namespace schema {
|
||||
case 'comments/comment/title': return MenuId.CommentTitle;
|
||||
case 'comments/comment/context': return MenuId.CommentActions;
|
||||
case 'extension/context': return MenuId.ExtensionContext;
|
||||
case 'timeline/title': return MenuId.TimelineTitle;
|
||||
case 'timeline/item/context': return MenuId.TimelineItemContext;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -220,6 +222,16 @@ namespace schema {
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'timeline/title': {
|
||||
description: localize('view.timelineTitle', "The Timeline view title menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
'timeline/item/context': {
|
||||
description: localize('view.timelineContext', "The Timeline view item context menu"),
|
||||
type: 'array',
|
||||
items: menuItem
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
//#region IView
|
||||
|
||||
readonly minimumWidth: number = 420;
|
||||
readonly minimumWidth: number = 300;
|
||||
readonly maximumWidth: number = Number.POSITIVE_INFINITY;
|
||||
readonly minimumHeight: number = 77;
|
||||
readonly maximumHeight: number = Number.POSITIVE_INFINITY;
|
||||
|
||||
@@ -57,12 +57,12 @@ export interface IViewContainerDescriptor {
|
||||
|
||||
export interface IViewContainersRegistry {
|
||||
/**
|
||||
* An event that is triggerred when a view container is registered.
|
||||
* An event that is triggered when a view container is registered.
|
||||
*/
|
||||
readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>;
|
||||
|
||||
/**
|
||||
* An event that is triggerred when a view container is deregistered.
|
||||
* An event that is triggered when a view container is deregistered.
|
||||
*/
|
||||
readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./bulkEdit';
|
||||
import { WorkbenchAsyncDataTree, TreeResourceNavigator, IOpenEvent } from 'vs/platform/list/browser/listService';
|
||||
import { WorkspaceEdit } from 'vs/editor/common/modes';
|
||||
import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree';
|
||||
import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree';
|
||||
import { FuzzyScore } from 'vs/base/common/filters';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -135,6 +135,7 @@ export class BulkEditPane extends ViewPane {
|
||||
expandOnlyOnTwistieClick: true,
|
||||
multipleSelectionSupport: false,
|
||||
keyboardNavigationLabelProvider: new BulkEditNaviLabelProvider(),
|
||||
sorter: new BulkEditSorter()
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -99,6 +99,15 @@ export class BulkFileOperation {
|
||||
this.newUri = edit.newUri;
|
||||
}
|
||||
}
|
||||
|
||||
needsConfirmation(): boolean {
|
||||
for (let [, edit] of this.originalEdits) {
|
||||
if (!this.parent.checked.isChecked(edit)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class BulkCategory {
|
||||
@@ -230,7 +239,7 @@ export class BulkFileOperations {
|
||||
}
|
||||
|
||||
operationByResource.forEach(value => this.fileOperations.push(value));
|
||||
operationByCategory.forEach(value => value.metadata.needsConfirmation ? this.categories.unshift(value) : this.categories.push(value));
|
||||
operationByCategory.forEach(value => this.categories.push(value));
|
||||
|
||||
// "correct" invalid parent-check child states that is
|
||||
// unchecked file edits (rename, create, delete) uncheck
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
|
||||
@@ -24,6 +24,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { WorkspaceFileEdit } from 'vs/editor/common/modes';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
|
||||
// --- VIEW MODEL
|
||||
|
||||
@@ -248,6 +249,39 @@ export class BulkEditDataSource implements IAsyncDataSource<BulkFileOperations,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class BulkEditSorter implements ITreeSorter<BulkEditElement> {
|
||||
|
||||
compare(a: BulkEditElement, b: BulkEditElement): number {
|
||||
if (a instanceof CategoryElement && b instanceof CategoryElement) {
|
||||
//
|
||||
const aConfirm = BulkEditSorter._needsConfirmation(a.category);
|
||||
const bConfirm = BulkEditSorter._needsConfirmation(b.category);
|
||||
if (aConfirm === bConfirm) {
|
||||
return a.category.metadata.label.localeCompare(b.category.metadata.label);
|
||||
} else if (aConfirm) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (a instanceof FileElement && b instanceof FileElement) {
|
||||
return compare(a.edit.uri.toString(), b.edit.uri.toString());
|
||||
}
|
||||
|
||||
if (a instanceof TextEditElement && b instanceof TextEditElement) {
|
||||
return Range.compareRangesUsingStarts(a.edit.textEdit.edit.range, b.edit.textEdit.edit.range);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static _needsConfirmation(a: BulkCategory): boolean {
|
||||
return a.fileOperations.some(ops => ops.needsConfirmation());
|
||||
}
|
||||
}
|
||||
|
||||
// --- ACCESSI
|
||||
|
||||
export class BulkEditAccessibilityProvider implements IAccessibilityProvider<BulkEditElement> {
|
||||
|
||||
@@ -40,7 +40,7 @@ CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAcces
|
||||
// #region Reopen With
|
||||
|
||||
const REOPEN_WITH_COMMAND_ID = 'reOpenWith';
|
||||
const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With'), original: 'Reopen With' };
|
||||
const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With...'), original: 'Reopen With' };
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: REOPEN_WITH_COMMAND_ID,
|
||||
@@ -83,6 +83,17 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
when: CONTEXT_HAS_CUSTOM_EDITORS,
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: REOPEN_WITH_COMMAND_ID,
|
||||
title: REOPEN_WITH_TITLE,
|
||||
category: viewCategory,
|
||||
},
|
||||
group: '3_open',
|
||||
order: 20,
|
||||
when: CONTEXT_HAS_CUSTOM_EDITORS,
|
||||
});
|
||||
|
||||
// #endregion
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
|
||||
import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
|
||||
export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
|
||||
export const twistiePixels = 20;
|
||||
@@ -233,11 +232,3 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpr
|
||||
templateData.toDispose.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseDebugViewPane extends ViewPane {
|
||||
|
||||
render(): void {
|
||||
super.render();
|
||||
dom.addClass(this.element, 'debug-pane');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,13 @@ import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Gesture } from 'vs/base/browser/touch';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -54,7 +53,7 @@ export function getExpandedBodySize(model: IDebugModel): number {
|
||||
return Math.min(MAX_VISIBLE_BREAKPOINTS, length) * 22;
|
||||
}
|
||||
|
||||
export class BreakpointsView extends BaseDebugViewPane {
|
||||
export class BreakpointsView extends ViewPane {
|
||||
|
||||
private list!: WorkbenchList<IEnablement>;
|
||||
private needsRefresh = false;
|
||||
@@ -82,6 +81,7 @@ export class BreakpointsView extends BaseDebugViewPane {
|
||||
public renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
dom.addClass(this.element, 'debug-pane');
|
||||
dom.addClass(container, 'debug-breakpoints');
|
||||
const delegate = new BreakpointsDelegate(this.debugService);
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { renderViewTree, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
@@ -74,7 +74,7 @@ export function getContextForContributedActions(element: CallStackItem | null):
|
||||
return '';
|
||||
}
|
||||
|
||||
export class CallStackView extends BaseDebugViewPane {
|
||||
export class CallStackView extends ViewPane {
|
||||
private pauseMessage!: HTMLSpanElement;
|
||||
private pauseMessageLabel!: HTMLSpanElement;
|
||||
private onCallStackChangeScheduler: RunOnceScheduler;
|
||||
@@ -158,7 +158,7 @@ export class CallStackView extends BaseDebugViewPane {
|
||||
|
||||
renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
dom.addClass(this.element, 'debug-pane');
|
||||
dom.addClass(container, 'debug-call-stack');
|
||||
const treeContainer = renderViewTree(container);
|
||||
|
||||
|
||||
@@ -136,17 +136,19 @@ function getWordToLineNumbersMap(model: ITextModel | null): Map<string, number[]
|
||||
model.forceTokenization(lineNumber);
|
||||
const lineTokens = model.getLineTokens(lineNumber);
|
||||
for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {
|
||||
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
|
||||
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
|
||||
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
|
||||
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
|
||||
|
||||
// Token is a word and not a comment
|
||||
if (tokenType === StandardTokenType.Other) {
|
||||
DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match
|
||||
|
||||
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
|
||||
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
|
||||
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
|
||||
const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr);
|
||||
|
||||
if (wordMatch) {
|
||||
|
||||
const word = wordMatch[0];
|
||||
if (!result.has(word)) {
|
||||
result.set(word, []);
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDebugService, IExpression, IExpressionContainer, IStackFrame } from 'vs/workbench/contrib/debug/common/debug';
|
||||
@@ -30,6 +30,8 @@ import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView';
|
||||
import { EvaluatableExpressionProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
const $ = dom.$;
|
||||
const MAX_TREE_HEIGHT = 324;
|
||||
@@ -107,6 +109,7 @@ export class DebugHoverWidget implements IContentWidget {
|
||||
accessibilityProvider: new DebugHoverAccessibilityProvider(),
|
||||
mouseSupport: false,
|
||||
horizontalScrolling: true,
|
||||
useShadows: false,
|
||||
overrideStyles: {
|
||||
listBackground: editorHoverBackground
|
||||
}
|
||||
@@ -174,18 +177,52 @@ export class DebugHoverWidget implements IContentWidget {
|
||||
}
|
||||
|
||||
async showAt(range: Range, focus: boolean): Promise<void> {
|
||||
const pos = range.getStartPosition();
|
||||
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
if (!this.editor.hasModel()) {
|
||||
|
||||
if (!session || !this.editor.hasModel()) {
|
||||
return Promise.resolve(this.hide());
|
||||
}
|
||||
|
||||
const lineContent = this.editor.getModel().getLineContent(pos.lineNumber);
|
||||
const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn);
|
||||
// use regex to extract the sub-expression #9821
|
||||
const matchingExpression = lineContent.substring(start - 1, end);
|
||||
if (!matchingExpression || !session) {
|
||||
const model = this.editor.getModel();
|
||||
const pos = range.getStartPosition();
|
||||
|
||||
let rng: IRange | undefined = undefined;
|
||||
let matchingExpression: string | undefined;
|
||||
|
||||
if (EvaluatableExpressionProviderRegistry.has(model)) {
|
||||
const supports = EvaluatableExpressionProviderRegistry.ordered(model);
|
||||
|
||||
const promises = supports.map(support => {
|
||||
return Promise.resolve(support.provideEvaluatableExpression(model, pos, CancellationToken.None)).then(expression => {
|
||||
return expression;
|
||||
}, err => {
|
||||
//onUnexpectedExternalError(err);
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
|
||||
const results = await Promise.all(promises).then(coalesce);
|
||||
if (results.length > 0) {
|
||||
matchingExpression = results[0].expression;
|
||||
rng = results[0].range;
|
||||
|
||||
if (!matchingExpression) {
|
||||
const lineContent = model.getLineContent(pos.lineNumber);
|
||||
matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn);
|
||||
}
|
||||
}
|
||||
|
||||
} else { // old one-size-fits-all strategy
|
||||
const lineContent = model.getLineContent(pos.lineNumber);
|
||||
const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn);
|
||||
|
||||
// use regex to extract the sub-expression #9821
|
||||
matchingExpression = lineContent.substring(start - 1, end);
|
||||
rng = new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length);
|
||||
}
|
||||
|
||||
if (!matchingExpression) {
|
||||
return Promise.resolve(this.hide());
|
||||
}
|
||||
|
||||
@@ -202,13 +239,15 @@ export class DebugHoverWidget implements IContentWidget {
|
||||
|
||||
if (!expression || (expression instanceof Expression && !expression.available)) {
|
||||
this.hide();
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{
|
||||
range: new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length),
|
||||
options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS
|
||||
}]);
|
||||
if (rng) {
|
||||
this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{
|
||||
range: rng,
|
||||
options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS
|
||||
}]);
|
||||
}
|
||||
|
||||
return this.doShow(pos, expression, focus);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { normalize, isAbsolute, posix } from 'vs/base/common/path';
|
||||
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { renderViewTree, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
@@ -402,7 +402,7 @@ function asTreeElement(item: BaseTreeItem, viewState?: IViewState): ITreeElement
|
||||
};
|
||||
}
|
||||
|
||||
export class LoadedScriptsView extends BaseDebugViewPane {
|
||||
export class LoadedScriptsView extends ViewPane {
|
||||
|
||||
private treeContainer!: HTMLElement;
|
||||
private loadedScriptsItemType: IContextKey<string>;
|
||||
@@ -435,6 +435,7 @@ export class LoadedScriptsView extends BaseDebugViewPane {
|
||||
renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
dom.addClass(this.element, 'debug-pane');
|
||||
dom.addClass(container, 'debug-loaded-scripts');
|
||||
dom.addClass(container, 'show-file-icons');
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
/* Expressions */
|
||||
|
||||
.monaco-workbench .debug-viewlet .monaco-list-row .expression,
|
||||
.monaco-workbench .debug-pane .monaco-list-row .expression,
|
||||
.monaco-workbench .debug-hover-widget .monaco-list-row .expression {
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
@@ -84,7 +84,7 @@
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.monaco-workbench.mac .debug-viewlet .monaco-list-row .expression,
|
||||
.monaco-workbench.mac .debug-pane .monaco-list-row .expression,
|
||||
.monaco-workbench.mac .debug-hover-widget .monaco-list-row .expression {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
word-break: break-all;
|
||||
padding: 4px 5px;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .complex-value {
|
||||
@@ -62,6 +61,7 @@
|
||||
overflow: auto;
|
||||
font-family: var(--monaco-monospace-font);
|
||||
max-height: 500px;
|
||||
padding: 4px 5px;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .error {
|
||||
|
||||
@@ -601,7 +601,7 @@ export class RawDebugSession implements IDisposable {
|
||||
private send<R extends DebugProtocol.Response>(command: string, args: any, token?: CancellationToken, timeout?: number): Promise<R> {
|
||||
return new Promise<DebugProtocol.Response>((completeDispatch, errorDispatch) => {
|
||||
if (!this.debugAdapter) {
|
||||
errorDispatch(new Error('no debug adapter found'));
|
||||
errorDispatch(new Error(nls.localize('noDebugAdapter', "No debug adapter found. Can not send '{0}'.", command)));
|
||||
return;
|
||||
}
|
||||
let cancelationListener: IDisposable;
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -170,6 +170,7 @@ export class StartView extends ViewPane {
|
||||
}));
|
||||
attachButtonStyler(this.debugButton, this.themeService);
|
||||
|
||||
dom.addClass(this.element, 'debug-pane');
|
||||
dom.addClass(container, 'debug-start-view');
|
||||
|
||||
this.secondMessageContainer = $('.section');
|
||||
|
||||
@@ -12,12 +12,12 @@ import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewMod
|
||||
import { Variable, Scope } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
@@ -39,7 +39,7 @@ let forgetScopes = true;
|
||||
|
||||
export const variableSetEmitter = new Emitter<void>();
|
||||
|
||||
export class VariablesView extends BaseDebugViewPane {
|
||||
export class VariablesView extends ViewPane {
|
||||
|
||||
private onFocusStackFrameScheduler: RunOnceScheduler;
|
||||
private needsRefresh = false;
|
||||
@@ -90,6 +90,7 @@ export class VariablesView extends BaseDebugViewPane {
|
||||
renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
dom.addClass(this.element, 'debug-pane');
|
||||
dom.addClass(container, 'debug-variables');
|
||||
const treeContainer = renderViewTree(container);
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
@@ -38,7 +38,7 @@ const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
|
||||
let ignoreVariableSetEmitter = false;
|
||||
let useCachedEvaluation = false;
|
||||
|
||||
export class WatchExpressionsView extends BaseDebugViewPane {
|
||||
export class WatchExpressionsView extends ViewPane {
|
||||
|
||||
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
|
||||
private needsRefresh = false;
|
||||
@@ -67,6 +67,7 @@ export class WatchExpressionsView extends BaseDebugViewPane {
|
||||
renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
dom.addClass(this.element, 'debug-pane');
|
||||
dom.addClass(container, 'debug-watch');
|
||||
const treeContainer = renderViewTree(container);
|
||||
|
||||
|
||||
@@ -676,7 +676,11 @@ export class ExplorerView extends ViewPane {
|
||||
if (item.isDisposed) {
|
||||
return this.onSelectResource(resource, reveal, retry + 1);
|
||||
}
|
||||
this.tree.reveal(item, 0.5);
|
||||
|
||||
// Don't scroll to the item if it's already visible
|
||||
if (this.tree.getRelativeTop(item) === null) {
|
||||
this.tree.reveal(item, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
this.tree.setFocus([item]);
|
||||
|
||||
@@ -29,13 +29,13 @@ export class OpenInDesktopIndicator extends Disposable implements IWorkbenchCont
|
||||
) {
|
||||
super();
|
||||
|
||||
const links = environmentService.options?.applicationLinkProvider?.();
|
||||
const links = environmentService.options?.applicationLinks;
|
||||
if (Array.isArray(links) && links?.length > 0) {
|
||||
this.installOpenInDesktopIndicator(links);
|
||||
}
|
||||
}
|
||||
|
||||
private installOpenInDesktopIndicator(links: IApplicationLink[]): void {
|
||||
private installOpenInDesktopIndicator(links: readonly IApplicationLink[]): void {
|
||||
|
||||
// Register action to trigger "Open In Desktop"
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
@@ -71,7 +71,7 @@ export class OpenInDesktopAction extends Action {
|
||||
}
|
||||
|
||||
async run(): Promise<boolean> {
|
||||
const links = this.environmentService.options?.applicationLinkProvider?.();
|
||||
const links = this.environmentService.options?.applicationLinks;
|
||||
if (Array.isArray(links)) {
|
||||
if (links.length === 1) {
|
||||
return this.openApplicationLink(links[0]);
|
||||
@@ -83,7 +83,7 @@ export class OpenInDesktopAction extends Action {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async runWithPicker(links: IApplicationLink[]): Promise<boolean> {
|
||||
private async runWithPicker(links: readonly IApplicationLink[]): Promise<boolean> {
|
||||
|
||||
// Show a picker with choices
|
||||
const quickPick = this.quickInputService.createQuickPick<IApplicationLink>();
|
||||
|
||||
@@ -27,7 +27,7 @@ import { localize } from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IResourceInput, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
|
||||
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { WorkbenchDataTree } from 'vs/platform/list/browser/listService';
|
||||
@@ -37,7 +37,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { CollapseAction } from 'vs/workbench/browser/viewlet';
|
||||
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { OutlineConfigKeys, OutlineViewFocused, OutlineViewFiltered } from 'vs/editor/contrib/documentSymbols/outline';
|
||||
import { FuzzyScore } from 'vs/base/common/filters';
|
||||
import { OutlineDataSource, OutlineItemComparator, OutlineSortOrder, OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItem, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree';
|
||||
@@ -49,6 +49,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco
|
||||
import { MarkerSeverity } from 'vs/platform/markers/common/markers';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
|
||||
class RequestState {
|
||||
|
||||
@@ -261,7 +262,7 @@ export class OutlinePane extends ViewPane {
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
||||
@IMarkerDecorationsService private readonly _markerDecorationService: IMarkerDecorationsService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@@ -347,7 +348,7 @@ export class OutlinePane extends ViewPane {
|
||||
|
||||
this._disposables.push(this._tree);
|
||||
this._disposables.push(this._outlineViewState.onDidChange(this._onDidChangeUserState, this));
|
||||
this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => {
|
||||
this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views }) => {
|
||||
if (views.some(v => v.id === this.id)) {
|
||||
this._tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } });
|
||||
}
|
||||
@@ -629,15 +630,18 @@ export class OutlinePane extends ViewPane {
|
||||
}
|
||||
|
||||
private async _revealTreeSelection(model: OutlineModel, element: OutlineElement, focus: boolean, aside: boolean): Promise<void> {
|
||||
|
||||
await this._editorService.openEditor({
|
||||
resource: model.textModel.uri,
|
||||
options: {
|
||||
preserveFocus: !focus,
|
||||
selection: Range.collapseToStart(element.symbol.selectionRange),
|
||||
selectionRevealType: TextEditorSelectionRevealType.NearTop,
|
||||
}
|
||||
} as IResourceInput, aside ? SIDE_GROUP : ACTIVE_GROUP);
|
||||
await this._editorService.openCodeEditor(
|
||||
{
|
||||
resource: model.textModel.uri,
|
||||
options: {
|
||||
preserveFocus: !focus,
|
||||
selection: Range.collapseToStart(element.symbol.selectionRange),
|
||||
selectionRevealType: TextEditorSelectionRevealType.NearTop,
|
||||
}
|
||||
},
|
||||
this._editorService.getActiveCodeEditor(),
|
||||
aside
|
||||
);
|
||||
}
|
||||
|
||||
private _revealEditorSelection(model: OutlineModel, selection: Selection): void {
|
||||
|
||||
@@ -29,7 +29,7 @@ import { SelectionHighlighter } from 'vs/editor/contrib/multicursor/multicursor'
|
||||
import * as nls from 'vs/nls';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IInstantiationService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
@@ -984,7 +984,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor {
|
||||
private static _getContributions(): IEditorContributionDescription[] {
|
||||
const skipContributions = [FoldingController.ID, SelectionHighlighter.ID, FindController.ID];
|
||||
const contributions = EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.id) === -1);
|
||||
contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution });
|
||||
contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution as IConstructorSignature1<ICodeEditor, editorCommon.IEditorContribution> });
|
||||
return contributions;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { isMacintosh, isNative } from 'vs/base/common/platform';
|
||||
import { isMacintosh, isNative, isLinux } from 'vs/base/common/platform';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
@@ -26,6 +26,7 @@ interface IConfiguration extends IWindowsConfiguration {
|
||||
telemetry: { enableCrashReporter: boolean };
|
||||
workbench: { list: { horizontalScrolling: boolean } };
|
||||
debug: { console: { wordWrap: boolean } };
|
||||
editor: { accessibilitySupport: 'on' | 'off' | 'auto' };
|
||||
}
|
||||
|
||||
export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution {
|
||||
@@ -38,6 +39,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo
|
||||
private enableCrashReporter: boolean | undefined;
|
||||
private treeHorizontalScrolling: boolean | undefined;
|
||||
private debugConsoleWordWrap: boolean | undefined;
|
||||
private accessibilitySupport: 'on' | 'off' | 'auto' | undefined;
|
||||
|
||||
constructor(
|
||||
@IHostService private readonly hostService: IHostService,
|
||||
@@ -103,6 +105,14 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo
|
||||
this.enableCrashReporter = config.telemetry.enableCrashReporter;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// On linux turning on accessibility support will also pass this flag to the chrome renderer, thus a restart is required
|
||||
if (isLinux && typeof config.editor?.accessibilitySupport === 'string' && config.editor.accessibilitySupport !== this.accessibilitySupport) {
|
||||
this.accessibilitySupport = config.editor.accessibilitySupport;
|
||||
if (this.accessibilitySupport === 'on') {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify only when changed and we are the focused window (avoids notification spam across windows)
|
||||
|
||||
@@ -13,3 +13,20 @@
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label {
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container {
|
||||
margin-left: 2px;
|
||||
margin-right: 4px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container .timeline-timestamp {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import { localize } from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IListVirtualDelegate, IIdentityProvider, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeNode, ITreeRenderer, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
@@ -20,7 +20,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TimelineItem, ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItem } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { SideBySideEditor, toResource } from 'vs/workbench/common/editor';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
@@ -31,10 +31,20 @@ import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IAction, ActionRunner } from 'vs/base/common/actions';
|
||||
import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { MenuItemAction, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
|
||||
// TODO[ECA]: Localize all the strings
|
||||
|
||||
type TreeElement = TimelineItem;
|
||||
|
||||
// TODO[ECA]: Localize all the strings
|
||||
interface TimelineActionContext {
|
||||
uri: URI | undefined;
|
||||
item: TreeElement;
|
||||
}
|
||||
|
||||
export class TimelinePane extends ViewPane {
|
||||
static readonly ID = 'timeline';
|
||||
@@ -44,10 +54,12 @@ export class TimelinePane extends ViewPane {
|
||||
private _messageElement!: HTMLDivElement;
|
||||
private _treeElement!: HTMLDivElement;
|
||||
private _tree!: WorkbenchObjectTree<TreeElement, FuzzyScore>;
|
||||
private _treeRenderer: TimelineTreeRenderer | undefined;
|
||||
private _menus: TimelineMenus;
|
||||
private _visibilityDisposables: DisposableStore | undefined;
|
||||
|
||||
// private _excludedSources: Set<string> | undefined;
|
||||
private _items: TimelineItemWithSource[] = [];
|
||||
private _items: TimelineItem[] = [];
|
||||
private _loadingMessageTimer: any | undefined;
|
||||
private _pendingRequests = new Map<string, TimelineRequest>();
|
||||
private _uri: URI | undefined;
|
||||
@@ -67,7 +79,9 @@ export class TimelinePane extends ViewPane {
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService);
|
||||
super({ ...options, titleMenuId: MenuId.TimelineTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService);
|
||||
|
||||
this._menus = this._register(this.instantiationService.createInstance(TimelineMenus, this.id));
|
||||
|
||||
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
|
||||
scopedContextKeyService.createKey('view', TimelinePane.ID);
|
||||
@@ -88,6 +102,7 @@ export class TimelinePane extends ViewPane {
|
||||
}
|
||||
|
||||
this._uri = uri;
|
||||
this._treeRenderer?.setUri(uri);
|
||||
this.loadTimeline();
|
||||
}
|
||||
|
||||
@@ -187,7 +202,7 @@ export class TimelinePane extends ViewPane {
|
||||
let request = this._pendingRequests.get(source);
|
||||
request?.tokenSource.dispose(true);
|
||||
|
||||
request = this.timelineService.getTimelineRequest(source, this._uri, new CancellationTokenSource())!;
|
||||
request = this.timelineService.getTimeline(source, this._uri, {}, new CancellationTokenSource(), { cacheResults: true })!;
|
||||
|
||||
this._pendingRequests.set(source, request);
|
||||
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
|
||||
@@ -199,7 +214,7 @@ export class TimelinePane extends ViewPane {
|
||||
private async handleRequest(request: TimelineRequest) {
|
||||
let items;
|
||||
try {
|
||||
items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.items);
|
||||
items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.result.then(r => r?.items ?? []));
|
||||
}
|
||||
catch { }
|
||||
|
||||
@@ -211,7 +226,7 @@ export class TimelinePane extends ViewPane {
|
||||
this.replaceItems(request.source, items);
|
||||
}
|
||||
|
||||
private replaceItems(source: string, items?: TimelineItemWithSource[]) {
|
||||
private replaceItems(source: string, items?: TimelineItem[]) {
|
||||
const hasItems = this._items.length !== 0;
|
||||
|
||||
if (items?.length) {
|
||||
@@ -291,17 +306,20 @@ export class TimelinePane extends ViewPane {
|
||||
// DOM.addClass(this._treeElement, 'show-file-icons');
|
||||
container.appendChild(this._treeElement);
|
||||
|
||||
const renderer = this.instantiationService.createInstance(TimelineTreeRenderer);
|
||||
this._tree = <WorkbenchObjectTree<TreeElement, FuzzyScore>>this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', this._treeElement, new TimelineListVirtualDelegate(), [renderer], {
|
||||
this._treeRenderer = this.instantiationService.createInstance(TimelineTreeRenderer, this._menus);
|
||||
this._tree = <WorkbenchObjectTree<TreeElement, FuzzyScore>>this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane',
|
||||
this._treeElement, new TimelineListVirtualDelegate(), [this._treeRenderer], {
|
||||
identityProvider: new TimelineIdentityProvider(),
|
||||
keyboardNavigationLabelProvider: new TimelineKeyboardNavigationLabelProvider(),
|
||||
overrideStyles: {
|
||||
listBackground: this.getBackgroundColor()
|
||||
listBackground: this.getBackgroundColor(),
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
const customTreeNavigator = new TreeResourceNavigator(this._tree, { openOnFocus: false, openOnSelection: false });
|
||||
this._register(customTreeNavigator);
|
||||
this._register(this._tree.onContextMenu(e => this.onContextMenu(this._menus, e)));
|
||||
this._register(
|
||||
customTreeNavigator.onDidOpenResource(e => {
|
||||
if (!e.browserEvent) {
|
||||
@@ -316,36 +334,112 @@ export class TimelinePane extends ViewPane {
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class TimelineElementTemplate {
|
||||
static readonly id = 'TimelineElementTemplate';
|
||||
private onContextMenu(menus: TimelineMenus, treeEvent: ITreeContextMenuEvent<TreeElement | null>): void {
|
||||
const item = treeEvent.element;
|
||||
if (item === null) {
|
||||
return;
|
||||
}
|
||||
const event: UIEvent = treeEvent.browserEvent;
|
||||
|
||||
constructor(
|
||||
readonly container: HTMLElement,
|
||||
readonly iconLabel: IconLabel,
|
||||
readonly icon: HTMLElement
|
||||
) { }
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
export class TimelineIdentityProvider implements IIdentityProvider<TimelineItem> {
|
||||
getId(item: TimelineItem): { toString(): string } {
|
||||
return `${item.id}|${item.timestamp}`;
|
||||
this._tree.setFocus([item]);
|
||||
const actions = menus.getResourceContextActions(item);
|
||||
if (!actions.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => treeEvent.anchor,
|
||||
getActions: () => actions,
|
||||
getActionViewItem: (action) => {
|
||||
const keybinding = this.keybindingService.lookupKeybinding(action.id);
|
||||
if (keybinding) {
|
||||
return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() });
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
onHide: (wasCancelled?: boolean) => {
|
||||
if (wasCancelled) {
|
||||
this._tree.domFocus();
|
||||
}
|
||||
},
|
||||
getActionsContext: (): TimelineActionContext => ({ uri: this._uri, item: item }),
|
||||
actionRunner: new TimelineActionRunner()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class TimelineKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider<TimelineItem> {
|
||||
getKeyboardNavigationLabel(element: TimelineItem): { toString(): string } {
|
||||
export class TimelineElementTemplate implements IDisposable {
|
||||
static readonly id = 'TimelineElementTemplate';
|
||||
|
||||
readonly actionBar: ActionBar;
|
||||
readonly icon: HTMLElement;
|
||||
readonly iconLabel: IconLabel;
|
||||
readonly timestamp: HTMLSpanElement;
|
||||
|
||||
constructor(
|
||||
readonly container: HTMLElement,
|
||||
actionViewItemProvider: IActionViewItemProvider
|
||||
) {
|
||||
DOM.addClass(container, 'custom-view-tree-node-item');
|
||||
this.icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon'));
|
||||
|
||||
this.iconLabel = new IconLabel(container, { supportHighlights: true, supportCodicons: true });
|
||||
|
||||
const timestampContainer = DOM.append(this.iconLabel.element, DOM.$('.timeline-timestamp-container'));
|
||||
this.timestamp = DOM.append(timestampContainer, DOM.$('span.timeline-timestamp'));
|
||||
|
||||
const actionsContainer = DOM.append(this.iconLabel.element, DOM.$('.actions'));
|
||||
this.actionBar = new ActionBar(actionsContainer, { actionViewItemProvider: actionViewItemProvider });
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.iconLabel.dispose();
|
||||
this.actionBar.dispose();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.actionBar.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export class TimelineIdentityProvider implements IIdentityProvider<TreeElement> {
|
||||
getId(item: TreeElement): { toString(): string } {
|
||||
return item.handle;
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineActionRunner extends ActionRunner {
|
||||
|
||||
runAction(action: IAction, { uri, item }: TimelineActionContext): Promise<any> {
|
||||
return action.run(...[
|
||||
{
|
||||
$mid: 11,
|
||||
handle: item.handle,
|
||||
source: item.source,
|
||||
uri: uri
|
||||
},
|
||||
uri,
|
||||
item.source,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export class TimelineKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider<TreeElement> {
|
||||
getKeyboardNavigationLabel(element: TreeElement): { toString(): string } {
|
||||
return element.label;
|
||||
}
|
||||
}
|
||||
|
||||
export class TimelineListVirtualDelegate implements IListVirtualDelegate<TimelineItem> {
|
||||
getHeight(_element: TimelineItem): number {
|
||||
export class TimelineListVirtualDelegate implements IListVirtualDelegate<TreeElement> {
|
||||
getHeight(_element: TreeElement): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(element: TimelineItem): string {
|
||||
getTemplateId(element: TreeElement): string {
|
||||
return TimelineElementTemplate.id;
|
||||
}
|
||||
}
|
||||
@@ -353,14 +447,25 @@ export class TimelineListVirtualDelegate implements IListVirtualDelegate<Timelin
|
||||
class TimelineTreeRenderer implements ITreeRenderer<TreeElement, FuzzyScore, TimelineElementTemplate> {
|
||||
readonly templateId: string = TimelineElementTemplate.id;
|
||||
|
||||
constructor(@IThemeService private _themeService: IThemeService) { }
|
||||
private _actionViewItemProvider: IActionViewItemProvider;
|
||||
|
||||
constructor(
|
||||
private readonly _menus: TimelineMenus,
|
||||
@IInstantiationService protected readonly instantiationService: IInstantiationService,
|
||||
@IThemeService private _themeService: IThemeService
|
||||
) {
|
||||
this._actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction
|
||||
? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private _uri: URI | undefined;
|
||||
setUri(uri: URI | undefined) {
|
||||
this._uri = uri;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): TimelineElementTemplate {
|
||||
DOM.addClass(container, 'custom-view-tree-node-item');
|
||||
const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon'));
|
||||
|
||||
const iconLabel = new IconLabel(container, { supportHighlights: true, supportCodicons: true });
|
||||
return new TimelineElementTemplate(container, iconLabel, icon);
|
||||
return new TimelineElementTemplate(container, this._actionViewItemProvider);
|
||||
}
|
||||
|
||||
renderElement(
|
||||
@@ -369,30 +474,74 @@ class TimelineTreeRenderer implements ITreeRenderer<TreeElement, FuzzyScore, Tim
|
||||
template: TimelineElementTemplate,
|
||||
height: number | undefined
|
||||
): void {
|
||||
const { element } = node;
|
||||
template.reset();
|
||||
|
||||
const icon = this._themeService.getTheme().type === LIGHT ? element.icon : element.iconDark;
|
||||
const { element: item } = node;
|
||||
|
||||
const icon = this._themeService.getTheme().type === LIGHT ? item.icon : item.iconDark;
|
||||
const iconUrl = icon ? URI.revive(icon) : null;
|
||||
|
||||
if (iconUrl) {
|
||||
template.icon.className = 'custom-view-tree-node-item-icon';
|
||||
template.icon.style.backgroundImage = DOM.asCSSUrl(iconUrl);
|
||||
|
||||
} else {
|
||||
let iconClass: string | undefined;
|
||||
if (element.themeIcon /*&& !this.isFileKindThemeIcon(element.themeIcon)*/) {
|
||||
iconClass = ThemeIcon.asClassName(element.themeIcon);
|
||||
if (item.themeIcon /*&& !this.isFileKindThemeIcon(element.themeIcon)*/) {
|
||||
iconClass = ThemeIcon.asClassName(item.themeIcon);
|
||||
}
|
||||
template.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : '';
|
||||
}
|
||||
|
||||
template.iconLabel.setLabel(element.label, element.description, {
|
||||
title: element.detail,
|
||||
template.iconLabel.setLabel(item.label, item.description, {
|
||||
title: item.detail,
|
||||
matches: createMatches(node.filterData)
|
||||
});
|
||||
|
||||
template.timestamp.textContent = fromNow(item.timestamp);
|
||||
|
||||
template.actionBar.context = { uri: this._uri, item: item } as TimelineActionContext;
|
||||
template.actionBar.actionRunner = new TimelineActionRunner();
|
||||
template.actionBar.push(this._menus.getResourceActions(item), { icon: true, label: false });
|
||||
}
|
||||
|
||||
disposeTemplate(template: TimelineElementTemplate): void {
|
||||
template.iconLabel.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineMenus extends Disposable {
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getResourceActions(element: TreeElement): IAction[] {
|
||||
return this.getActions(MenuId.TimelineItemContext, { key: 'timelineItem', value: element.contextValue }).primary;
|
||||
}
|
||||
|
||||
getResourceContextActions(element: TreeElement): IAction[] {
|
||||
return this.getActions(MenuId.TimelineItemContext, { key: 'timelineItem', value: element.contextValue }).secondary;
|
||||
}
|
||||
|
||||
private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } {
|
||||
const contextKeyService = this.contextKeyService.createScoped();
|
||||
contextKeyService.createKey('view', this.id);
|
||||
contextKeyService.createKey(context.key, context.value);
|
||||
|
||||
const menu = this.menuService.createMenu(menuId, contextKeyService);
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
const result = { primary, secondary };
|
||||
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
|
||||
|
||||
menu.dispose();
|
||||
contextKeyService.dispose();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@ export function toKey(extension: ExtensionIdentifier | string, source: string) {
|
||||
}
|
||||
|
||||
export interface TimelineItem {
|
||||
handle: string;
|
||||
source: string;
|
||||
|
||||
timestamp: number;
|
||||
label: string;
|
||||
id?: string;
|
||||
icon?: URI,
|
||||
iconDark?: URI,
|
||||
themeIcon?: { id: string },
|
||||
@@ -28,19 +30,29 @@ export interface TimelineItem {
|
||||
contextValue?: string;
|
||||
}
|
||||
|
||||
export interface TimelineItemWithSource extends TimelineItem {
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface TimelineChangeEvent {
|
||||
id: string;
|
||||
uri?: URI;
|
||||
}
|
||||
|
||||
export interface TimelineCursor {
|
||||
cursor?: any;
|
||||
before?: boolean;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface Timeline {
|
||||
source: string;
|
||||
items: TimelineItem[];
|
||||
|
||||
cursor?: any;
|
||||
more?: boolean;
|
||||
}
|
||||
|
||||
export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable {
|
||||
onDidChange?: Event<TimelineChangeEvent>;
|
||||
|
||||
provideTimeline(uri: URI, token: CancellationToken): Promise<TimelineItemWithSource[]>;
|
||||
provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
}
|
||||
|
||||
export interface TimelineProviderDescriptor {
|
||||
@@ -55,7 +67,7 @@ export interface TimelineProvidersChangeEvent {
|
||||
}
|
||||
|
||||
export interface TimelineRequest {
|
||||
readonly items: Promise<TimelineItemWithSource[]>;
|
||||
readonly result: Promise<Timeline | undefined>;
|
||||
readonly source: string;
|
||||
readonly tokenSource: CancellationTokenSource;
|
||||
readonly uri: URI;
|
||||
@@ -72,9 +84,7 @@ export interface ITimelineService {
|
||||
|
||||
getSources(): string[];
|
||||
|
||||
getTimeline(uri: URI, token: CancellationToken): Promise<TimelineItem[]>;
|
||||
|
||||
getTimelineRequest(id: string, uri: URI, tokenSource: CancellationTokenSource): TimelineRequest | undefined;
|
||||
getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }): TimelineRequest | undefined;
|
||||
}
|
||||
|
||||
const TIMELINE_SERVICE_ID = 'timeline';
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
// import { basename } from 'vs/base/common/path';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITimelineService, TimelineProvider, TimelineItem, TimelineChangeEvent, TimelineProvidersChangeEvent } from './timeline';
|
||||
import { ITimelineService, TimelineChangeEvent, TimelineCursor, TimelineProvidersChangeEvent, TimelineProvider } from './timeline';
|
||||
|
||||
export class TimelineService implements ITimelineService {
|
||||
_serviceBrand: undefined;
|
||||
@@ -81,42 +81,7 @@ export class TimelineService implements ITimelineService {
|
||||
return [...this._providers.keys()];
|
||||
}
|
||||
|
||||
async getTimeline(uri: URI, token: CancellationToken, predicate?: (provider: TimelineProvider) => boolean) {
|
||||
this.logService.trace(`TimelineService#getTimeline(${uri.toString(true)})`);
|
||||
|
||||
const requests: Promise<[string, TimelineItem[]]>[] = [];
|
||||
|
||||
for (const provider of this._providers.values()) {
|
||||
if (typeof provider.scheme === 'string') {
|
||||
if (provider.scheme !== '*' && provider.scheme !== uri.scheme) {
|
||||
continue;
|
||||
}
|
||||
} else if (!provider.scheme.includes(uri.scheme)) {
|
||||
continue;
|
||||
}
|
||||
if (!(predicate?.(provider) ?? true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
requests.push(provider.provideTimeline(uri, token).then(p => [provider.id, p]));
|
||||
}
|
||||
|
||||
const timelines = await Promise.all(requests);
|
||||
|
||||
const timeline = [];
|
||||
for (const [source, items] of timelines) {
|
||||
if (items.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
timeline.push(...items.map(item => ({ ...item, source: source })));
|
||||
}
|
||||
|
||||
timeline.sort((a, b) => b.timestamp - a.timestamp);
|
||||
return timeline;
|
||||
}
|
||||
|
||||
getTimelineRequest(id: string, uri: URI, tokenSource: CancellationTokenSource) {
|
||||
getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }) {
|
||||
this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`);
|
||||
|
||||
const provider = this._providers.get(id);
|
||||
@@ -133,12 +98,16 @@ export class TimelineService implements ITimelineService {
|
||||
}
|
||||
|
||||
return {
|
||||
items: provider.provideTimeline(uri, tokenSource.token)
|
||||
.then(items => {
|
||||
items = items.map(item => ({ ...item, source: provider.id }));
|
||||
items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' }));
|
||||
result: provider.provideTimeline(uri, cursor, tokenSource.token, options)
|
||||
.then(result => {
|
||||
if (result === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return items;
|
||||
result.items = result.items.map(item => ({ ...item, source: provider.id }));
|
||||
result.items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' }));
|
||||
|
||||
return result;
|
||||
}),
|
||||
source: provider.id,
|
||||
tokenSource: tokenSource,
|
||||
|
||||
@@ -127,41 +127,41 @@ export class ProductContribution implements IWorkbenchContribution {
|
||||
@IHostService hostService: IHostService,
|
||||
@IProductService productService: IProductService
|
||||
) {
|
||||
if (!hostService.hasFocus) {
|
||||
return;
|
||||
}
|
||||
hostService.hadLastFocus().then(hadLastFocus => {
|
||||
if (!hadLastFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, '');
|
||||
const shouldShowReleaseNotes = configurationService.getValue<boolean>('update.showReleaseNotes');
|
||||
const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, '');
|
||||
const shouldShowReleaseNotes = configurationService.getValue<boolean>('update.showReleaseNotes');
|
||||
|
||||
// was there an update? if so, open release notes
|
||||
const releaseNotesUrl = productService.releaseNotesUrl;
|
||||
if (shouldShowReleaseNotes && !environmentService.args['skip-release-notes'] && releaseNotesUrl && lastVersion && productService.version !== lastVersion && productService.quality === 'stable') { // {{SQL CARBON EDIT}} Only show release notes for stable build
|
||||
/* // {{SQL CARBON EDIT}} Prompt user to open release notes in browser until we can get ADS release notes from the web
|
||||
showReleaseNotes(instantiationService, productService.version)
|
||||
.then(undefined, () => {
|
||||
*/
|
||||
notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", productService.nameLong, productService.version),
|
||||
[{
|
||||
label: nls.localize('releaseNotes', "Release Notes"),
|
||||
run: () => {
|
||||
const uri = URI.parse(releaseNotesUrl);
|
||||
openerService.open(uri);
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
// }); // {{SQL CARBON EDIT}}
|
||||
}
|
||||
// was there an update? if so, open release notes
|
||||
const releaseNotesUrl = productService.releaseNotesUrl;
|
||||
if (shouldShowReleaseNotes && !environmentService.args['skip-release-notes'] && releaseNotesUrl && lastVersion && productService.version !== lastVersion && productService.quality === 'stable') { // {{SQL CARBON EDIT}} Only show release notes for stable build) {
|
||||
/*showReleaseNotes(instantiationService, productService.version) {{SQL CARBON EDIT}} Prompt user to open release notes in browser until we can get ADS release notes from the web
|
||||
.then(undefined, () => {*/
|
||||
notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", productService.nameLong, productService.version),
|
||||
[{
|
||||
label: nls.localize('releaseNotes', "Release Notes"),
|
||||
run: () => {
|
||||
const uri = URI.parse(releaseNotesUrl);
|
||||
openerService.open(uri);
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
}/*);
|
||||
}*/
|
||||
|
||||
// should we show the new license?
|
||||
if (productService.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(productService.version, '>=1.0.0')) {
|
||||
notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", productService.licenseUrl));
|
||||
}
|
||||
// should we show the new license?
|
||||
if (productService.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(productService.version, '>=1.0.0')) {
|
||||
notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", productService.licenseUrl));
|
||||
}
|
||||
|
||||
storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL);
|
||||
storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -286,4 +286,10 @@ export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
|
||||
this.element.style.pointerEvents = '';
|
||||
}
|
||||
}
|
||||
|
||||
public selectAll() {
|
||||
if (this.element) {
|
||||
this._send('execCommand', 'selectAll');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd
|
||||
showFind(): void { this.withWebview(webview => webview.showFind()); }
|
||||
hideFind(): void { this.withWebview(webview => webview.hideFind()); }
|
||||
runFindAction(previous: boolean): void { this.withWebview(webview => webview.runFindAction(previous)); }
|
||||
selectAll(): void { this.withWebview(webview => webview.selectAll()); }
|
||||
|
||||
public getInnerWebview() {
|
||||
return this._webview.value;
|
||||
|
||||
@@ -232,23 +232,23 @@
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
const handleAuxClick =
|
||||
(event) => {
|
||||
// Prevent middle clicks opening a broken link in the browser
|
||||
if (!event.view || !event.view.document) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.button === 1) {
|
||||
let node = /** @type {any} */ (event.target);
|
||||
while (node) {
|
||||
if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) {
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
(event) => {
|
||||
// Prevent middle clicks opening a broken link in the browser
|
||||
if (!event.view || !event.view.document) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (event.button === 1) {
|
||||
let node = /** @type {any} */ (event.target);
|
||||
while (node) {
|
||||
if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) {
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {KeyboardEvent} e
|
||||
@@ -449,6 +449,10 @@
|
||||
}, 0);
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {Document} contentDocument
|
||||
* @param {Window} contentWindow
|
||||
*/
|
||||
const onLoad = (contentDocument, contentWindow) => {
|
||||
if (contentDocument && contentDocument.body) {
|
||||
// Workaround for https://github.com/Microsoft/vscode/issues/12865
|
||||
@@ -492,10 +496,12 @@
|
||||
}, 200);
|
||||
|
||||
newFrame.contentWindow.addEventListener('load', function (e) {
|
||||
const contentDocument = /** @type {Document} */ (e.target);
|
||||
|
||||
if (loadTimeout) {
|
||||
clearTimeout(loadTimeout);
|
||||
loadTimeout = undefined;
|
||||
onLoad(e.target, this);
|
||||
onLoad(contentDocument, this);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -539,6 +545,13 @@
|
||||
initData.initialScrollProgress = progress;
|
||||
});
|
||||
|
||||
host.onMessage('execCommand', (_event, data) => {
|
||||
const target = getActiveFrame();
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
target.contentDocument.execCommand(data);
|
||||
});
|
||||
|
||||
trackFocus({
|
||||
onFocus: () => host.postMessage('did-focus'),
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor
|
||||
import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
|
||||
import { webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory';
|
||||
import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from '../browser/webviewCommands';
|
||||
import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand, SelectAllWebviewEditorCommand } from '../browser/webviewCommands';
|
||||
import { WebviewEditor } from './webviewEditor';
|
||||
import { WebviewInput } from './webviewEditorInput';
|
||||
import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService';
|
||||
@@ -50,6 +50,11 @@ registerAction2(class extends WebViewEditorFindPreviousCommand {
|
||||
constructor() { super(webviewActiveContextKeyExpr); }
|
||||
});
|
||||
|
||||
registerAction2(class extends SelectAllWebviewEditorCommand {
|
||||
constructor() { super(webviewActiveContextKeyExpr); }
|
||||
});
|
||||
|
||||
|
||||
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
actionRegistry.registerWorkbenchAction(
|
||||
SyncActionDescriptor.create(ReloadWebviewAction, ReloadWebviewAction.ID, ReloadWebviewAction.LABEL),
|
||||
|
||||
@@ -93,6 +93,8 @@ export interface Webview extends IDisposable {
|
||||
hideFind(): void;
|
||||
runFindAction(previous: boolean): void;
|
||||
|
||||
selectAll(): void;
|
||||
|
||||
windowDidDragStart(): void;
|
||||
windowDidDragEnd(): void;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
|
||||
import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
|
||||
|
||||
export class ShowWebViewEditorFindWidgetAction extends Action2 {
|
||||
public static readonly ID = 'editor.action.webvieweditor.showFind';
|
||||
@@ -97,6 +98,29 @@ export class WebViewEditorFindPreviousCommand extends Action2 {
|
||||
getActiveWebviewEditor(accessor)?.find(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectAllWebviewEditorCommand extends Action2 {
|
||||
public static readonly ID = 'editor.action.webvieweditor.selectAll';
|
||||
public static readonly LABEL = nls.localize('editor.action.webvieweditor.selectAll', 'Select all');
|
||||
|
||||
constructor(contextKeyExpr: ContextKeyExpr) {
|
||||
const precondition = ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey));
|
||||
super({
|
||||
id: SelectAllWebviewEditorCommand.ID,
|
||||
title: SelectAllWebviewEditorCommand.LABEL,
|
||||
keybinding: {
|
||||
when: precondition,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_A,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, args: any): void {
|
||||
getActiveWebviewEditor(accessor)?.selectAll();
|
||||
}
|
||||
}
|
||||
|
||||
export class ReloadWebviewAction extends Action {
|
||||
static readonly ID = 'workbench.action.webview.reloadWebviewAction';
|
||||
static readonly LABEL = nls.localize('refreshWebviewLabel', "Reload Webviews");
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
|
||||
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -71,31 +71,33 @@ export class WebviewEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
public showFind() {
|
||||
this.withWebview(webview => {
|
||||
webview.showFind();
|
||||
if (this.webview) {
|
||||
this.webview.showFind();
|
||||
this._findWidgetVisible.set(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public hideFind() {
|
||||
this._findWidgetVisible.reset();
|
||||
this.withWebview(webview => webview.hideFind());
|
||||
this.webview?.hideFind();
|
||||
}
|
||||
|
||||
public find(previous: boolean) {
|
||||
this.withWebview(webview => {
|
||||
webview.runFindAction(previous);
|
||||
});
|
||||
this.webview?.runFindAction(previous);
|
||||
}
|
||||
|
||||
public selectAll() {
|
||||
this.webview?.selectAll();
|
||||
}
|
||||
|
||||
public reload() {
|
||||
this.withWebview(webview => webview.reload());
|
||||
this.webview?.reload();
|
||||
}
|
||||
|
||||
public layout(dimension: DOM.Dimension): void {
|
||||
this._dimension = dimension;
|
||||
if (this.input && this.input instanceof WebviewInput) {
|
||||
this.synchronizeWebviewContainerDimensions(this.input.webview, dimension);
|
||||
if (this.webview) {
|
||||
this.synchronizeWebviewContainerDimensions(this.webview, dimension);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,22 +111,19 @@ export class WebviewEditor extends BaseEditor {
|
||||
}
|
||||
});
|
||||
}
|
||||
this.withWebview(webview => webview.focus());
|
||||
this.webview?.focus();
|
||||
}
|
||||
|
||||
public withWebview(f: (element: Webview) => void): void {
|
||||
if (this.input && this.input instanceof WebviewInput) {
|
||||
f(this.input.webview);
|
||||
}
|
||||
public get webview(): WebviewEditorOverlay | undefined {
|
||||
return this.input instanceof WebviewInput ? this.input.webview : undefined;
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
|
||||
if (this.input instanceof WebviewInput) {
|
||||
const webview = this.input.webview;
|
||||
if (this.input instanceof WebviewInput && this.webview) {
|
||||
if (visible) {
|
||||
webview.claim(this);
|
||||
this.webview.claim(this);
|
||||
} else {
|
||||
webview.release(this);
|
||||
this.webview.release(this);
|
||||
}
|
||||
this.claimWebview(this.input);
|
||||
}
|
||||
@@ -132,8 +131,8 @@ export class WebviewEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
public clearInput() {
|
||||
if (this.input && this.input instanceof WebviewInput) {
|
||||
this.input.webview.release(this);
|
||||
if (this.webview) {
|
||||
this.webview.release(this);
|
||||
this._webviewVisibleDisposables.clear();
|
||||
}
|
||||
|
||||
@@ -145,8 +144,8 @@ export class WebviewEditor extends BaseEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.input && this.input instanceof WebviewInput) {
|
||||
this.input.webview.release(this);
|
||||
if (this.webview) {
|
||||
this.webview.release(this);
|
||||
}
|
||||
|
||||
await super.setInput(input, options, token);
|
||||
@@ -189,15 +188,11 @@ export class WebviewEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_START, () => {
|
||||
if (this.input instanceof WebviewInput) {
|
||||
this.input.webview.windowDidDragStart();
|
||||
}
|
||||
this.webview?.windowDidDragStart();
|
||||
}));
|
||||
|
||||
const onDragEnd = () => {
|
||||
if (this.input instanceof WebviewInput) {
|
||||
this.input.webview.windowDidDragEnd();
|
||||
}
|
||||
this.webview?.windowDidDragEnd();
|
||||
};
|
||||
this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_END, onDragEnd));
|
||||
this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.MOUSE_MOVE, currentEvent => {
|
||||
|
||||
@@ -26,8 +26,6 @@ actionRegistry.registerWorkbenchAction(
|
||||
function registerWebViewCommands(editorId: string): void {
|
||||
const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */)!;
|
||||
|
||||
registerAction2(class extends webviewCommands.SelectAllWebviewEditorCommand { constructor() { super(contextKeyExpr); } });
|
||||
|
||||
// These commands are only needed on MacOS where we have to disable the menu bar commands
|
||||
if (isMacintosh) {
|
||||
registerAction2(class extends webviewCommands.CopyWebviewEditorCommand { constructor() { super(contextKeyExpr); } });
|
||||
|
||||
@@ -38,28 +38,6 @@ export class OpenWebviewDeveloperToolsAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectAllWebviewEditorCommand extends Action2 {
|
||||
public static readonly ID = 'editor.action.webvieweditor.selectAll';
|
||||
public static readonly LABEL = nls.localize('editor.action.webvieweditor.selectAll', 'Select all');
|
||||
|
||||
constructor(contextKeyExpr: ContextKeyExpr) {
|
||||
const precondition = ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey));
|
||||
super({
|
||||
id: SelectAllWebviewEditorCommand.ID,
|
||||
title: SelectAllWebviewEditorCommand.LABEL,
|
||||
keybinding: {
|
||||
when: precondition,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_A,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, args: any): void {
|
||||
withActiveWebviewBasedWebview(accessor, webview => webview.selectAll());
|
||||
}
|
||||
}
|
||||
|
||||
export class CopyWebviewEditorCommand extends Action2 {
|
||||
public static readonly ID = 'editor.action.webvieweditor.copy';
|
||||
public static readonly LABEL = nls.localize('editor.action.webvieweditor.copy', "Copy2");
|
||||
@@ -77,7 +55,7 @@ export class CopyWebviewEditorCommand extends Action2 {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor): void {
|
||||
withActiveWebviewBasedWebview(accessor, webview => webview.copy());
|
||||
getActiveWebviewBasedWebview(accessor)?.copy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +76,7 @@ export class PasteWebviewEditorCommand extends Action2 {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor): void {
|
||||
withActiveWebviewBasedWebview(accessor, webview => webview.paste());
|
||||
getActiveWebviewBasedWebview(accessor)?.paste();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +97,7 @@ export class CutWebviewEditorCommand extends Action2 {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor): void {
|
||||
withActiveWebviewBasedWebview(accessor, webview => webview.cut());
|
||||
getActiveWebviewBasedWebview(accessor)?.cut();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +118,7 @@ export class UndoWebviewEditorCommand extends Action2 {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, args: any): void {
|
||||
withActiveWebviewBasedWebview(accessor, webview => webview.undo());
|
||||
getActiveWebviewBasedWebview(accessor)?.undo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,22 +141,24 @@ export class RedoWebviewEditorCommand extends Action2 {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, args: any): void {
|
||||
withActiveWebviewBasedWebview(accessor, webview => webview.redo());
|
||||
getActiveWebviewBasedWebview(accessor)?.redo();
|
||||
}
|
||||
}
|
||||
|
||||
function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview: ElectronWebviewBasedWebview) => void): void {
|
||||
const webViewEditor = getActiveWebviewEditor(accessor);
|
||||
if (webViewEditor) {
|
||||
webViewEditor.withWebview(webview => {
|
||||
if (webview instanceof ElectronWebviewBasedWebview) {
|
||||
f(webview);
|
||||
} else if ((webview as WebviewEditorOverlay).getInnerWebview) {
|
||||
const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview();
|
||||
if (innerWebview instanceof ElectronWebviewBasedWebview) {
|
||||
f(innerWebview);
|
||||
}
|
||||
}
|
||||
});
|
||||
function getActiveWebviewBasedWebview(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined {
|
||||
const webview = getActiveWebviewEditor(accessor)?.webview;
|
||||
if (!webview) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (webview instanceof ElectronWebviewBasedWebview) {
|
||||
return webview;
|
||||
} else if ((webview as WebviewEditorOverlay).getInnerWebview) {
|
||||
const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview();
|
||||
if (innerWebview instanceof ElectronWebviewBasedWebview) {
|
||||
return innerWebview;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench
|
||||
import { IElectronService } from 'vs/platform/electron/node/electron';
|
||||
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
|
||||
import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; // {{SQL CARBON EDIT}} add import
|
||||
|
||||
@@ -333,8 +334,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten
|
||||
(function registerJSONSchemas(): void {
|
||||
const argvDefinitionFileSchemaId = 'vscode://schemas/argv';
|
||||
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
|
||||
|
||||
jsonRegistry.registerSchema(argvDefinitionFileSchemaId, {
|
||||
const schema: IJSONSchema = {
|
||||
id: argvDefinitionFileSchemaId,
|
||||
allowComments: true,
|
||||
allowTrailingCommas: true,
|
||||
@@ -355,5 +355,13 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten
|
||||
description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.')
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
if (isLinux) {
|
||||
schema.properties!['force-renderer-accessibility'] = {
|
||||
type: 'boolean',
|
||||
description: nls.localize('argv.force-renderer-accessibility', 'Forces the renderer to be accessible. ONLY change this if you are using a screen reader on Linux. On other platforms the renderer will automatically be accessible. This flag is automatically set if you have editor.accessibilitySupport: on.'),
|
||||
};
|
||||
}
|
||||
|
||||
jsonRegistry.registerSchema(argvDefinitionFileSchemaId, schema);
|
||||
})();
|
||||
|
||||
@@ -4,13 +4,18 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { isWindows, isLinux } from 'vs/base/common/platform';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
interface AccessibilityMetrics {
|
||||
enabled: boolean;
|
||||
@@ -65,3 +70,22 @@ export class NodeAccessibilityService extends AccessibilityService implements IA
|
||||
}
|
||||
|
||||
registerSingleton(IAccessibilityService, NodeAccessibilityService, true);
|
||||
|
||||
// On linux we do not automatically detect that a screen reader is detected, thus we have to implicitly notify the renderer to enable accessibility when user configures it in settings
|
||||
class LinuxAccessibilityContribution implements IWorkbenchContribution {
|
||||
constructor(
|
||||
@IJSONEditingService jsonEditingService: IJSONEditingService,
|
||||
@IAccessibilityService accessibilityService: AccessibilityService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService
|
||||
) {
|
||||
accessibilityService.onDidChangeScreenReaderOptimized(async () => {
|
||||
if (accessibilityService.isScreenReaderOptimized()) {
|
||||
await jsonEditingService.write(environmentService.argvResource, [{ key: 'force-renderer-accessibility', value: true }], true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isLinux) {
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LinuxAccessibilityContribution, LifecyclePhase.Ready);
|
||||
}
|
||||
|
||||
@@ -95,6 +95,10 @@ export class BrowserHostService extends Disposable implements IHostService {
|
||||
return document.hasFocus();
|
||||
}
|
||||
|
||||
async hadLastFocus(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async focus(): Promise<void> {
|
||||
window.focus();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ export interface IHostService {
|
||||
*/
|
||||
readonly hasFocus: boolean;
|
||||
|
||||
/**
|
||||
* Find out if the window had the last focus.
|
||||
*/
|
||||
hadLastFocus(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Attempt to bring the window to the foreground and focus it.
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,16 @@ export class DesktopHostService extends Disposable implements IHostService {
|
||||
return document.hasFocus();
|
||||
}
|
||||
|
||||
async hadLastFocus(): Promise<boolean> {
|
||||
const activeWindowId = await this.electronService.getActiveWindowId();
|
||||
|
||||
if (typeof activeWindowId === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return activeWindowId === this.electronEnvironmentService.windowId;
|
||||
}
|
||||
|
||||
openWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
|
||||
openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void>;
|
||||
openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise<void> {
|
||||
|
||||
@@ -79,7 +79,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
private lastResolvedFileStat: IFileStatWithMetadata | undefined;
|
||||
|
||||
private readonly saveSequentializer = new TaskSequentializer();
|
||||
private lastSaveAttemptTime = 0;
|
||||
|
||||
private dirty = false;
|
||||
private inConflictMode = false;
|
||||
@@ -553,16 +552,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return false; // if model is in save conflict or error, do not save unless save reason is explicit
|
||||
}
|
||||
|
||||
// Actually do save and log
|
||||
this.logService.trace('[text file model] save() - enter', this.resource.toString());
|
||||
|
||||
await this.doSave(options);
|
||||
|
||||
this.logService.trace('[text file model] save() - exit', this.resource.toString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private doSave(options: ITextFileSaveOptions): Promise<void> {
|
||||
private async doSave(options: ITextFileSaveOptions): Promise<void> {
|
||||
if (typeof options.reason !== 'number') {
|
||||
options.reason = SaveReason.EXPLICIT;
|
||||
}
|
||||
@@ -587,7 +585,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
if (!options.force && !this.dirty) {
|
||||
this.logService.trace(`[text file model] doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource.toString());
|
||||
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Return if currently saving by storing this save request as the next save that should happen.
|
||||
@@ -618,26 +616,22 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
this.textEditorModel.pushStackElement();
|
||||
}
|
||||
|
||||
// A save participant can still change the model now and since we are so close to saving
|
||||
// we do not want to trigger another auto save or similar, so we block this
|
||||
// In addition we update our version right after in case it changed because of a model change
|
||||
//
|
||||
// Save participants can also be skipped through API.
|
||||
const saveParticipantCancellation = new CancellationTokenSource();
|
||||
let saveParticipantPromise: Promise<number> = Promise.resolve(versionId);
|
||||
if (this.isResolved() && this.textFileService.saveParticipant && !options.skipSaveParticipants) {
|
||||
const onCompleteOrError = () => {
|
||||
this.ignoreDirtyOnModelContentChange = false;
|
||||
|
||||
return this.versionId;
|
||||
};
|
||||
return (this.saveSequentializer as TaskSequentializer).setPending(versionId, (async () => { // {{SQL CARBON EDIT}} strict-null-checks
|
||||
|
||||
this.ignoreDirtyOnModelContentChange = true;
|
||||
saveParticipantPromise = this.textFileService.saveParticipant.participate(this, { reason: options.reason }, saveParticipantCancellation.token).then(onCompleteOrError, onCompleteOrError);
|
||||
}
|
||||
|
||||
// mark the save participant as current pending save operation
|
||||
return (this.saveSequentializer as TaskSequentializer).setPending(versionId, saveParticipantPromise.then(newVersionId => { // {{SQL CARBON EDIT}} strict-null-check
|
||||
// A save participant can still change the model now and since we are so close to saving
|
||||
// we do not want to trigger another auto save or similar, so we block this
|
||||
// In addition we update our version right after in case it changed because of a model change
|
||||
//
|
||||
// Save participants can also be skipped through API.
|
||||
if (this.isResolved() && this.textFileService.saveParticipant && !options.skipSaveParticipants) {
|
||||
try {
|
||||
await this.textFileService.saveParticipant.participate(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveParticipantCancellation.token);
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
// We have to protect against being disposed at this point. It could be that the save() operation
|
||||
// was triggerd followed by a dispose() operation right after without waiting. Typically we cannot
|
||||
@@ -661,32 +655,39 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// - the model is not dirty (otherwise we know there are changed which needs to go to the file)
|
||||
// - the model is not in orphan mode (because in that case we know the file does not exist on disk)
|
||||
// - the model version did not change due to save participants running
|
||||
if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === newVersionId) {
|
||||
return this.doTouch(newVersionId, options.reason);
|
||||
if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === this.versionId) {
|
||||
return this.doTouch(this.versionId, options.reason);
|
||||
}
|
||||
|
||||
// update versionId with its new value (if pre-save changes happened)
|
||||
versionId = newVersionId;
|
||||
versionId = this.versionId;
|
||||
|
||||
// Clear error flag since we are trying to save again
|
||||
this.inErrorMode = false;
|
||||
|
||||
// Remember when this model was saved last
|
||||
this.lastSaveAttemptTime = Date.now();
|
||||
|
||||
// Save to Disk
|
||||
// mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering)
|
||||
// Save to Disk. We mark the save operation as currently pending with
|
||||
// the latest versionId because it might have changed from a save
|
||||
// participant triggering
|
||||
this.logService.trace(`[text file model] doSave(${versionId}) - before write()`, this.resource.toString());
|
||||
const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat);
|
||||
return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), {
|
||||
overwriteReadonly: options.overwriteReadonly,
|
||||
overwriteEncoding: options.overwriteEncoding,
|
||||
mtime: lastResolvedFileStat.mtime,
|
||||
encoding: this.getEncoding(),
|
||||
etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, this.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag,
|
||||
writeElevated: options.writeElevated
|
||||
}).then(stat => this.handleSaveSuccess(stat, versionId, options), error => this.handleSaveError(error, versionId, options)));
|
||||
}), () => saveParticipantCancellation.cancel());
|
||||
const textFileEdiorModel = this;
|
||||
return this.saveSequentializer.setPending(versionId, (async () => {
|
||||
try {
|
||||
const stat = await this.textFileService.write(lastResolvedFileStat.resource, textFileEdiorModel.createSnapshot(), {
|
||||
overwriteReadonly: options.overwriteReadonly,
|
||||
overwriteEncoding: options.overwriteEncoding,
|
||||
mtime: lastResolvedFileStat.mtime,
|
||||
encoding: this.getEncoding(),
|
||||
etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, textFileEdiorModel.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag,
|
||||
writeElevated: options.writeElevated
|
||||
});
|
||||
|
||||
this.handleSaveSuccess(stat, versionId, options);
|
||||
} catch (error) {
|
||||
this.handleSaveError(error, versionId, options);
|
||||
}
|
||||
})());
|
||||
})(), () => saveParticipantCancellation.cancel());
|
||||
}
|
||||
|
||||
private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveOptions): void {
|
||||
@@ -733,19 +734,24 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
private doTouch(this: TextFileEditorModel & IResolvedTextFileEditorModel, versionId: number, reason: SaveReason): Promise<void> {
|
||||
const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat);
|
||||
return this.saveSequentializer.setPending(versionId, this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), {
|
||||
mtime: lastResolvedFileStat.mtime,
|
||||
encoding: this.getEncoding(),
|
||||
etag: lastResolvedFileStat.etag
|
||||
}).then(stat => {
|
||||
|
||||
// Updated resolved stat with updated stat since touching it might have changed mtime
|
||||
this.updateLastResolvedFileStat(stat);
|
||||
return this.saveSequentializer.setPending(versionId, (async () => {
|
||||
try {
|
||||
const stat = await this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), {
|
||||
mtime: lastResolvedFileStat.mtime,
|
||||
encoding: this.getEncoding(),
|
||||
etag: lastResolvedFileStat.etag
|
||||
});
|
||||
|
||||
// Emit File Saved Event
|
||||
this._onDidSave.fire(reason);
|
||||
// Updated resolved stat with updated stat since touching it might have changed mtime
|
||||
this.updateLastResolvedFileStat(stat);
|
||||
|
||||
}, error => onUnexpectedError(error) /* just log any error but do not notify the user since the file was not dirty */));
|
||||
// Emit File Saved Event
|
||||
this._onDidSave.fire(reason);
|
||||
} catch (error) {
|
||||
onUnexpectedError(error); // just log any error but do not notify the user since the file was not dirty
|
||||
}
|
||||
})());
|
||||
}
|
||||
|
||||
private updateSavedVersionId(): void {
|
||||
@@ -776,10 +782,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
//#endregion
|
||||
|
||||
getLastSaveAttemptTime(): number {
|
||||
return this.lastSaveAttemptTime;
|
||||
}
|
||||
|
||||
hasState(state: ModelState): boolean {
|
||||
switch (state) {
|
||||
case ModelState.CONFLICT:
|
||||
|
||||
@@ -84,7 +84,13 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
// to have a size of 2 (1 running load and 1 queued load).
|
||||
const queue = this.modelLoadQueue.queueFor(model.resource);
|
||||
if (queue.size <= 1) {
|
||||
queue.queue(() => model.load().then(undefined, onUnexpectedError));
|
||||
queue.queue(async () => {
|
||||
try {
|
||||
await model.load();
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,6 @@ suite('Files - TextFileEditorModel', () => {
|
||||
|
||||
await pendingSave;
|
||||
|
||||
assert.ok(model.getLastSaveAttemptTime() <= Date.now());
|
||||
assert.ok(model.hasState(ModelState.SAVED));
|
||||
assert.ok(!model.isDirty());
|
||||
assert.ok(savedEvent);
|
||||
@@ -488,8 +487,6 @@ suite('Files - TextFileEditorModel', () => {
|
||||
assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt')));
|
||||
assert.ok(assertIsDefined(model1.getStat()).mtime > m1Mtime);
|
||||
assert.ok(assertIsDefined(model2.getStat()).mtime > m2Mtime);
|
||||
assert.ok(model1.getLastSaveAttemptTime() > m1Mtime);
|
||||
assert.ok(model2.getLastSaveAttemptTime() > m2Mtime);
|
||||
|
||||
model1.dispose();
|
||||
model2.dispose();
|
||||
@@ -506,12 +503,11 @@ suite('Files - TextFileEditorModel', () => {
|
||||
});
|
||||
|
||||
accessor.textFileService.saveParticipant = {
|
||||
participate: model => {
|
||||
participate: async model => {
|
||||
assert.ok(model.isDirty());
|
||||
model.textEditorModel!.setValue('bar');
|
||||
assert.ok(model.isDirty());
|
||||
eventCounter++;
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -545,8 +541,8 @@ suite('Files - TextFileEditorModel', () => {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
|
||||
accessor.textFileService.saveParticipant = {
|
||||
participate: (model) => {
|
||||
return Promise.reject(new Error('boom'));
|
||||
participate: async model => {
|
||||
new Error('boom');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -563,10 +559,9 @@ suite('Files - TextFileEditorModel', () => {
|
||||
let participations: boolean[] = [];
|
||||
|
||||
accessor.textFileService.saveParticipant = {
|
||||
participate: (model) => {
|
||||
return timeout(10).then(() => {
|
||||
participations.push(true);
|
||||
});
|
||||
participate: async model => {
|
||||
await timeout(10);
|
||||
participations.push(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -586,4 +581,49 @@ suite('Files - TextFileEditorModel', () => {
|
||||
assert.equal(participations.length, 1);
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
test('Save Participant, calling save from within is unsupported but does not explode (sync save)', async function () {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
|
||||
await testSaveFromSaveParticipant(model, false);
|
||||
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
test('Save Participant, calling save from within is unsupported but does not explode (async save)', async function () {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
|
||||
await testSaveFromSaveParticipant(model, true);
|
||||
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
async function testSaveFromSaveParticipant(model: TextFileEditorModel, async: boolean): Promise<void> {
|
||||
let savePromise: Promise<boolean>;
|
||||
let breakLoop = false;
|
||||
|
||||
accessor.textFileService.saveParticipant = {
|
||||
participate: async model => {
|
||||
if (breakLoop) {
|
||||
return;
|
||||
}
|
||||
|
||||
breakLoop = true;
|
||||
|
||||
if (async) {
|
||||
await timeout(10);
|
||||
}
|
||||
const newSavePromise = model.save();
|
||||
|
||||
// assert that this is the same promise as the outer one
|
||||
assert.equal(savePromise, newSavePromise);
|
||||
}
|
||||
};
|
||||
|
||||
await model.load();
|
||||
model.textEditorModel!.setValue('foo');
|
||||
|
||||
savePromise = model.save();
|
||||
await savePromise;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -537,6 +537,22 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
|
||||
}
|
||||
}
|
||||
|
||||
// If a value is not present in the cache, it must be reset to default
|
||||
this.viewContainersRegistry.all.forEach(viewContainer => {
|
||||
const viewDescriptorCollection = this.getViewDescriptors(viewContainer);
|
||||
viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => {
|
||||
if (!newCachedPositions.has(viewDescriptor.id)) {
|
||||
const currentContainer = this.getViewContainer(viewDescriptor.id);
|
||||
const defaultContainer = this.getDefaultContainer(viewDescriptor.id);
|
||||
if (currentContainer && defaultContainer && currentContainer !== defaultContainer) {
|
||||
this.moveViews([viewDescriptor], currentContainer, defaultContainer);
|
||||
}
|
||||
|
||||
this.cachedViewInfo.delete(viewDescriptor.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.cachedViewInfo = this.getCachedViewPositions();
|
||||
}
|
||||
}
|
||||
@@ -571,6 +587,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
|
||||
});
|
||||
});
|
||||
|
||||
// Do no save default positions to the cache
|
||||
// so that default changes can be recognized
|
||||
// https://github.com/microsoft/vscode/issues/90414
|
||||
for (const [viewId, containerInfo] of this.cachedViewInfo) {
|
||||
const defaultContainer = this.getDefaultContainer(viewId);
|
||||
if (defaultContainer?.id === containerInfo.containerId) {
|
||||
this.cachedViewInfo.delete(viewId);
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedViewPositionsValue = JSON.stringify([...this.cachedViewInfo]);
|
||||
}
|
||||
|
||||
|
||||
@@ -865,6 +865,7 @@ export class TestHostService implements IHostService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
readonly hasFocus: boolean = true;
|
||||
async hadLastFocus(): Promise<boolean> { return true; }
|
||||
readonly onDidChangeFocus: Event<boolean> = Event.None;
|
||||
|
||||
async restart(): Promise<void> { }
|
||||
|
||||
@@ -80,17 +80,29 @@ interface IApplicationLink {
|
||||
uri: URI;
|
||||
|
||||
/**
|
||||
* A label for the link to display.
|
||||
* A label for the application link to display.
|
||||
*/
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface IApplicationLinkProvider {
|
||||
(): IApplicationLink[] | undefined
|
||||
interface ICommand {
|
||||
|
||||
/**
|
||||
* An identifier for the command. Commands can be executed from extensions
|
||||
* using the `vscode.commands.executeCommand` API using that command ID.
|
||||
*/
|
||||
id: string,
|
||||
|
||||
/**
|
||||
* A function that is being executed with any arguments passed over.
|
||||
*/
|
||||
handler: (...args: any[]) => void;
|
||||
}
|
||||
|
||||
interface IWorkbenchConstructionOptions {
|
||||
|
||||
//#region Connection related configuration
|
||||
|
||||
/**
|
||||
* The remote authority is the IP:PORT from where the workbench is served
|
||||
* from. It is for example being used for the websocket connections as address.
|
||||
@@ -108,6 +120,36 @@ interface IWorkbenchConstructionOptions {
|
||||
*/
|
||||
readonly webviewEndpoint?: string;
|
||||
|
||||
/**
|
||||
* A factory for web sockets.
|
||||
*/
|
||||
readonly webSocketFactory?: IWebSocketFactory;
|
||||
|
||||
/**
|
||||
* A provider for resource URIs.
|
||||
*/
|
||||
readonly resourceUriProvider?: IResourceUriProvider;
|
||||
|
||||
/**
|
||||
* Resolves an external uri before it is opened.
|
||||
*/
|
||||
readonly resolveExternalUri?: IExternalUriResolver;
|
||||
|
||||
/**
|
||||
* Support for creating tunnels.
|
||||
*/
|
||||
readonly tunnelFactory?: ITunnelFactory;
|
||||
|
||||
/**
|
||||
* Support for filtering candidate ports
|
||||
*/
|
||||
readonly showCandidate?: IShowCandidate;
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Workbench configuration
|
||||
|
||||
/**
|
||||
* A handler for opening workspaces and providing the initial workspace.
|
||||
*/
|
||||
@@ -119,16 +161,6 @@ interface IWorkbenchConstructionOptions {
|
||||
*/
|
||||
userDataProvider?: IFileSystemProvider;
|
||||
|
||||
/**
|
||||
* A factory for web sockets.
|
||||
*/
|
||||
readonly webSocketFactory?: IWebSocketFactory;
|
||||
|
||||
/**
|
||||
* A provider for resource URIs.
|
||||
*/
|
||||
readonly resourceUriProvider?: IResourceUriProvider;
|
||||
|
||||
/**
|
||||
* The credentials provider to store and retrieve secrets.
|
||||
*/
|
||||
@@ -154,21 +186,6 @@ interface IWorkbenchConstructionOptions {
|
||||
*/
|
||||
readonly resolveCommonTelemetryProperties?: ICommontTelemetryPropertiesResolver;
|
||||
|
||||
/**
|
||||
* Resolves an external uri before it is opened.
|
||||
*/
|
||||
readonly resolveExternalUri?: IExternalUriResolver;
|
||||
|
||||
/**
|
||||
* Support for creating tunnels.
|
||||
*/
|
||||
readonly tunnelFactory?: ITunnelFactory;
|
||||
|
||||
/**
|
||||
* Support for filtering candidate ports
|
||||
*/
|
||||
readonly showCandidate?: IShowCandidate;
|
||||
|
||||
/**
|
||||
* Provide entries for the "Open in Desktop" feature.
|
||||
*
|
||||
@@ -179,7 +196,20 @@ interface IWorkbenchConstructionOptions {
|
||||
* - N elements: there will be a "Open in Desktop" affordance that opens
|
||||
* a picker on click to select which application to open.
|
||||
*/
|
||||
readonly applicationLinkProvider?: IApplicationLinkProvider;
|
||||
readonly applicationLinks?: readonly IApplicationLink[];
|
||||
|
||||
/**
|
||||
* A set of optional commands that should be registered with the commands
|
||||
* registry.
|
||||
*
|
||||
* Note: commands can be called from extensions if the identifier is known!
|
||||
*/
|
||||
readonly commands?: readonly ICommand[];
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Diagnostics
|
||||
|
||||
/**
|
||||
* Current logging level. Default is `LogLevel.Info`.
|
||||
@@ -190,20 +220,8 @@ interface IWorkbenchConstructionOptions {
|
||||
* Whether to enable the smoke test driver.
|
||||
*/
|
||||
readonly driver?: boolean;
|
||||
}
|
||||
|
||||
interface ICommandHandler {
|
||||
(...args: any[]): void;
|
||||
}
|
||||
|
||||
interface IWorkbench {
|
||||
|
||||
/**
|
||||
* Register a command with the provided identifier and handler with
|
||||
* the workbench. The command can be called from extensions using the
|
||||
* `vscode.commands.executeCommand` API.
|
||||
*/
|
||||
registerCommand(id: string, command: ICommandHandler): IDisposable;
|
||||
//#endregion
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,24 +229,22 @@ interface IWorkbench {
|
||||
*
|
||||
* @param domElement the container to create the workbench in
|
||||
* @param options for setting up the workbench
|
||||
*
|
||||
* @returns the workbench facade with additional methods to call on.
|
||||
*/
|
||||
async function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise<IWorkbench> {
|
||||
async function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise<void> {
|
||||
|
||||
// Startup workbench
|
||||
await main(domElement, options);
|
||||
|
||||
// Return facade
|
||||
return {
|
||||
registerCommand: (id: string, command: ICommandHandler): IDisposable => {
|
||||
return CommandsRegistry.registerCommand(id, (accessor, ...args: any[]) => {
|
||||
// Register commands if any
|
||||
if (Array.isArray(options.commands)) {
|
||||
for (const command of options.commands) {
|
||||
CommandsRegistry.registerCommand(command.id, (accessor, ...args: any[]) => {
|
||||
// we currently only pass on the arguments but not the accessor
|
||||
// to the command to reduce our exposure of internal API.
|
||||
command(...args);
|
||||
command.handler(...args);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -237,9 +253,6 @@ export {
|
||||
create,
|
||||
IWorkbenchConstructionOptions,
|
||||
|
||||
// Workbench Facade
|
||||
IWorkbench,
|
||||
ICommandHandler,
|
||||
|
||||
// Basic Types
|
||||
URI,
|
||||
@@ -291,5 +304,7 @@ export {
|
||||
|
||||
// Protocol Links
|
||||
IApplicationLink,
|
||||
IApplicationLinkProvider
|
||||
|
||||
// Commands
|
||||
ICommand
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user