Merge from vscode 1a81711a85e38ccf784110568ebf3784ab9094a5 (#9161)

* Merge from vscode 1a81711a85e38ccf784110568ebf3784ab9094a5

* small spacing fix
This commit is contained in:
Anthony Dresser
2020-02-15 00:43:09 -06:00
committed by GitHub
parent 74b89a0a85
commit 873c6a39fe
78 changed files with 1474 additions and 1011 deletions
+3
View File
@@ -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();
+47
View File
@@ -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) +
+20 -35
View File
@@ -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
});
})();
+33
View File
@@ -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
*/
+25
View File
@@ -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,
+3 -1
View File
@@ -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 */
+96 -3
View File
@@ -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 {
+106 -34
View File
@@ -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;
+2 -2
View File
@@ -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> { }
+70 -55
View File
@@ -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
};