mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-29 08:10:29 -04:00
Merge from vscode e5834d3280fcd04898efeac32b9cf1b893f9b127 (#9385)
* Merge from vscode e5834d3280fcd04898efeac32b9cf1b893f9b127 * distro
This commit is contained in:
@@ -25,7 +25,7 @@ export class MainThreadAuthenticationProvider {
|
||||
return {
|
||||
id: session.id,
|
||||
accountName: session.accountName,
|
||||
accessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id)
|
||||
getAccessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id)
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export class MainThreadAuthenticationProvider {
|
||||
return {
|
||||
id: session.id,
|
||||
accountName: session.accountName,
|
||||
accessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id)
|
||||
getAccessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id)
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -75,48 +75,52 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
async $getSessionsPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
|
||||
const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL);
|
||||
if (alwaysAllow) {
|
||||
return true;
|
||||
return alwaysAllow === 'true';
|
||||
}
|
||||
|
||||
const { choice } = await this.dialogService.show(
|
||||
const { choice, checkboxChecked } = await this.dialogService.show(
|
||||
Severity.Info,
|
||||
nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information from {1}.", extensionName, providerName),
|
||||
[nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow"), nls.localize('alwaysAllow', "Always Allow"),],
|
||||
{ cancelId: 0 }
|
||||
[nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")],
|
||||
{
|
||||
cancelId: 0,
|
||||
checkbox: {
|
||||
label: nls.localize('neverAgain', "Don't Show Again")
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
switch (choice) {
|
||||
case 1/** Allow */:
|
||||
return true;
|
||||
case 2 /** Always Allow */:
|
||||
this.storageService.store(`${extensionId}-${providerId}`, 'true', StorageScope.GLOBAL);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
const allow = choice === 1;
|
||||
if (checkboxChecked) {
|
||||
this.storageService.store(`${extensionId}-${providerId}`, allow ? 'true' : 'false', StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
return allow;
|
||||
}
|
||||
|
||||
async $loginPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
|
||||
const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL);
|
||||
if (alwaysAllow) {
|
||||
return true;
|
||||
return alwaysAllow === 'true';
|
||||
}
|
||||
|
||||
const { choice } = await this.dialogService.show(
|
||||
const { choice, checkboxChecked } = await this.dialogService.show(
|
||||
Severity.Info,
|
||||
nls.localize('confirmLogin', "The extension '{0}' wants to sign in using {1}.", extensionName, providerName),
|
||||
[nls.localize('cancel', "Cancel"), nls.localize('continue', "Continue"), nls.localize('neverAgain', "Don't Show Again")],
|
||||
{ cancelId: 0 }
|
||||
[nls.localize('cancel', "Cancel"), nls.localize('continue', "Continue")],
|
||||
{
|
||||
cancelId: 0,
|
||||
checkbox: {
|
||||
label: nls.localize('neverAgain', "Don't Show Again")
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
switch (choice) {
|
||||
case 1/** Allow */:
|
||||
return true;
|
||||
case 2 /** Always Allow */:
|
||||
this.storageService.store(`${extensionId}-${providerId}`, 'true', StorageScope.GLOBAL);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
const allow = choice === 1;
|
||||
if (checkboxChecked) {
|
||||
this.storageService.store(`${extensionId}-${providerId}`, allow ? 'true' : 'false', StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
return allow;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
this._register(_webviewWorkbenchService.registerResolver({
|
||||
canResolve: (webview: WebviewInput) => {
|
||||
if (webview instanceof CustomEditorInput) {
|
||||
extensionService.activateByEvent(`onWebviewEditor:${webview.viewType}`);
|
||||
extensionService.activateByEvent(`onCustomEditor:${webview.viewType}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -624,7 +624,7 @@ export interface ExtHostWebviewsShape {
|
||||
$onSave(resource: UriComponents, viewType: string): Promise<void>;
|
||||
$onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise<void>;
|
||||
|
||||
$backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<boolean>;
|
||||
$backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadUrlsShape extends IDisposable {
|
||||
|
||||
@@ -34,7 +34,7 @@ export class AuthenticationProviderWrapper implements vscode.AuthenticationProvi
|
||||
id: session.id,
|
||||
accountName: session.accountName,
|
||||
scopes: session.scopes,
|
||||
accessToken: async () => {
|
||||
getAccessToken: async () => {
|
||||
const isAllowed = await this._proxy.$getSessionsPrompt(
|
||||
this._provider.id,
|
||||
this.displayName,
|
||||
@@ -45,7 +45,7 @@ export class AuthenticationProviderWrapper implements vscode.AuthenticationProvi
|
||||
throw new Error('User did not consent to token access.');
|
||||
}
|
||||
|
||||
return session.accessToken();
|
||||
return session.getAccessToken();
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -60,7 +60,7 @@ export class AuthenticationProviderWrapper implements vscode.AuthenticationProvi
|
||||
return this._provider.login(scopes);
|
||||
}
|
||||
|
||||
logout(sessionId: string): Promise<void> {
|
||||
logout(sessionId: string): Thenable<void> {
|
||||
return this._provider.logout(sessionId);
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
const sessions = await authProvider.getSessions();
|
||||
const session = sessions.find(session => session.id === sessionId);
|
||||
if (session) {
|
||||
return session.accessToken();
|
||||
return session.getAccessToken();
|
||||
}
|
||||
|
||||
throw new Error(`Unable to find session with id: ${sessionId}`);
|
||||
|
||||
@@ -71,11 +71,17 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
scheme: scheme,
|
||||
onDidChange: undefined,
|
||||
async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) {
|
||||
timelineDisposables.clear();
|
||||
|
||||
// For now, only allow the caching of a single Uri
|
||||
if (internalOptions?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) {
|
||||
itemsBySourceByUriMap.clear();
|
||||
if (internalOptions?.cacheResults) {
|
||||
if (options.cursor === undefined) {
|
||||
timelineDisposables.clear();
|
||||
}
|
||||
|
||||
if (!itemsBySourceByUriMap.has(getUriKey(uri))) {
|
||||
itemsBySourceByUriMap.clear();
|
||||
}
|
||||
} else {
|
||||
timelineDisposables.clear();
|
||||
}
|
||||
|
||||
const result = await provider.provideTimeline(uri, options, token);
|
||||
|
||||
@@ -247,143 +247,160 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
|
||||
|
||||
type EditType = unknown;
|
||||
|
||||
class WebviewEditorCustomDocument extends Disposable implements vscode.CustomDocument {
|
||||
private _currentEditIndex: number = -1;
|
||||
private _savePoint: number = -1;
|
||||
private readonly _edits: Array<EditType> = [];
|
||||
class CustomDocument extends Disposable implements vscode.CustomDocument {
|
||||
|
||||
public userData: unknown;
|
||||
|
||||
public _capabilities?: vscode.CustomEditorCapabilities = undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadWebviewsShape,
|
||||
public readonly viewType: string,
|
||||
public readonly uri: vscode.Uri,
|
||||
) {
|
||||
super();
|
||||
public static create(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) {
|
||||
return Object.seal(new CustomDocument(proxy, viewType, uri));
|
||||
}
|
||||
|
||||
_setCapabilities(capabilities: vscode.CustomEditorCapabilities) {
|
||||
if (this._capabilities) {
|
||||
throw new Error('Capabilities already provided');
|
||||
}
|
||||
// Explicitly initialize all properties as we seal the object after creation!
|
||||
|
||||
this._capabilities = capabilities;
|
||||
capabilities.editing?.onDidEdit(edit => {
|
||||
this.pushEdit(edit, this);
|
||||
});
|
||||
#currentEditIndex: number = -1;
|
||||
#savePoint: number = -1;
|
||||
readonly #edits: Array<EditType> = [];
|
||||
|
||||
readonly #proxy: MainThreadWebviewsShape;
|
||||
readonly #viewType: string;
|
||||
readonly #uri: vscode.Uri;
|
||||
|
||||
#capabilities: vscode.CustomEditorCapabilities | undefined = undefined;
|
||||
|
||||
private constructor(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) {
|
||||
super();
|
||||
this.#proxy = proxy;
|
||||
this.#viewType = viewType;
|
||||
this.#uri = uri;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.#onDidDispose.fire();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
//#region Public API
|
||||
|
||||
#_onDidDispose = this._register(new Emitter<void>());
|
||||
public readonly onDidDispose = this.#_onDidDispose.event;
|
||||
public get viewType(): string { return this.#viewType; }
|
||||
|
||||
public get uri(): vscode.Uri { return this.#uri; }
|
||||
|
||||
#onDidDispose = this._register(new Emitter<void>());
|
||||
public readonly onDidDispose = this.#onDidDispose.event;
|
||||
|
||||
public userData: unknown = undefined;
|
||||
|
||||
//#endregion
|
||||
|
||||
dispose() {
|
||||
this.#_onDidDispose.fire();
|
||||
super.dispose();
|
||||
//#region Internal
|
||||
|
||||
/** @internal*/ _setCapabilities(capabilities: vscode.CustomEditorCapabilities) {
|
||||
if (this.#capabilities) {
|
||||
throw new Error('Capabilities already provided');
|
||||
}
|
||||
|
||||
this.#capabilities = capabilities;
|
||||
capabilities.editing?.onDidEdit(edit => {
|
||||
this.pushEdit(edit);
|
||||
});
|
||||
}
|
||||
|
||||
private pushEdit(edit: EditType, trigger: any) {
|
||||
this.spliceEdits(edit);
|
||||
|
||||
this._currentEditIndex = this._edits.length - 1;
|
||||
this.updateState();
|
||||
// this._onApplyEdit.fire({ edits: [edit], trigger });
|
||||
}
|
||||
|
||||
private updateState() {
|
||||
const dirty = this._edits.length > 0 && this._savePoint !== this._currentEditIndex;
|
||||
this._proxy.$onDidChangeCustomDocumentState(this.uri, this.viewType, { dirty });
|
||||
}
|
||||
|
||||
private spliceEdits(editToInsert?: EditType) {
|
||||
const start = this._currentEditIndex + 1;
|
||||
const toRemove = this._edits.length - this._currentEditIndex;
|
||||
|
||||
editToInsert
|
||||
? this._edits.splice(start, toRemove, editToInsert)
|
||||
: this._edits.splice(start, toRemove);
|
||||
}
|
||||
|
||||
revert() {
|
||||
/** @internal*/ _revert() {
|
||||
const editing = this.getEditingCapability();
|
||||
if (this._currentEditIndex === this._savePoint) {
|
||||
if (this.#currentEditIndex === this.#savePoint) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._currentEditIndex >= this._savePoint) {
|
||||
const editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex);
|
||||
if (this.#currentEditIndex >= this.#savePoint) {
|
||||
const editsToUndo = this.#edits.slice(this.#savePoint, this.#currentEditIndex);
|
||||
editing.undoEdits(editsToUndo.reverse());
|
||||
} else if (this._currentEditIndex < this._savePoint) {
|
||||
const editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint);
|
||||
} else if (this.#currentEditIndex < this.#savePoint) {
|
||||
const editsToRedo = this.#edits.slice(this.#currentEditIndex, this.#savePoint);
|
||||
editing.applyEdits(editsToRedo);
|
||||
}
|
||||
|
||||
this._currentEditIndex = this._savePoint;
|
||||
this.#currentEditIndex = this.#savePoint;
|
||||
this.spliceEdits();
|
||||
|
||||
this.updateState();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
/** @internal*/ _undo() {
|
||||
const editing = this.getEditingCapability();
|
||||
if (this._currentEditIndex < 0) {
|
||||
if (this.#currentEditIndex < 0) {
|
||||
// nothing to undo
|
||||
return;
|
||||
}
|
||||
|
||||
const undoneEdit = this._edits[this._currentEditIndex];
|
||||
--this._currentEditIndex;
|
||||
const undoneEdit = this.#edits[this.#currentEditIndex];
|
||||
--this.#currentEditIndex;
|
||||
editing.undoEdits([undoneEdit]);
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
redo() {
|
||||
/** @internal*/ _redo() {
|
||||
const editing = this.getEditingCapability();
|
||||
if (this._currentEditIndex >= this._edits.length - 1) {
|
||||
if (this.#currentEditIndex >= this.#edits.length - 1) {
|
||||
// nothing to redo
|
||||
return;
|
||||
}
|
||||
|
||||
++this._currentEditIndex;
|
||||
const redoneEdit = this._edits[this._currentEditIndex];
|
||||
++this.#currentEditIndex;
|
||||
const redoneEdit = this.#edits[this.#currentEditIndex];
|
||||
editing.applyEdits([redoneEdit]);
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
save() {
|
||||
/** @internal*/ _save() {
|
||||
return this.getEditingCapability().save();
|
||||
}
|
||||
|
||||
saveAs(target: vscode.Uri) {
|
||||
/** @internal*/ _saveAs(target: vscode.Uri) {
|
||||
return this.getEditingCapability().saveAs(target);
|
||||
}
|
||||
|
||||
backup(cancellation: CancellationToken) {
|
||||
/** @internal*/ _backup(cancellation: CancellationToken) {
|
||||
return this.getEditingCapability().backup(cancellation);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
private pushEdit(edit: EditType) {
|
||||
this.spliceEdits(edit);
|
||||
|
||||
this.#currentEditIndex = this.#edits.length - 1;
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
private updateState() {
|
||||
const dirty = this.#edits.length > 0 && this.#savePoint !== this.#currentEditIndex;
|
||||
this.#proxy.$onDidChangeCustomDocumentState(this.uri, this.viewType, { dirty });
|
||||
}
|
||||
|
||||
private spliceEdits(editToInsert?: EditType) {
|
||||
const start = this.#currentEditIndex + 1;
|
||||
const toRemove = this.#edits.length - this.#currentEditIndex;
|
||||
|
||||
editToInsert
|
||||
? this.#edits.splice(start, toRemove, editToInsert)
|
||||
: this.#edits.splice(start, toRemove);
|
||||
}
|
||||
|
||||
private getEditingCapability(): vscode.CustomEditorEditingCapability {
|
||||
if (!this._capabilities?.editing) {
|
||||
if (!this.#capabilities?.editing) {
|
||||
throw new Error('Document is not editable');
|
||||
}
|
||||
return this._capabilities.editing;
|
||||
return this.#capabilities.editing;
|
||||
}
|
||||
}
|
||||
|
||||
class WebviewDocumentStore {
|
||||
private readonly _documents = new Map<string, WebviewEditorCustomDocument>();
|
||||
private readonly _documents = new Map<string, CustomDocument>();
|
||||
|
||||
public get(viewType: string, resource: vscode.Uri): WebviewEditorCustomDocument | undefined {
|
||||
public get(viewType: string, resource: vscode.Uri): CustomDocument | undefined {
|
||||
return this._documents.get(this.key(viewType, resource));
|
||||
}
|
||||
|
||||
public add(document: WebviewEditorCustomDocument) {
|
||||
public add(document: CustomDocument) {
|
||||
const key = this.key(document.viewType, document.uri);
|
||||
if (this._documents.has(key)) {
|
||||
throw new Error(`Document already exists for viewType:${document.viewType} resource:${document.uri}`);
|
||||
@@ -391,7 +408,7 @@ class WebviewDocumentStore {
|
||||
this._documents.set(key, document);
|
||||
}
|
||||
|
||||
public delete(document: WebviewEditorCustomDocument) {
|
||||
public delete(document: CustomDocument) {
|
||||
const key = this.key(document.viewType, document.uri);
|
||||
this._documents.delete(key);
|
||||
}
|
||||
@@ -622,7 +639,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
}
|
||||
|
||||
const revivedResource = URI.revive(resource);
|
||||
const document = Object.seal(new WebviewEditorCustomDocument(this._proxy, viewType, revivedResource));
|
||||
const document = CustomDocument.create(this._proxy, viewType, revivedResource);
|
||||
const capabilities = await entry.provider.resolveCustomDocument(document);
|
||||
document._setCapabilities(capabilities);
|
||||
this._documents.add(document);
|
||||
@@ -687,39 +704,39 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
|
||||
async $undo(resourceComponents: UriComponents, viewType: string): Promise<void> {
|
||||
const document = this.getDocument(viewType, resourceComponents);
|
||||
document.undo();
|
||||
document._undo();
|
||||
}
|
||||
|
||||
async $redo(resourceComponents: UriComponents, viewType: string): Promise<void> {
|
||||
const document = this.getDocument(viewType, resourceComponents);
|
||||
document.redo();
|
||||
document._redo();
|
||||
}
|
||||
|
||||
async $revert(resourceComponents: UriComponents, viewType: string): Promise<void> {
|
||||
const document = this.getDocument(viewType, resourceComponents);
|
||||
document.revert();
|
||||
document._revert();
|
||||
}
|
||||
|
||||
async $onSave(resourceComponents: UriComponents, viewType: string): Promise<void> {
|
||||
const document = this.getDocument(viewType, resourceComponents);
|
||||
document.save();
|
||||
document._save();
|
||||
}
|
||||
|
||||
async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents): Promise<void> {
|
||||
const document = this.getDocument(viewType, resourceComponents);
|
||||
return document.saveAs(URI.revive(targetResource));
|
||||
return document._saveAs(URI.revive(targetResource));
|
||||
}
|
||||
|
||||
async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<boolean> {
|
||||
async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
|
||||
const document = this.getDocument(viewType, resourceComponents);
|
||||
return document.backup(cancellation);
|
||||
return document._backup(cancellation);
|
||||
}
|
||||
|
||||
private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined {
|
||||
return this._webviewPanels.get(handle);
|
||||
}
|
||||
|
||||
private getDocument(viewType: string, resource: UriComponents): WebviewEditorCustomDocument {
|
||||
private getDocument(viewType: string, resource: UriComponents): CustomDocument {
|
||||
const document = this._documents.get(viewType, URI.revive(resource));
|
||||
if (!document) {
|
||||
throw new Error('No webview editor custom document found');
|
||||
|
||||
@@ -25,9 +25,10 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo
|
||||
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, FocusedViewContext, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
|
||||
const viewCategory = nls.localize('view', "View");
|
||||
@@ -534,6 +535,7 @@ export class MoveFocusedViewAction extends Action {
|
||||
@IQuickInputService private quickInputService: IQuickInputService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IActivityBarService private activityBarService: IActivityBarService,
|
||||
@IViewletService private viewletService: IViewletService
|
||||
) {
|
||||
super(id, label);
|
||||
@@ -542,58 +544,64 @@ export class MoveFocusedViewAction extends Action {
|
||||
run(): Promise<void> {
|
||||
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
|
||||
const focusedView = FocusedViewContext.getValue(this.contextKeyService);
|
||||
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
|
||||
|
||||
if (focusedView === undefined || focusedView.trim() === '') {
|
||||
if (focusedViewId === undefined || focusedViewId.trim() === '') {
|
||||
this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused."));
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedView);
|
||||
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedViewId);
|
||||
if (!viewDescriptor || !viewDescriptor.canMoveView) {
|
||||
this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable {0}.", focusedView));
|
||||
this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable."));
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const quickPick = this.quickInputService.createQuickPick();
|
||||
quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a destination area for the view...");
|
||||
quickPick.autoFocusOnList = true;
|
||||
quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a Destination for the View");
|
||||
|
||||
quickPick.items = [
|
||||
{
|
||||
id: 'sidebar',
|
||||
label: nls.localize('sidebar', "Sidebar")
|
||||
},
|
||||
{
|
||||
id: 'panel',
|
||||
const pinnedViewlets = this.activityBarService.getPinnedViewletIds();
|
||||
const items: Array<IQuickPickItem | IQuickPickSeparator> = this.viewletService.getViewlets()
|
||||
.filter(viewlet => {
|
||||
if (viewlet.id === this.viewDescriptorService.getViewContainer(focusedViewId)!.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !viewContainerRegistry.get(viewlet.id)!.rejectAddedViews && pinnedViewlets.indexOf(viewlet.id) !== -1;
|
||||
})
|
||||
.map(viewlet => {
|
||||
return {
|
||||
id: viewlet.id,
|
||||
label: viewlet.name,
|
||||
};
|
||||
});
|
||||
|
||||
if (this.viewDescriptorService.getViewLocation(focusedViewId) !== ViewContainerLocation.Panel) {
|
||||
items.unshift({
|
||||
type: 'separator',
|
||||
label: nls.localize('sidebar', "Side Bar")
|
||||
});
|
||||
items.push({
|
||||
type: 'separator',
|
||||
label: nls.localize('panel', "Panel")
|
||||
}
|
||||
];
|
||||
});
|
||||
items.push({
|
||||
id: '_.panel.newcontainer',
|
||||
label: nls.localize('moveFocusedView.newContainerInPanel', "New Container in Panel"),
|
||||
});
|
||||
}
|
||||
|
||||
quickPick.items = items;
|
||||
|
||||
quickPick.onDidAccept(() => {
|
||||
const destination = quickPick.selectedItems[0];
|
||||
|
||||
if (destination.id === 'panel') {
|
||||
quickPick.hide();
|
||||
if (destination.id === '_.panel.newcontainer') {
|
||||
this.viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Panel);
|
||||
this.viewsService.openView(focusedView, true);
|
||||
|
||||
return;
|
||||
} else if (destination.id === 'sidebar') {
|
||||
quickPick.placeholder = nls.localize('moveFocusedView.selectDestinationContainer', "Select a destination view group...");
|
||||
quickPick.items = this.viewletService.getViewlets().map(viewlet => {
|
||||
return {
|
||||
id: viewlet.id,
|
||||
label: viewlet.name
|
||||
};
|
||||
});
|
||||
|
||||
return;
|
||||
this.viewsService.openView(focusedViewId, true);
|
||||
} else if (destination.id) {
|
||||
quickPick.hide();
|
||||
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], viewContainerRegistry.get(destination.id)!);
|
||||
this.viewsService.openView(focusedView, true);
|
||||
return;
|
||||
this.viewsService.openView(focusedViewId, true);
|
||||
}
|
||||
|
||||
quickPick.hide();
|
||||
@@ -605,7 +613,7 @@ export class MoveFocusedViewAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL), 'View: Move Focused View', viewCategory);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL), 'View: Move Focused View', viewCategory, FocusedViewContext.notEqualsTo(''));
|
||||
|
||||
|
||||
// --- Resize View
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar > .content > .composite-bar-excess {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar .menubar {
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
|
||||
@@ -17,13 +17,14 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { LocalSelectionTransfer, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
|
||||
export interface ICompositeBarItem {
|
||||
id: string;
|
||||
@@ -38,7 +39,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
constructor(
|
||||
private viewDescriptorService: IViewDescriptorService,
|
||||
private targetContainerLocation: ViewContainerLocation,
|
||||
private openComposite: (id: string, focus?: boolean) => void,
|
||||
private openComposite: (id: string, focus?: boolean) => Promise<IPaneComposite | undefined>,
|
||||
private moveComposite: (from: string, to: string) => void,
|
||||
private getVisibleCompositeIds: () => string[]
|
||||
) { }
|
||||
@@ -52,9 +53,14 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
if (targetCompositeId) {
|
||||
if (currentLocation !== this.targetContainerLocation && this.targetContainerLocation !== ViewContainerLocation.Panel) {
|
||||
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
|
||||
if (destinationContainer) {
|
||||
this.viewDescriptorService.moveViewsToContainer(this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView), destinationContainer);
|
||||
this.openComposite(targetCompositeId, true);
|
||||
if (destinationContainer && !destinationContainer.rejectAddedViews) {
|
||||
const viewsToMove = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView);
|
||||
this.viewDescriptorService.moveViewsToContainer(viewsToMove, destinationContainer);
|
||||
this.openComposite(targetCompositeId, true).then(composite => {
|
||||
if (composite && viewsToMove.length === 1) {
|
||||
composite.openView(viewsToMove[0].id, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.moveComposite(dragData.id, targetCompositeId);
|
||||
@@ -73,10 +79,14 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
if (viewDescriptor && viewDescriptor.canMoveView) {
|
||||
if (targetCompositeId) {
|
||||
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
|
||||
if (destinationContainer) {
|
||||
if (destinationContainer && !destinationContainer.rejectAddedViews) {
|
||||
if (this.targetContainerLocation === ViewContainerLocation.Sidebar) {
|
||||
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], destinationContainer);
|
||||
this.openComposite(targetCompositeId, true);
|
||||
this.openComposite(targetCompositeId, true).then(composite => {
|
||||
if (composite) {
|
||||
composite.openView(viewDescriptor.id, true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation);
|
||||
this.moveComposite(this.viewDescriptorService.getViewContainer(viewDescriptor.id)!.id, targetCompositeId);
|
||||
@@ -91,13 +101,25 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
this.moveComposite(newCompositeId, targetId);
|
||||
}
|
||||
|
||||
this.openComposite(newCompositeId, true);
|
||||
this.openComposite(newCompositeId, true).then(composite => {
|
||||
if (composite) {
|
||||
composite.openView(viewDescriptor.id, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDragEnter(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): boolean {
|
||||
return this.canDrop(data, targetCompositeId);
|
||||
}
|
||||
|
||||
onDragOver(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): boolean {
|
||||
return this.canDrop(data, targetCompositeId);
|
||||
}
|
||||
|
||||
private canDrop(data: CompositeDragAndDropData, targetCompositeId: string | undefined): boolean {
|
||||
const dragData = data.getData();
|
||||
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
|
||||
@@ -134,6 +156,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
if (this.targetContainerLocation === ViewContainerLocation.Sidebar) {
|
||||
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
|
||||
return !!destinationContainer &&
|
||||
!destinationContainer.rejectAddedViews &&
|
||||
this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.some(vd => vd.canMoveView);
|
||||
}
|
||||
// ... from sidebar to the panel
|
||||
@@ -155,10 +178,9 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
}
|
||||
|
||||
// ... into a destination
|
||||
return true;
|
||||
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
|
||||
return !!destinationContainer && !destinationContainer.rejectAddedViews;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +222,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
constructor(
|
||||
items: ICompositeBarItem[],
|
||||
private options: ICompositeBarOptions,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService
|
||||
) {
|
||||
@@ -228,6 +251,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
|
||||
create(parent: HTMLElement): HTMLElement {
|
||||
const actionBarDiv = parent.appendChild($('.composite-bar'));
|
||||
const excessDiv = parent.appendChild($('.composite-bar-excess'));
|
||||
|
||||
this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, {
|
||||
actionViewItemProvider: (action: IAction) => {
|
||||
@@ -254,58 +278,99 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
|
||||
|
||||
// Allow to drop at the end to move composites to the end
|
||||
this._register(addDisposableListener(parent, EventType.DROP, (e: DragEvent) => {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
EventHelper.stop(e, true);
|
||||
this._register(new DragAndDropObserver(excessDiv, {
|
||||
onDragOver: (e: DragEvent) => {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedCompositeId = data[0].id;
|
||||
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
|
||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedCompositeId = data[0].id;
|
||||
|
||||
this.options.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedViewId = data[0].id;
|
||||
this.compositeTransfer.clearData(DraggedViewIdentifier.prototype);
|
||||
|
||||
this.options.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), undefined, e);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(parent, EventType.DRAG_OVER, (e: DragEvent) => {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedCompositeId = data[0].id;
|
||||
|
||||
// Check if drop is allowed
|
||||
if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e)) {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
// Check if drop is allowed
|
||||
if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e)) {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
||||
EventHelper.stop(e, true);
|
||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedViewId = data[0].id;
|
||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedViewId = data[0].id;
|
||||
|
||||
// Check if drop is allowed
|
||||
if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), undefined, e)) {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
// Check if drop is allowed
|
||||
if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), undefined, e)) {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDragEnter: (e: DragEvent) => {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedCompositeId = data[0].id;
|
||||
|
||||
// Check if drop is allowed
|
||||
const validDropTarget = this.options.dndHandler.onDragEnter(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e);
|
||||
this.updateFromDragging(excessDiv, validDropTarget);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedViewId = data[0].id;
|
||||
|
||||
// Check if drop is allowed
|
||||
const validDropTarget = this.options.dndHandler.onDragEnter(new CompositeDragAndDropData('view', draggedViewId), undefined, e);
|
||||
this.updateFromDragging(excessDiv, validDropTarget);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDragLeave: (e: DragEvent) => {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) ||
|
||||
this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
||||
this.updateFromDragging(excessDiv, false);
|
||||
}
|
||||
},
|
||||
onDragEnd: (e: DragEvent) => {
|
||||
// no-op, will not be called
|
||||
},
|
||||
onDrop: (e: DragEvent) => {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedCompositeId = data[0].id;
|
||||
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
|
||||
|
||||
this.options.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e);
|
||||
this.updateFromDragging(excessDiv, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedViewId = data[0].id;
|
||||
this.compositeTransfer.clearData(DraggedViewIdentifier.prototype);
|
||||
|
||||
this.options.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), undefined, e);
|
||||
this.updateFromDragging(excessDiv, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
return actionBarDiv;
|
||||
@@ -410,6 +475,13 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
}
|
||||
}
|
||||
|
||||
private updateFromDragging(element: HTMLElement, isDragging: boolean): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
const dragBackground = this.options.colors(theme).dragAndDropBackground;
|
||||
|
||||
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : '';
|
||||
}
|
||||
|
||||
private resetActiveComposite(compositeId: string) {
|
||||
const defaultCompositeId = this.options.getDefaultCompositeId();
|
||||
|
||||
|
||||
@@ -544,14 +544,16 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
||||
if (Array.isArray(data) && data[0].id !== this.activity.id) {
|
||||
this.updateFromDragging(container, true);
|
||||
const validDropTarget = this.dndHandler.onDragEnter(new CompositeDragAndDropData('composite', data[0].id), this.activity.id, e);
|
||||
this.updateFromDragging(container, validDropTarget);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
||||
if (Array.isArray(data) && data[0].id !== this.activity.id) {
|
||||
this.updateFromDragging(container, true);
|
||||
const validDropTarget = this.dndHandler.onDragEnter(new CompositeDragAndDropData('view', data[0].id), this.activity.id, e);
|
||||
this.updateFromDragging(container, validDropTarget);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -616,6 +618,8 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
||||
if (Array.isArray(data)) {
|
||||
const draggedViewId = data[0].id;
|
||||
this.updateFromDragging(container, false);
|
||||
this.compositeTransfer.clearData(DraggedViewIdentifier.prototype);
|
||||
|
||||
this.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e);
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ class StateChange {
|
||||
encoding: boolean = false;
|
||||
EOL: boolean = false;
|
||||
tabFocusMode: boolean = false;
|
||||
columnSelectionMode: boolean = false;
|
||||
screenReaderMode: boolean = false;
|
||||
metadata: boolean = false;
|
||||
|
||||
@@ -157,6 +158,7 @@ class StateChange {
|
||||
this.encoding = this.encoding || other.encoding;
|
||||
this.EOL = this.EOL || other.EOL;
|
||||
this.tabFocusMode = this.tabFocusMode || other.tabFocusMode;
|
||||
this.columnSelectionMode = this.columnSelectionMode || other.columnSelectionMode;
|
||||
this.screenReaderMode = this.screenReaderMode || other.screenReaderMode;
|
||||
this.metadata = this.metadata || other.metadata;
|
||||
}
|
||||
@@ -168,21 +170,23 @@ class StateChange {
|
||||
|| this.encoding
|
||||
|| this.EOL
|
||||
|| this.tabFocusMode
|
||||
|| this.columnSelectionMode
|
||||
|| this.screenReaderMode
|
||||
|| this.metadata;
|
||||
}
|
||||
}
|
||||
|
||||
interface StateDelta {
|
||||
selectionStatus?: string;
|
||||
mode?: string;
|
||||
encoding?: string;
|
||||
EOL?: string;
|
||||
indentation?: string;
|
||||
tabFocusMode?: boolean;
|
||||
screenReaderMode?: boolean;
|
||||
metadata?: string | undefined;
|
||||
}
|
||||
type StateDelta = (
|
||||
{ type: 'selectionStatus'; selectionStatus: string | undefined; }
|
||||
| { type: 'mode'; mode: string | undefined; }
|
||||
| { type: 'encoding'; encoding: string | undefined; }
|
||||
| { type: 'EOL'; EOL: string | undefined; }
|
||||
| { type: 'indentation'; indentation: string | undefined; }
|
||||
| { type: 'tabFocusMode'; tabFocusMode: boolean; }
|
||||
| { type: 'columnSelectionMode'; columnSelectionMode: boolean; }
|
||||
| { type: 'screenReaderMode'; screenReaderMode: boolean; }
|
||||
| { type: 'metadata'; metadata: string | undefined; }
|
||||
);
|
||||
|
||||
class State {
|
||||
|
||||
@@ -204,6 +208,9 @@ class State {
|
||||
private _tabFocusMode: boolean | undefined;
|
||||
get tabFocusMode(): boolean | undefined { return this._tabFocusMode; }
|
||||
|
||||
private _columnSelectionMode: boolean | undefined;
|
||||
get columnSelectionMode(): boolean | undefined { return this._columnSelectionMode; }
|
||||
|
||||
private _screenReaderMode: boolean | undefined;
|
||||
get screenReaderMode(): boolean | undefined { return this._screenReaderMode; }
|
||||
|
||||
@@ -213,56 +220,63 @@ class State {
|
||||
update(update: StateDelta): StateChange {
|
||||
const change = new StateChange();
|
||||
|
||||
if ('selectionStatus' in update) {
|
||||
if (update.type === 'selectionStatus') {
|
||||
if (this._selectionStatus !== update.selectionStatus) {
|
||||
this._selectionStatus = update.selectionStatus;
|
||||
change.selectionStatus = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ('indentation' in update) {
|
||||
if (update.type === 'indentation') {
|
||||
if (this._indentation !== update.indentation) {
|
||||
this._indentation = update.indentation;
|
||||
change.indentation = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ('mode' in update) {
|
||||
if (update.type === 'mode') {
|
||||
if (this._mode !== update.mode) {
|
||||
this._mode = update.mode;
|
||||
change.mode = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ('encoding' in update) {
|
||||
if (update.type === 'encoding') {
|
||||
if (this._encoding !== update.encoding) {
|
||||
this._encoding = update.encoding;
|
||||
change.encoding = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ('EOL' in update) {
|
||||
if (update.type === 'EOL') {
|
||||
if (this._EOL !== update.EOL) {
|
||||
this._EOL = update.EOL;
|
||||
change.EOL = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ('tabFocusMode' in update) {
|
||||
if (update.type === 'tabFocusMode') {
|
||||
if (this._tabFocusMode !== update.tabFocusMode) {
|
||||
this._tabFocusMode = update.tabFocusMode;
|
||||
change.tabFocusMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ('screenReaderMode' in update) {
|
||||
if (update.type === 'columnSelectionMode') {
|
||||
if (this._columnSelectionMode !== update.columnSelectionMode) {
|
||||
this._columnSelectionMode = update.columnSelectionMode;
|
||||
change.columnSelectionMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (update.type === 'screenReaderMode') {
|
||||
if (this._screenReaderMode !== update.screenReaderMode) {
|
||||
this._screenReaderMode = update.screenReaderMode;
|
||||
change.screenReaderMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ('metadata' in update) {
|
||||
if (update.type === 'metadata') {
|
||||
if (this._metadata !== update.metadata) {
|
||||
this._metadata = update.metadata;
|
||||
change.metadata = true;
|
||||
@@ -282,6 +296,7 @@ const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF");
|
||||
|
||||
export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
private readonly tabFocusModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
private readonly columnSelectionModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
private readonly screenRedearModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
private readonly indentationElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
private readonly selectionElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
@@ -402,6 +417,22 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
private updateColumnSelectionModeElement(visible: boolean): void {
|
||||
if (visible) {
|
||||
if (!this.columnSelectionModeElement.value) {
|
||||
this.columnSelectionModeElement.value = this.statusbarService.addEntry({
|
||||
text: nls.localize('columnSelectionModeEnabled', "Column Selection"),
|
||||
tooltip: nls.localize('disableColumnSelectionMode', "Disable Column Selection Mode"),
|
||||
command: 'editor.action.toggleColumnSelection',
|
||||
backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND),
|
||||
color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND)
|
||||
}, 'status.editor.columnSelectionMode', nls.localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8);
|
||||
}
|
||||
} else {
|
||||
this.columnSelectionModeElement.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private updateScreenReaderModeElement(visible: boolean): void {
|
||||
if (visible) {
|
||||
if (!this.screenRedearModeElement.value) {
|
||||
@@ -544,6 +575,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private doRenderNow(changed: StateChange): void {
|
||||
this.updateTabFocusModeElement(!!this.state.tabFocusMode);
|
||||
this.updateColumnSelectionModeElement(!!this.state.columnSelectionMode);
|
||||
this.updateScreenReaderModeElement(!!this.state.screenReaderMode);
|
||||
this.updateIndentationElement(this.state.indentation);
|
||||
this.updateSelectionElement(this.state.selectionStatus && !this.state.screenReaderMode ? this.state.selectionStatus : undefined);
|
||||
@@ -583,6 +615,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const activeCodeEditor = activeControl ? withNullAsUndefined(getCodeEditor(activeControl.getControl())) : undefined;
|
||||
|
||||
// Update all states
|
||||
this.onColumnSelectionModeChange(activeCodeEditor);
|
||||
this.onScreenReaderModeChange(activeCodeEditor);
|
||||
this.onSelectionChange(activeCodeEditor);
|
||||
this.onModeChange(activeCodeEditor, activeInput);
|
||||
@@ -600,6 +633,9 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
// Hook Listener for Configuration changes
|
||||
this.activeEditorListeners.add(activeCodeEditor.onDidChangeConfiguration((event: ConfigurationChangedEvent) => {
|
||||
if (event.hasChanged(EditorOption.columnSelection)) {
|
||||
this.onColumnSelectionModeChange(activeCodeEditor);
|
||||
}
|
||||
if (event.hasChanged(EditorOption.accessibilitySupport)) {
|
||||
this.onScreenReaderModeChange(activeCodeEditor);
|
||||
}
|
||||
@@ -668,14 +704,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
private onModeChange(editorWidget: ICodeEditor | undefined, editorInput: IEditorInput | undefined): void {
|
||||
let info: StateDelta = { mode: undefined };
|
||||
let info: StateDelta = { type: 'mode', mode: undefined };
|
||||
|
||||
// We only support text based editors
|
||||
if (editorWidget && editorInput && toEditorWithModeSupport(editorInput)) {
|
||||
const textModel = editorWidget.getModel();
|
||||
if (textModel) {
|
||||
const modeId = textModel.getLanguageIdentifier().language;
|
||||
info = { mode: withNullAsUndefined(this.modeService.getLanguageName(modeId)) };
|
||||
info.mode = withNullAsUndefined(this.modeService.getLanguageName(modeId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,7 +719,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
private onIndentationChange(editorWidget: ICodeEditor | undefined): void {
|
||||
const update: StateDelta = { indentation: undefined };
|
||||
const update: StateDelta = { type: 'indentation', indentation: undefined };
|
||||
|
||||
if (editorWidget) {
|
||||
const model = editorWidget.getModel();
|
||||
@@ -701,7 +737,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
private onMetadataChange(editor: IBaseEditor | undefined): void {
|
||||
const update: StateDelta = { metadata: undefined };
|
||||
const update: StateDelta = { type: 'metadata', metadata: undefined };
|
||||
|
||||
if (editor instanceof BaseBinaryResourceEditor || editor instanceof BinaryResourceDiffEditor) {
|
||||
update.metadata = editor.getMetadata();
|
||||
@@ -710,6 +746,16 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
this.updateState(update);
|
||||
}
|
||||
|
||||
private onColumnSelectionModeChange(editorWidget: ICodeEditor | undefined): void {
|
||||
const info: StateDelta = { type: 'columnSelectionMode', columnSelectionMode: false };
|
||||
|
||||
if (editorWidget && editorWidget.getOption(EditorOption.columnSelection)) {
|
||||
info.columnSelectionMode = true;
|
||||
}
|
||||
|
||||
this.updateState(info);
|
||||
}
|
||||
|
||||
private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void {
|
||||
let screenReaderMode = false;
|
||||
|
||||
@@ -733,7 +779,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
this.screenReaderNotification.close();
|
||||
}
|
||||
|
||||
this.updateState({ screenReaderMode: screenReaderMode });
|
||||
this.updateState({ type: 'screenReaderMode', screenReaderMode: screenReaderMode });
|
||||
}
|
||||
|
||||
private onSelectionChange(editorWidget: ICodeEditor | undefined): void {
|
||||
@@ -773,11 +819,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
this.updateState({ selectionStatus: this.getSelectionLabel(info) });
|
||||
this.updateState({ type: 'selectionStatus', selectionStatus: this.getSelectionLabel(info) });
|
||||
}
|
||||
|
||||
private onEOLChange(editorWidget: ICodeEditor | undefined): void {
|
||||
const info: StateDelta = { EOL: undefined };
|
||||
const info: StateDelta = { type: 'EOL', EOL: undefined };
|
||||
|
||||
if (editorWidget && !editorWidget.getOption(EditorOption.readOnly)) {
|
||||
const codeEditorModel = editorWidget.getModel();
|
||||
@@ -794,7 +840,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
return;
|
||||
}
|
||||
|
||||
const info: StateDelta = { encoding: undefined };
|
||||
const info: StateDelta = { type: 'encoding', encoding: undefined };
|
||||
|
||||
// We only support text based editors that have a model associated
|
||||
// This ensures we do not show the encoding picker while an editor
|
||||
@@ -828,7 +874,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
private onTabFocusModeChange(): void {
|
||||
const info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() };
|
||||
const info: StateDelta = { type: 'tabFocusMode', tabFocusMode: TabFocus.getTabFocusMode() };
|
||||
|
||||
this.updateState(info);
|
||||
}
|
||||
|
||||
@@ -26,10 +26,8 @@ export class ClearNotificationAction extends Action {
|
||||
super(id, label, 'codicon-close');
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
async run(notification: INotificationViewItem): Promise<any> {
|
||||
this.commandService.executeCommand(CLEAR_NOTIFICATION, notification);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +44,8 @@ export class ClearAllNotificationsAction extends Action {
|
||||
super(id, label, 'codicon-clear-all');
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
async run(notification: INotificationViewItem): Promise<any> {
|
||||
this.commandService.executeCommand(CLEAR_ALL_NOTIFICATIONS);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,13 +59,11 @@ export class HideNotificationsCenterAction extends Action {
|
||||
label: string,
|
||||
@ICommandService private readonly commandService: ICommandService
|
||||
) {
|
||||
super(id, label, 'codicon-close');
|
||||
super(id, label, 'codicon-chevron-down');
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
async run(notification: INotificationViewItem): Promise<any> {
|
||||
this.commandService.executeCommand(HIDE_NOTIFICATIONS_CENTER);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,10 +80,8 @@ export class ExpandNotificationAction extends Action {
|
||||
super(id, label, 'codicon-chevron-up');
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
async run(notification: INotificationViewItem): Promise<any> {
|
||||
this.commandService.executeCommand(EXPAND_NOTIFICATION, notification);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,10 +98,8 @@ export class CollapseNotificationAction extends Action {
|
||||
super(id, label, 'codicon-chevron-down');
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
async run(notification: INotificationViewItem): Promise<any> {
|
||||
this.commandService.executeCommand(COLLAPSE_NOTIFICATION, notification);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,9 @@ export class NotificationsCenter extends Themable implements INotificationsCente
|
||||
// Theming
|
||||
this.updateStyles();
|
||||
|
||||
// Mark as visible
|
||||
this.model.notifications.forEach(notification => notification.updateVisibility(true));
|
||||
|
||||
// Context Key
|
||||
this.notificationsCenterVisibleContextKey.set(true);
|
||||
|
||||
@@ -115,7 +118,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente
|
||||
clearAllAction.enabled = false;
|
||||
} else {
|
||||
notificationsCenterTitle.textContent = localize('notifications', "Notifications");
|
||||
clearAllAction.enabled = true;
|
||||
clearAllAction.enabled = this.model.notifications.some(notification => !notification.hasProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,20 +175,22 @@ export class NotificationsCenter extends Themable implements INotificationsCente
|
||||
return; // only if visible
|
||||
}
|
||||
|
||||
let focusGroup = false;
|
||||
let focusEditor = false;
|
||||
|
||||
// Update notifications list based on event
|
||||
const [notificationsList, notificationsCenterContainer] = assertAllDefined(this.notificationsList, this.notificationsCenterContainer);
|
||||
switch (e.kind) {
|
||||
case NotificationChangeType.ADD:
|
||||
notificationsList.updateNotificationsList(e.index, 0, [e.item]);
|
||||
e.item.updateVisibility(true);
|
||||
break;
|
||||
case NotificationChangeType.CHANGE:
|
||||
notificationsList.updateNotificationsList(e.index, 1, [e.item]);
|
||||
break;
|
||||
case NotificationChangeType.REMOVE:
|
||||
focusGroup = isAncestor(document.activeElement, notificationsCenterContainer);
|
||||
focusEditor = isAncestor(document.activeElement, notificationsCenterContainer);
|
||||
notificationsList.updateNotificationsList(e.index, 1);
|
||||
e.item.updateVisibility(false);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -197,7 +202,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente
|
||||
this.hide();
|
||||
|
||||
// Restore focus to editor group if we had focus
|
||||
if (focusGroup) {
|
||||
if (focusEditor) {
|
||||
this.editorGroupService.activeGroup.focus();
|
||||
}
|
||||
}
|
||||
@@ -208,13 +213,16 @@ export class NotificationsCenter extends Themable implements INotificationsCente
|
||||
return; // already hidden
|
||||
}
|
||||
|
||||
const focusGroup = isAncestor(document.activeElement, this.notificationsCenterContainer);
|
||||
const focusEditor = isAncestor(document.activeElement, this.notificationsCenterContainer);
|
||||
|
||||
// Hide
|
||||
this._isVisible = false;
|
||||
removeClass(this.notificationsCenterContainer, 'visible');
|
||||
this.notificationsList.hide();
|
||||
|
||||
// Mark as hidden
|
||||
this.model.notifications.forEach(notification => notification.updateVisibility(false));
|
||||
|
||||
// Context Key
|
||||
this.notificationsCenterVisibleContextKey.set(false);
|
||||
|
||||
@@ -222,7 +230,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente
|
||||
this._onDidChangeVisibility.fire();
|
||||
|
||||
// Restore focus to editor group if we had focus
|
||||
if (focusGroup) {
|
||||
if (focusEditor) {
|
||||
this.editorGroupService.activeGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
|
||||
|
||||
// Show Notifications Cneter
|
||||
CommandsRegistry.registerCommand(SHOW_NOTIFICATIONS_CENTER, () => {
|
||||
toasts.hide();
|
||||
center.show();
|
||||
});
|
||||
|
||||
@@ -92,6 +93,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
|
||||
if (center.isVisible) {
|
||||
center.hide();
|
||||
} else {
|
||||
toasts.hide();
|
||||
center.show();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ export class NotificationsStatus extends Disposable {
|
||||
if (!this.isNotificationsCenterVisible) {
|
||||
if (e.kind === NotificationChangeType.ADD) {
|
||||
this.newNotificationsCount++;
|
||||
} else if (e.kind === NotificationChangeType.REMOVE) {
|
||||
} else if (e.kind === NotificationChangeType.REMOVE && this.newNotificationsCount > 0) {
|
||||
this.newNotificationsCount--;
|
||||
}
|
||||
}
|
||||
@@ -69,8 +69,9 @@ export class NotificationsStatus extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
// Show the bell with a dot if there are unread or in-progress notifications
|
||||
const statusProperties: IStatusbarEntry = {
|
||||
text: `${this.newNotificationsCount === 0 ? '$(bell)' : '$(bell-dot)'}${notificationsInProgress > 0 ? ' $(sync~spin)' : ''}`,
|
||||
text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-dot)' : '$(bell)'}`,
|
||||
command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER,
|
||||
tooltip: this.getTooltip(notificationsInProgress),
|
||||
showBeak: this.isNotificationsCenterVisible
|
||||
|
||||
@@ -179,11 +179,8 @@ export class NotificationsToasts extends Themable implements INotificationsToast
|
||||
const toast: INotificationToast = { item, list: notificationList, container: notificationToastContainer, toast: notificationToast, toDispose: itemDisposables };
|
||||
this.mapNotificationToToast.set(item, toast);
|
||||
|
||||
itemDisposables.add(toDisposable(() => {
|
||||
if (this.isToastVisible(toast) && notificationsToastsContainer) {
|
||||
notificationsToastsContainer.removeChild(toast.container);
|
||||
}
|
||||
}));
|
||||
// When disposed, remove as visible
|
||||
itemDisposables.add(toDisposable(() => this.updateToastVisibility(toast, false)));
|
||||
|
||||
// Make visible
|
||||
notificationList.show();
|
||||
@@ -236,6 +233,9 @@ export class NotificationsToasts extends Themable implements INotificationsToast
|
||||
addClass(notificationToast, 'notification-fade-in-done');
|
||||
}));
|
||||
|
||||
// Mark as visible
|
||||
item.updateVisibility(true);
|
||||
|
||||
// Events
|
||||
if (!this._isVisible) {
|
||||
this._isVisible = true;
|
||||
@@ -292,12 +292,13 @@ export class NotificationsToasts extends Themable implements INotificationsToast
|
||||
}
|
||||
|
||||
private removeToast(item: INotificationViewItem): void {
|
||||
let focusEditor = false;
|
||||
|
||||
const notificationToast = this.mapNotificationToToast.get(item);
|
||||
let focusGroup = false;
|
||||
if (notificationToast) {
|
||||
const toastHasDOMFocus = isAncestor(document.activeElement, notificationToast.container);
|
||||
if (toastHasDOMFocus) {
|
||||
focusGroup = !(this.focusNext() || this.focusPrevious()); // focus next if any, otherwise focus editor
|
||||
focusEditor = !(this.focusNext() || this.focusPrevious()); // focus next if any, otherwise focus editor
|
||||
}
|
||||
|
||||
// Listeners
|
||||
@@ -317,7 +318,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast
|
||||
this.doHide();
|
||||
|
||||
// Move focus back to editor group as needed
|
||||
if (focusGroup) {
|
||||
if (focusEditor) {
|
||||
this.editorGroupService.activeGroup.focus();
|
||||
}
|
||||
}
|
||||
@@ -346,11 +347,11 @@ export class NotificationsToasts extends Themable implements INotificationsToast
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
const focusGroup = this.notificationsToastsContainer ? isAncestor(document.activeElement, this.notificationsToastsContainer) : false;
|
||||
const focusEditor = this.notificationsToastsContainer ? isAncestor(document.activeElement, this.notificationsToastsContainer) : false;
|
||||
|
||||
this.removeToasts();
|
||||
|
||||
if (focusGroup) {
|
||||
if (focusEditor) {
|
||||
this.editorGroupService.activeGroup.focus();
|
||||
}
|
||||
}
|
||||
@@ -459,12 +460,12 @@ export class NotificationsToasts extends Themable implements INotificationsToast
|
||||
notificationToasts.push(toast);
|
||||
break;
|
||||
case ToastVisibility.HIDDEN:
|
||||
if (!this.isToastVisible(toast)) {
|
||||
if (!this.isToastInDOM(toast)) {
|
||||
notificationToasts.push(toast);
|
||||
}
|
||||
break;
|
||||
case ToastVisibility.VISIBLE:
|
||||
if (this.isToastVisible(toast)) {
|
||||
if (this.isToastInDOM(toast)) {
|
||||
notificationToasts.push(toast);
|
||||
}
|
||||
break;
|
||||
@@ -530,7 +531,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast
|
||||
|
||||
// In order to measure the client height, the element cannot have display: none
|
||||
toast.container.style.opacity = '0';
|
||||
this.setVisibility(toast, true);
|
||||
this.updateToastVisibility(toast, true);
|
||||
|
||||
heightToGive -= toast.container.offsetHeight;
|
||||
|
||||
@@ -542,7 +543,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast
|
||||
}
|
||||
|
||||
// Hide or show toast based on context
|
||||
this.setVisibility(toast, makeVisible);
|
||||
this.updateToastVisibility(toast, makeVisible);
|
||||
toast.container.style.opacity = '';
|
||||
|
||||
if (makeVisible) {
|
||||
@@ -551,20 +552,24 @@ export class NotificationsToasts extends Themable implements INotificationsToast
|
||||
});
|
||||
}
|
||||
|
||||
private setVisibility(toast: INotificationToast, visible: boolean): void {
|
||||
if (this.isToastVisible(toast) === visible) {
|
||||
private updateToastVisibility(toast: INotificationToast, visible: boolean): void {
|
||||
if (this.isToastInDOM(toast) === visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update visibility in DOM
|
||||
const notificationsToastsContainer = assertIsDefined(this.notificationsToastsContainer);
|
||||
if (visible) {
|
||||
notificationsToastsContainer.appendChild(toast.container);
|
||||
} else {
|
||||
notificationsToastsContainer.removeChild(toast.container);
|
||||
}
|
||||
|
||||
// Update visibility in model
|
||||
toast.item.updateVisibility(visible);
|
||||
}
|
||||
|
||||
private isToastVisible(toast: INotificationToast): boolean {
|
||||
private isToastInDOM(toast: INotificationToast): boolean {
|
||||
return !!toast.container.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,10 @@
|
||||
|
||||
}
|
||||
|
||||
.monaco-workbench .part.panel > .composite.title > .composite-bar-excess {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar {
|
||||
line-height: 27px; /* matches panel titles in settings */
|
||||
height: 35px;
|
||||
|
||||
@@ -36,6 +36,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
|
||||
import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
|
||||
interface ICachedPanel {
|
||||
id: string;
|
||||
@@ -143,7 +144,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(),
|
||||
hidePart: () => this.layoutService.setPanelHidden(true),
|
||||
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel,
|
||||
(id: string, focus?: boolean) => this.openPanel(id, focus),
|
||||
(id: string, focus?: boolean) => (<unknown>this.openPanel(id, focus)) as Promise<IPaneComposite | undefined>, // {{SQL CARBON EDIT}} strict-null-check
|
||||
(from: string, to: string) => this.compositeBar.move(from, to),
|
||||
() => this.getPinnedPanels().map(p => p.id)
|
||||
),
|
||||
|
||||
@@ -255,7 +255,10 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
this._onDidFocus.fire();
|
||||
}));
|
||||
this._register(focusTracker.onDidBlur(() => {
|
||||
this.focusedViewContextKey.reset();
|
||||
if (this.focusedViewContextKey.get() === this.id) {
|
||||
this.focusedViewContextKey.reset();
|
||||
}
|
||||
|
||||
this._onDidBlur.fire();
|
||||
}));
|
||||
}
|
||||
@@ -1006,7 +1009,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
if (!this.areExtensionsReady) {
|
||||
if (this.visibleViewsCountFromCache === undefined) {
|
||||
return true;
|
||||
// TODO @sbatten fix hack for #91367
|
||||
return this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel;
|
||||
}
|
||||
// Check in cache so that view do not jump. See #29609
|
||||
return this.visibleViewsCountFromCache === 1;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification';
|
||||
import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter, INotificationProgressProperties } from 'vs/platform/notification/common/notification';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -92,6 +92,9 @@ export class NotificationHandle extends Disposable implements INotificationHandl
|
||||
private readonly _onDidClose = this._register(new Emitter<void>());
|
||||
readonly onDidClose = this._onDidClose.event;
|
||||
|
||||
private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||
|
||||
constructor(private readonly item: INotificationViewItem, private readonly onClose: (item: INotificationViewItem) => void) {
|
||||
super();
|
||||
|
||||
@@ -99,6 +102,11 @@ export class NotificationHandle extends Disposable implements INotificationHandl
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Visibility
|
||||
this._register(this.item.onDidChangeVisibility(visible => this._onDidChangeVisibility.fire(visible)));
|
||||
|
||||
// Closing
|
||||
Event.once(this.item.onDidClose)(() => {
|
||||
this._onDidClose.fire();
|
||||
|
||||
@@ -265,6 +273,7 @@ export interface INotificationViewItem {
|
||||
|
||||
readonly onDidChangeExpansion: Event<void>;
|
||||
readonly onDidClose: Event<void>;
|
||||
readonly onDidChangeVisibility: Event<boolean>;
|
||||
readonly onDidChangeLabel: Event<INotificationViewItemLabelChangeEvent>;
|
||||
|
||||
expand(): void;
|
||||
@@ -275,6 +284,8 @@ export interface INotificationViewItem {
|
||||
updateMessage(message: NotificationMessage): void;
|
||||
updateActions(actions?: INotificationActions): void;
|
||||
|
||||
updateVisibility(visible: boolean): void;
|
||||
|
||||
close(): void;
|
||||
|
||||
equals(item: INotificationViewItem): boolean;
|
||||
@@ -398,6 +409,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie
|
||||
private static readonly MAX_MESSAGE_LENGTH = 1000;
|
||||
|
||||
private _expanded: boolean | undefined;
|
||||
private _visible: boolean = false;
|
||||
|
||||
private _actions: INotificationActions | undefined;
|
||||
private _progress: NotificationViewItemProgress | undefined;
|
||||
@@ -411,6 +423,9 @@ export class NotificationViewItem extends Disposable implements INotificationVie
|
||||
private readonly _onDidChangeLabel = this._register(new Emitter<INotificationViewItemLabelChangeEvent>());
|
||||
readonly onDidChangeLabel = this._onDidChangeLabel.event;
|
||||
|
||||
private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||
|
||||
static create(notification: INotification, filter: NotificationsFilter = NotificationsFilter.OFF): INotificationViewItem | undefined {
|
||||
if (!notification || !notification.message || isPromiseCanceledError(notification.message)) {
|
||||
return undefined; // we need a message to show
|
||||
@@ -435,7 +450,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie
|
||||
actions = { primary: notification.message.actions };
|
||||
}
|
||||
|
||||
return new NotificationViewItem(severity, notification.sticky, notification.silent || filter === NotificationsFilter.SILENT || (filter === NotificationsFilter.ERROR && notification.severity !== Severity.Error), message, notification.source, actions);
|
||||
return new NotificationViewItem(severity, notification.sticky, notification.silent || filter === NotificationsFilter.SILENT || (filter === NotificationsFilter.ERROR && notification.severity !== Severity.Error), message, notification.source, notification.progress, actions);
|
||||
}
|
||||
|
||||
private static parseNotificationMessage(input: NotificationMessage): INotificationMessage | undefined {
|
||||
@@ -472,13 +487,30 @@ export class NotificationViewItem extends Disposable implements INotificationVie
|
||||
private _silent: boolean | undefined,
|
||||
private _message: INotificationMessage,
|
||||
private _source: string | undefined,
|
||||
progress: INotificationProgressProperties | undefined,
|
||||
actions?: INotificationActions
|
||||
) {
|
||||
super();
|
||||
|
||||
if (progress) {
|
||||
this.setProgress(progress);
|
||||
}
|
||||
|
||||
this.setActions(actions);
|
||||
}
|
||||
|
||||
private setProgress(progress: INotificationProgressProperties): void {
|
||||
if (progress.infinite) {
|
||||
this.progress.infinite();
|
||||
} else if (progress.total) {
|
||||
this.progress.total(progress.total);
|
||||
|
||||
if (progress.worked) {
|
||||
this.progress.worked(progress.worked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setActions(actions: INotificationActions = { primary: [], secondary: [] }): void {
|
||||
if (!Array.isArray(actions.primary)) {
|
||||
actions.primary = [];
|
||||
@@ -583,6 +615,14 @@ export class NotificationViewItem extends Disposable implements INotificationVie
|
||||
this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.ACTIONS });
|
||||
}
|
||||
|
||||
updateVisibility(visible: boolean): void {
|
||||
if (this._visible !== visible) {
|
||||
this._visible = visible;
|
||||
|
||||
this._onDidChangeVisibility.fire(visible);
|
||||
}
|
||||
}
|
||||
|
||||
expand(): void {
|
||||
if (this._expanded || !this.canCollapse) {
|
||||
return;
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IAction, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
import { flatten, mergeSort } from 'vs/base/common/arrays';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { SetMap } from 'vs/base/common/collections';
|
||||
|
||||
@@ -53,6 +53,7 @@ export interface IViewContainerDescriptor {
|
||||
|
||||
readonly extensionId?: ExtensionIdentifier;
|
||||
|
||||
readonly rejectAddedViews?: boolean;
|
||||
}
|
||||
|
||||
export interface IViewContainersRegistry {
|
||||
@@ -211,9 +212,16 @@ export interface IViewDescriptorCollection extends IDisposable {
|
||||
readonly allViewDescriptors: IViewDescriptor[];
|
||||
}
|
||||
|
||||
export enum ViewContentPriority {
|
||||
Normal = 0,
|
||||
Low = 1,
|
||||
Lowest = 2
|
||||
}
|
||||
|
||||
export interface IViewContentDescriptor {
|
||||
readonly content: string;
|
||||
readonly when?: ContextKeyExpr | 'default';
|
||||
readonly priority?: ViewContentPriority;
|
||||
|
||||
/**
|
||||
* ordered preconditions for each button in the content
|
||||
@@ -247,6 +255,13 @@ export interface IViewsRegistry {
|
||||
}
|
||||
|
||||
function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewContentDescriptor): number {
|
||||
const aPriority = a.priority ?? ViewContentPriority.Normal;
|
||||
const bPriority = b.priority ?? ViewContentPriority.Normal;
|
||||
|
||||
if (aPriority !== bPriority) {
|
||||
return aPriority - bPriority;
|
||||
}
|
||||
|
||||
return a.content < b.content ? -1 : 1;
|
||||
}
|
||||
|
||||
@@ -328,8 +343,8 @@ class ViewsRegistry extends Disposable implements IViewsRegistry {
|
||||
|
||||
getViewWelcomeContent(id: string): IViewContentDescriptor[] {
|
||||
const result: IViewContentDescriptor[] = [];
|
||||
result.sort(compareViewContentDescriptors);
|
||||
this._viewWelcomeContents.forEach(id, descriptor => result.push(descriptor));
|
||||
mergeSort(result, compareViewContentDescriptors);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ class BulkEditPreviewContribution {
|
||||
private async _previewEdit(edit: WorkspaceEdit) {
|
||||
this._ctxEnabled.set(true);
|
||||
|
||||
const uxState = this._activeSession?.uxState ?? new UXState(this._panelService, this._editorGroupsService);
|
||||
const view = await getBulkEditPane(this._viewsService);
|
||||
if (!view) {
|
||||
this._ctxEnabled.set(false);
|
||||
@@ -136,9 +137,9 @@ class BulkEditPreviewContribution {
|
||||
let session: PreviewSession;
|
||||
if (this._activeSession) {
|
||||
this._activeSession.cts.dispose(true);
|
||||
session = new PreviewSession(this._activeSession.uxState);
|
||||
session = new PreviewSession(uxState);
|
||||
} else {
|
||||
session = new PreviewSession(new UXState(this._panelService, this._editorGroupsService));
|
||||
session = new PreviewSession(uxState);
|
||||
}
|
||||
this._activeSession = session;
|
||||
|
||||
|
||||
@@ -10,6 +10,13 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
|
||||
|
||||
export class ToggleColumnSelectionAction extends Action {
|
||||
public static readonly ID = 'editor.action.toggleColumnSelection';
|
||||
@@ -18,26 +25,68 @@ export class ToggleColumnSelectionAction extends Action {
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
const newValue = !this._configurationService.getValue<boolean>('editor.columnSelection');
|
||||
return this._configurationService.updateValue('editor.columnSelection', newValue, ConfigurationTarget.USER);
|
||||
private _getCodeEditor(): ICodeEditor | null {
|
||||
const codeEditor = this._codeEditorService.getFocusedCodeEditor();
|
||||
if (codeEditor) {
|
||||
return codeEditor;
|
||||
}
|
||||
return this._codeEditorService.getActiveCodeEditor();
|
||||
}
|
||||
|
||||
public async run(): Promise<any> {
|
||||
const oldValue = this._configurationService.getValue<boolean>('editor.columnSelection');
|
||||
const codeEditor = this._getCodeEditor();
|
||||
await this._configurationService.updateValue('editor.columnSelection', !oldValue, ConfigurationTarget.USER);
|
||||
const newValue = this._configurationService.getValue<boolean>('editor.columnSelection');
|
||||
if (!codeEditor || codeEditor !== this._getCodeEditor() || oldValue === newValue || !codeEditor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const cursors = codeEditor._getCursors();
|
||||
if (codeEditor.getOption(EditorOption.columnSelection)) {
|
||||
const selection = codeEditor.getSelection();
|
||||
const modelSelectionStart = new Position(selection.selectionStartLineNumber, selection.selectionStartColumn);
|
||||
const viewSelectionStart = cursors.context.convertModelPositionToViewPosition(modelSelectionStart);
|
||||
const modelPosition = new Position(selection.positionLineNumber, selection.positionColumn);
|
||||
const viewPosition = cursors.context.convertModelPositionToViewPosition(modelPosition);
|
||||
|
||||
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursors, {
|
||||
position: modelSelectionStart,
|
||||
viewPosition: viewSelectionStart
|
||||
});
|
||||
const visibleColumn = CursorColumns.visibleColumnFromColumn2(cursors.context.config, cursors.context.viewModel, viewPosition);
|
||||
CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(cursors, {
|
||||
position: modelPosition,
|
||||
viewPosition: viewPosition,
|
||||
doColumnSelect: true,
|
||||
mouseColumn: visibleColumn + 1
|
||||
});
|
||||
} else {
|
||||
const columnSelectData = cursors.getColumnSelectData();
|
||||
const fromViewColumn = CursorColumns.columnFromVisibleColumn2(cursors.context.config, cursors.context.viewModel, columnSelectData.fromViewLineNumber, columnSelectData.fromViewVisualColumn);
|
||||
const fromPosition = cursors.context.convertViewPositionToModelPosition(columnSelectData.fromViewLineNumber, fromViewColumn);
|
||||
const toViewColumn = CursorColumns.columnFromVisibleColumn2(cursors.context.config, cursors.context.viewModel, columnSelectData.toViewLineNumber, columnSelectData.toViewVisualColumn);
|
||||
const toPosition = cursors.context.convertViewPositionToModelPosition(columnSelectData.toViewLineNumber, toViewColumn);
|
||||
|
||||
codeEditor.setSelection(new Selection(fromPosition.lineNumber, fromPosition.column, toPosition.lineNumber, toPosition.column));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleColumnSelectionAction, ToggleColumnSelectionAction.ID, ToggleColumnSelectionAction.LABEL), 'View: Toggle Column Selection Mode', nls.localize('view', "View"));
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleColumnSelectionAction, ToggleColumnSelectionAction.ID, ToggleColumnSelectionAction.LABEL), 'Toggle Column Selection Mode');
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, {
|
||||
group: '3_multi',
|
||||
group: '4_config',
|
||||
command: {
|
||||
id: ToggleColumnSelectionAction.ID,
|
||||
title: nls.localize({ key: 'miColumnSelection', comment: ['&& denotes a mnemonic'] }, "Column &&Selection Mode"),
|
||||
toggled: ContextKeyExpr.equals('config.editor.columnSelection', true)
|
||||
},
|
||||
order: 1.5
|
||||
order: 2
|
||||
});
|
||||
|
||||
@@ -70,7 +70,7 @@ Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).regi
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMultiCursorModifierAction, ToggleMultiCursorModifierAction.ID, ToggleMultiCursorModifierAction.LABEL), 'Toggle Multi-Cursor Modifier');
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, {
|
||||
group: '3_multi',
|
||||
group: '4_config',
|
||||
command: {
|
||||
id: ToggleMultiCursorModifierAction.ID,
|
||||
title: nls.localize('miMultiCursorAlt', "Switch to Alt+Click for Multi-Cursor")
|
||||
@@ -79,7 +79,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, {
|
||||
order: 1
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, {
|
||||
group: '3_multi',
|
||||
group: '4_config',
|
||||
command: {
|
||||
id: ToggleMultiCursorModifierAction.ID,
|
||||
title: (
|
||||
|
||||
@@ -20,10 +20,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
|
||||
export class SelectionClipboard extends Disposable implements IEditorContribution {
|
||||
private static readonly SELECTION_LENGTH_LIMIT = 65536;
|
||||
@@ -119,15 +116,7 @@ class PasteSelectionClipboardAction extends EditorAction {
|
||||
id: 'editor.action.selectionClipboardPaste',
|
||||
label: nls.localize('actions.pasteSelectionClipboard', "Paste Selection Clipboard"),
|
||||
alias: 'Paste Selection Clipboard',
|
||||
precondition: EditorContextKeys.writable,
|
||||
kbOpts: {
|
||||
kbExpr: ContextKeyExpr.and(
|
||||
EditorContextKeys.editorTextFocus,
|
||||
ContextKeyExpr.has('config.editor.selectionClipboard')
|
||||
),
|
||||
primary: KeyMod.Shift | KeyCode.Insert,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
precondition: EditorContextKeys.writable
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -333,11 +333,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
||||
}
|
||||
|
||||
const editorInfo = this._editorInfoStore.get(editor.viewType);
|
||||
if (!editorInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!editorInfo.matches(newResource)) {
|
||||
if (!editorInfo?.matches(newResource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ export interface ICustomEditorModel extends IWorkingCopy {
|
||||
readonly onWillSave: Event<CustomEditorSaveEvent>;
|
||||
readonly onWillSaveAs: Event<CustomEditorSaveAsEvent>;
|
||||
|
||||
onBackup(f: () => CancelablePromise<boolean>): void;
|
||||
onBackup(f: () => CancelablePromise<void>): void;
|
||||
|
||||
setDirty(dirty: boolean): void;
|
||||
undo(): void;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace HotExitState {
|
||||
readonly type = Type.Pending;
|
||||
|
||||
constructor(
|
||||
public readonly operation: CancelablePromise<boolean>,
|
||||
public readonly operation: CancelablePromise<void>,
|
||||
) { }
|
||||
}
|
||||
|
||||
@@ -90,9 +90,9 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel
|
||||
private readonly _onWillSaveAs = this._register(new Emitter<CustomEditorSaveAsEvent>());
|
||||
public readonly onWillSaveAs = this._onWillSaveAs.event;
|
||||
|
||||
private _onBackup: undefined | (() => CancelablePromise<boolean>);
|
||||
private _onBackup: undefined | (() => CancelablePromise<void>);
|
||||
|
||||
public onBackup(f: () => CancelablePromise<boolean>) {
|
||||
public onBackup(f: () => CancelablePromise<void>) {
|
||||
if (this._onBackup) {
|
||||
throw new Error('Backup already implemented');
|
||||
}
|
||||
@@ -182,7 +182,11 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel
|
||||
this._hotExitState = pendingState;
|
||||
|
||||
try {
|
||||
this._hotExitState = await pendingState.operation ? HotExitState.Allowed : HotExitState.NotAllowed;
|
||||
await pendingState.operation;
|
||||
// Make sure state has not changed in the meantime
|
||||
if (this._hotExitState === pendingState) {
|
||||
this._hotExitState = HotExitState.Allowed;
|
||||
}
|
||||
} catch (e) {
|
||||
// Make sure state has not changed in the meantime
|
||||
if (this._hotExitState === pendingState) {
|
||||
|
||||
@@ -267,7 +267,7 @@ configurationRegistry.registerConfiguration({
|
||||
default: true
|
||||
},
|
||||
'debug.onTaskErrors': {
|
||||
enum: ['debugAnyway', 'showErrors', 'prompt', 'cancel'],
|
||||
enum: ['debugAnyway', 'showErrors', 'prompt', 'abort'],
|
||||
enumDescriptions: [nls.localize('debugAnyway', "Ignore task errors and start debugging."), nls.localize('showErrors', "Show the Problems view and do not start debugging."), nls.localize('prompt', "Prompt user."), nls.localize('cancel', "Cancel debugging.")],
|
||||
description: nls.localize('debug.onTaskErrors', "Controls what to do when errors are encountered after running a preLaunchTask."),
|
||||
default: 'prompt'
|
||||
|
||||
@@ -519,18 +519,17 @@ abstract class AbstractLaunch {
|
||||
if (!config || (!Array.isArray(config.configurations) && !Array.isArray(config.compounds))) {
|
||||
return [];
|
||||
} else {
|
||||
const names: string[] = [];
|
||||
const configurations: (IConfig | ICompound)[] = [];
|
||||
if (config.configurations) {
|
||||
names.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name));
|
||||
configurations.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string'));
|
||||
}
|
||||
if (includeCompounds && config.compounds) {
|
||||
if (config.compounds) {
|
||||
names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length)
|
||||
.map(compound => compound.name));
|
||||
configurations.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length));
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
return getVisibleAndSorted(configurations).map(c => c.name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ export class DebugHoverWidget implements IContentWidget {
|
||||
|
||||
if (!matchingExpression) {
|
||||
const lineContent = model.getLineContent(pos.lineNumber);
|
||||
matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn);
|
||||
matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSess
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { RunOnceScheduler, Queue } from 'vs/base/common/async';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
|
||||
@@ -807,62 +807,58 @@ export class DebugSession implements IDebugSession {
|
||||
this._onDidChangeState.fire();
|
||||
}));
|
||||
|
||||
let outpuPromises: Promise<void>[] = [];
|
||||
const outputQueue = new Queue<void>();
|
||||
this.rawListeners.push(this.raw.onDidOutput(async event => {
|
||||
if (!event.body || !this.raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info;
|
||||
if (event.body.category === 'telemetry') {
|
||||
// only log telemetry events from debug adapter if the debug extension provided the telemetry key
|
||||
// and the user opted in telemetry
|
||||
if (this.raw.customTelemetryService && this.telemetryService.isOptedIn) {
|
||||
// __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly.
|
||||
this.raw.customTelemetryService.publicLog(event.body.output, event.body.data);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure to append output in the correct order by properly waiting on preivous promises #33822
|
||||
const waitFor = outpuPromises.slice();
|
||||
const source = event.body.source && event.body.line ? {
|
||||
lineNumber: event.body.line,
|
||||
column: event.body.column ? event.body.column : 1,
|
||||
source: this.getSource(event.body.source)
|
||||
} : undefined;
|
||||
|
||||
if (event.body.group === 'start' || event.body.group === 'startCollapsed') {
|
||||
const expanded = event.body.group === 'start';
|
||||
this.repl.startGroup(event.body.output || '', expanded, source);
|
||||
return;
|
||||
}
|
||||
if (event.body.group === 'end') {
|
||||
this.repl.endGroup();
|
||||
if (!event.body.output) {
|
||||
// Only return if the end event does not have additional output in it
|
||||
outputQueue.queue(async () => {
|
||||
if (!event.body || !this.raw) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.body.variablesReference) {
|
||||
const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid());
|
||||
outpuPromises.push(container.getChildren().then(async children => {
|
||||
await Promise.all(waitFor);
|
||||
children.forEach(child => {
|
||||
// Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names)
|
||||
(<any>child).name = null;
|
||||
this.appendToRepl(child, outputSeverity, source);
|
||||
const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info;
|
||||
if (event.body.category === 'telemetry') {
|
||||
// only log telemetry events from debug adapter if the debug extension provided the telemetry key
|
||||
// and the user opted in telemetry
|
||||
if (this.raw.customTelemetryService && this.telemetryService.isOptedIn) {
|
||||
// __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly.
|
||||
this.raw.customTelemetryService.publicLog(event.body.output, event.body.data);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure to append output in the correct order by properly waiting on preivous promises #33822
|
||||
const source = event.body.source && event.body.line ? {
|
||||
lineNumber: event.body.line,
|
||||
column: event.body.column ? event.body.column : 1,
|
||||
source: this.getSource(event.body.source)
|
||||
} : undefined;
|
||||
|
||||
if (event.body.group === 'start' || event.body.group === 'startCollapsed') {
|
||||
const expanded = event.body.group === 'start';
|
||||
this.repl.startGroup(event.body.output || '', expanded, source);
|
||||
return;
|
||||
}
|
||||
if (event.body.group === 'end') {
|
||||
this.repl.endGroup();
|
||||
if (!event.body.output) {
|
||||
// Only return if the end event does not have additional output in it
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.body.variablesReference) {
|
||||
const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid());
|
||||
await container.getChildren().then(children => {
|
||||
children.forEach(child => {
|
||||
// Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names)
|
||||
(<any>child).name = null;
|
||||
this.appendToRepl(child, outputSeverity, source);
|
||||
});
|
||||
});
|
||||
}));
|
||||
} else if (typeof event.body.output === 'string') {
|
||||
await Promise.all(waitFor);
|
||||
this.appendToRepl(event.body.output, outputSeverity, source);
|
||||
}
|
||||
|
||||
await Promise.all(outpuPromises);
|
||||
outpuPromises = [];
|
||||
} else if (typeof event.body.output === 'string') {
|
||||
this.appendToRepl(event.body.output, outputSeverity, source);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.rawListeners.push(this.raw.onDidBreakpoint(event => {
|
||||
|
||||
@@ -75,35 +75,35 @@ export class StartView extends ViewPane {
|
||||
};
|
||||
this._register(editorService.onDidActiveEditorChange(setContextKey));
|
||||
this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(setContextKey));
|
||||
this.registerViews();
|
||||
setContextKey();
|
||||
|
||||
const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID);
|
||||
debugKeybindingLabel = debugKeybinding ? ` (${debugKeybinding.getLabel()})` : '';
|
||||
}
|
||||
|
||||
shouldShowWelcome(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
private registerViews(): void {
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID),
|
||||
when: CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.toNegated()
|
||||
});
|
||||
|
||||
const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID);
|
||||
const debugKeybindingLabel = debugKeybinding ? ` (${debugKeybinding.getLabel()})` : '';
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('runAndDebugAction', "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID),
|
||||
preconditions: [CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR]
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID),
|
||||
when: WorkbenchStateContext.notEqualsTo('empty')
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID),
|
||||
when: WorkbenchStateContext.isEqualTo('empty')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID),
|
||||
when: CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.toNegated()
|
||||
});
|
||||
|
||||
let debugKeybindingLabel = '';
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('runAndDebugAction', "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID),
|
||||
preconditions: [CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR]
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID),
|
||||
when: WorkbenchStateContext.notEqualsTo('empty')
|
||||
});
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
|
||||
content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID),
|
||||
when: WorkbenchStateContext.isEqualTo('empty')
|
||||
});
|
||||
|
||||
@@ -87,7 +87,8 @@ Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegis
|
||||
name: localize('extensions', "Extensions"),
|
||||
ctorDescriptor: new SyncDescriptor(ExtensionsViewPaneContainer),
|
||||
icon: 'codicon-extensions',
|
||||
order: 14 // {{SQL CARBON EDIT}}
|
||||
order: 14, // {{SQL CARBON EDIT}}
|
||||
rejectAddedViews: true,
|
||||
}, ViewContainerLocation.Sidebar);
|
||||
|
||||
|
||||
|
||||
@@ -2690,7 +2690,7 @@ export class SystemDisabledWarningAction extends ExtensionAction {
|
||||
if (server) {
|
||||
this.tooltip = localize('Install in other server to enable', "Install the extension on '{0}' to enable.", server.label);
|
||||
} else {
|
||||
this.tooltip = localize('disabled because of extension kind', "This extension cannot be enabled in the remote server.");
|
||||
this.tooltip = localize('disabled because of extension kind', "This extension has defined that it cannot run on the remote server");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -65,21 +65,21 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor
|
||||
private registerViews(): void {
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID),
|
||||
when: WorkbenchStateContext.isEqualTo('workspace')
|
||||
});
|
||||
}));
|
||||
|
||||
const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID;
|
||||
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:{0})", commandId),
|
||||
when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated())
|
||||
});
|
||||
}));
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
|
||||
content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId),
|
||||
when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext))
|
||||
});
|
||||
}));
|
||||
|
||||
const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER);
|
||||
|
||||
|
||||
@@ -55,11 +55,11 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.explorer-viewlet .pane-header .monaco-count-badge.hidden {
|
||||
.pane-header .dirty-count.monaco-count-badge.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.explorer-viewlet .monaco-count-badge {
|
||||
.dirty-count.monaco-count-badge {
|
||||
padding: 1px 6px 2px;
|
||||
margin-left: 6px;
|
||||
min-height: auto;
|
||||
|
||||
@@ -185,7 +185,7 @@ export class OpenEditorsView extends ViewPane {
|
||||
super.renderHeaderTitle(container, this.title);
|
||||
|
||||
const count = dom.append(container, $('.count'));
|
||||
this.dirtyCountElement = dom.append(count, $('.monaco-count-badge'));
|
||||
this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge'));
|
||||
|
||||
this._register((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
|
||||
|
||||
@@ -856,6 +856,7 @@ export class ResourceDragAndDrop implements ITreeDragAndDrop<TreeElement> {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const linkFg = theme.getColor(textLinkForeground);
|
||||
if (linkFg) {
|
||||
collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container a.code-link span:hover { color: ${linkFg}; }`);
|
||||
collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container a.code-link .marker-code > span:hover { color: ${linkFg}; }`);
|
||||
collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-list:focus .monaco-tl-contents .details-container a.code-link .marker-code > span:hover { color: ${linkFg.lighten(.4)}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -349,7 +349,6 @@ export class MarkersView extends ViewPane implements IMarkerFilterController {
|
||||
private createArialLabelElement(parent: HTMLElement): void {
|
||||
this.ariaLabelElement = dom.append(parent, dom.$(''));
|
||||
this.ariaLabelElement.setAttribute('id', 'markers-panel-arialabel');
|
||||
this.ariaLabelElement.setAttribute('aria-live', 'polite');
|
||||
}
|
||||
|
||||
private createTree(parent: HTMLElement): void {
|
||||
|
||||
@@ -1238,7 +1238,10 @@ export class SettingTreeRenderers {
|
||||
private getActionsForSetting(setting: ISetting): IAction[] {
|
||||
const enableSync = this._userDataSyncEnablementService.isEnabled();
|
||||
return enableSync && !setting.disallowSyncIgnore ?
|
||||
[this._instantiationService.createInstance(SyncSettingAction, setting)] :
|
||||
[
|
||||
new Separator(),
|
||||
this._instantiationService.createInstance(SyncSettingAction, setting)
|
||||
] :
|
||||
[];
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ export interface ISpliceEvent<T> {
|
||||
export class EmptyPane extends ViewPane {
|
||||
|
||||
static readonly ID = 'workbench.scm';
|
||||
static readonly TITLE = localize('scm providers', "Source Control Providers");
|
||||
static readonly TITLE = localize('scm', "Source Control");
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
|
||||
@@ -19,5 +19,8 @@ export const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatch
|
||||
export const InSearchEditor = new RawContextKey<boolean>('inSearchEditor', false);
|
||||
|
||||
export const SearchEditorScheme = 'search-editor';
|
||||
export const SearchEditorBodyScheme = 'search-editor-body';
|
||||
|
||||
export const SearchEditorFindMatchClass = 'seaarchEditorFindMatch';
|
||||
|
||||
export const SearchEditorID = 'workbench.editor.searchEditor';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { endsWith } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -20,16 +20,16 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
|
||||
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry, ActiveEditorContext } from 'vs/workbench/common/editor';
|
||||
import * as SearchConstants from 'vs/workbench/contrib/search/common/constants';
|
||||
import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants';
|
||||
import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor';
|
||||
import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
|
||||
import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand, RerunSearchEditorSearchAction } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
|
||||
import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
|
||||
//#region Editor Descriptior
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
@@ -54,10 +54,15 @@ class SearchEditorContribution implements IWorkbenchContribution {
|
||||
) {
|
||||
|
||||
this.editorService.overrideOpenEditor((editor, options, group) => {
|
||||
const resource = editor.resource;
|
||||
if (!resource ||
|
||||
!(endsWith(resource.path, '.code-search') || resource.scheme === SearchEditorConstants.SearchEditorScheme) ||
|
||||
!(editor instanceof FileEditorInput || (resource.scheme === SearchEditorConstants.SearchEditorScheme))) {
|
||||
let resource = editor.resource;
|
||||
if (!resource) { return undefined; }
|
||||
|
||||
if (resource.scheme === SearchEditorConstants.SearchEditorBodyScheme) {
|
||||
resource = resource.with({ scheme: SearchEditorConstants.SearchEditorScheme });
|
||||
}
|
||||
|
||||
if (resource.scheme !== SearchEditorConstants.SearchEditorScheme
|
||||
&& !(endsWith(resource.path, '.code-search') && editor instanceof FileEditorInput)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -154,15 +159,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
handler: selectAllSearchEditorMatchesCommand
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(
|
||||
SearchEditorConstants.RerunSearchEditorSearchCommandId,
|
||||
(accessor: ServicesAccessor) => {
|
||||
const activeControl = accessor.get(IEditorService).activeControl;
|
||||
if (activeControl instanceof SearchEditor) {
|
||||
activeControl.triggerSearch({ resetCursor: false });
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(
|
||||
SearchEditorConstants.CleanSearchEditorStateCommandId,
|
||||
(accessor: ServicesAccessor) => {
|
||||
@@ -186,4 +182,19 @@ registry.registerWorkbenchAction(
|
||||
registry.registerWorkbenchAction(
|
||||
SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL),
|
||||
'Search Editor: Open New Search Editor', category);
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(RerunSearchEditorSearchAction, RerunSearchEditorSearchAction.ID, RerunSearchEditorSearchAction.LABEL,
|
||||
{ mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R } }, ContextKeyExpr.and(SearchEditorConstants.InSearchEditor)),
|
||||
'Search Editor: Rerun', category);
|
||||
//#endregion
|
||||
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: RerunSearchEditorSearchAction.ID,
|
||||
title: RerunSearchEditorSearchAction.LABEL,
|
||||
icon: { id: 'codicon/refresh' },
|
||||
},
|
||||
group: 'navigation',
|
||||
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID))
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ import { InputBoxFocusedKey } from 'vs/workbench/contrib/search/common/constants
|
||||
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||
import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search';
|
||||
import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel';
|
||||
import { InSearchEditor, SearchEditorFindMatchClass } from 'vs/workbench/contrib/searchEditor/browser/constants';
|
||||
import { InSearchEditor, SearchEditorFindMatchClass, SearchEditorID } from 'vs/workbench/contrib/searchEditor/browser/constants';
|
||||
import type { SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
|
||||
import { extractSearchQuery, serializeSearchConfiguration, serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization';
|
||||
import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search';
|
||||
@@ -56,7 +56,7 @@ const FILE_LINE_REGEX = /^(\S.*):$/;
|
||||
type SearchEditorViewState = ICodeEditorViewState & { focused: 'input' | 'editor' };
|
||||
|
||||
export class SearchEditor extends BaseTextEditor {
|
||||
static readonly ID: string = 'workbench.editor.searchEditor';
|
||||
static readonly ID: string = SearchEditorID;
|
||||
|
||||
static readonly SEARCH_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'searchEditorViewState';
|
||||
|
||||
@@ -194,6 +194,7 @@ export class SearchEditor extends BaseTextEditor {
|
||||
const runAgainLink = DOM.append(this.messageBox, DOM.$('a.pointer.prominent.message', {}, localize('runSearch', "Run Search")));
|
||||
this.messageDisposables.push(DOM.addDisposableListener(runAgainLink, DOM.EventType.CLICK, async () => {
|
||||
await this.triggerSearch();
|
||||
this.searchResultEditor.focus();
|
||||
this.toggleRunAgainMessage(false);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -116,6 +116,24 @@ export class OpenResultsInEditorAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class RerunSearchEditorSearchAction extends Action {
|
||||
static readonly ID: string = Constants.RerunSearchEditorSearchCommandId;
|
||||
static readonly LABEL = localize('search.rerunSearchInEditor', "Search Again");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
) {
|
||||
super(id, label, 'codicon-refresh');
|
||||
}
|
||||
|
||||
async run() {
|
||||
const input = this.editorService.activeEditor;
|
||||
if (input instanceof SearchEditorInput) {
|
||||
(this.editorService.activeControl as SearchEditor).triggerSearch({ resetCursor: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const openNewSearchEditor =
|
||||
async (accessor: ServicesAccessor) => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
|
||||
@@ -18,7 +18,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { EditorInput, GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, IMoveResult } from 'vs/workbench/common/editor';
|
||||
import { SearchEditorFindMatchClass, SearchEditorScheme } from 'vs/workbench/contrib/searchEditor/browser/constants';
|
||||
import { SearchEditorFindMatchClass, SearchEditorScheme, SearchEditorBodyScheme } from 'vs/workbench/contrib/searchEditor/browser/constants';
|
||||
import { extractSearchQuery, serializeSearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
@@ -339,7 +339,7 @@ export const getOrMakeSearchEditorInput = (
|
||||
}
|
||||
}
|
||||
|
||||
const contentsModelURI = uri.with({ scheme: 'search-editor-body' });
|
||||
const contentsModelURI = uri.with({ scheme: SearchEditorBodyScheme });
|
||||
const headerModelURI = uri.with({ scheme: 'search-editor-header' });
|
||||
const contentsModel = modelService.getModel(contentsModelURI) ?? modelService.createModel('', modeService.create('search-result'), contentsModelURI);
|
||||
const headerModel = modelService.getModel(headerModelURI) ?? modelService.createModel('', modeService.create('search-result'), headerModelURI);
|
||||
|
||||
@@ -216,7 +216,7 @@ export const serializeSearchResultForEditor =
|
||||
|
||||
const info = [
|
||||
searchResult.count()
|
||||
? `${filecount} - ${resultcount}`
|
||||
? `${resultcount} - ${filecount}`
|
||||
: localize('noResults', "No Results"),
|
||||
''];
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/com
|
||||
import * as TaskConfig from '../common/taskConfiguration';
|
||||
import { TerminalTaskSystem } from './terminalTaskSystem';
|
||||
|
||||
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IQuickInputService, IQuickPickItem, QuickPickInput, IQuickPick } from 'vs/platform/quickinput/common/quickInput';
|
||||
|
||||
import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -80,6 +80,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IViewsService } from 'vs/workbench/common/views';
|
||||
import { ProviderProgressMananger } from 'vs/workbench/contrib/tasks/browser/providerProgressManager';
|
||||
|
||||
const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history';
|
||||
const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail';
|
||||
@@ -218,6 +219,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
private _providers: Map<number, ITaskProvider>;
|
||||
private _providerTypes: Map<number, string>;
|
||||
protected _taskSystemInfos: Map<string, TaskSystemInfo>;
|
||||
private _providerProgressManager: ProviderProgressMananger | undefined;
|
||||
|
||||
protected _workspaceTasksPromise?: Promise<Map<string, WorkspaceFolderTaskResult>>;
|
||||
protected _areJsonTasksSupportedPromise: Promise<boolean> = Promise.resolve(false);
|
||||
@@ -560,6 +562,37 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
|
||||
protected abstract versionAndEngineCompatible(filter?: TaskFilter): boolean;
|
||||
|
||||
private tasksAndGroupedTasks(filter?: TaskFilter): { tasks: Promise<Task[]>, grouped: Promise<TaskMap> } {
|
||||
if (!this.versionAndEngineCompatible(filter)) {
|
||||
return { tasks: Promise.resolve<Task[]>([]), grouped: Promise.resolve(new TaskMap()) };
|
||||
}
|
||||
const grouped = this.getGroupedTasks(filter ? filter.type : undefined);
|
||||
const tasks = grouped.then((map) => {
|
||||
if (!filter || !filter.type) {
|
||||
return map.all();
|
||||
}
|
||||
let result: Task[] = [];
|
||||
map.forEach((tasks) => {
|
||||
for (let task of tasks) {
|
||||
if (ContributedTask.is(task) && task.defines.type === filter.type) {
|
||||
result.push(task);
|
||||
} else if (CustomTask.is(task)) {
|
||||
if (task.type === filter.type) {
|
||||
result.push(task);
|
||||
} else {
|
||||
let customizes = task.customizes();
|
||||
if (customizes && customizes.type === filter.type) {
|
||||
result.push(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
});
|
||||
return { tasks, grouped };
|
||||
}
|
||||
|
||||
public tasks(filter?: TaskFilter): Promise<Task[]> {
|
||||
if (!this.versionAndEngineCompatible(filter)) {
|
||||
return Promise.resolve<Task[]>([]);
|
||||
@@ -703,11 +736,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
});
|
||||
}
|
||||
|
||||
public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System): Promise<ITaskSummary | undefined> {
|
||||
public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System, grouped?: Promise<TaskMap>): Promise<ITaskSummary | undefined> {
|
||||
if (!task) {
|
||||
throw new TaskError(Severity.Info, nls.localize('TaskServer.noTask', 'Task to execute is undefined'), TaskErrors.TaskNotFound);
|
||||
}
|
||||
return this.getGroupedTasks().then((grouped) => {
|
||||
return (grouped ?? this.getGroupedTasks()).then((grouped) => {
|
||||
let resolver = this.createResolver(grouped);
|
||||
if (options && options.attachProblemMatcher && this.shouldAttachProblemMatcher(task) && !InMemoryTask.is(task)) {
|
||||
return this.attachProblemMatcher(task).then((toExecute) => {
|
||||
@@ -1345,11 +1378,24 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
|
||||
protected abstract getTaskSystem(): ITaskSystem;
|
||||
|
||||
private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary<boolean>): Promise<TaskSet> {
|
||||
private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary<boolean>): Promise<TaskSet | undefined> {
|
||||
return new Promise<TaskSet>(async (resolve, reject) => {
|
||||
provider.provideTasks(validTypes).then((value) => {
|
||||
let isDone = false;
|
||||
let disposable: IDisposable | undefined;
|
||||
const providePromise = provider.provideTasks(validTypes);
|
||||
this._providerProgressManager?.addProvider(type, providePromise);
|
||||
disposable = this._providerProgressManager?.canceled.token.onCancellationRequested(() => {
|
||||
if (!isDone) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
providePromise.then((value) => {
|
||||
isDone = true;
|
||||
disposable?.dispose();
|
||||
resolve(value);
|
||||
}, (e) => {
|
||||
isDone = true;
|
||||
disposable?.dispose();
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
@@ -1361,10 +1407,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true);
|
||||
validTypes['shell'] = true;
|
||||
validTypes['process'] = true;
|
||||
this._providerProgressManager = new ProviderProgressMananger();
|
||||
return new Promise<TaskSet[]>(resolve => {
|
||||
let result: TaskSet[] = [];
|
||||
let counter: number = 0;
|
||||
let done = (value: TaskSet) => {
|
||||
let done = (value: TaskSet | undefined) => {
|
||||
if (value) {
|
||||
result.push(value);
|
||||
}
|
||||
@@ -2060,30 +2107,69 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
}
|
||||
return entries;
|
||||
});
|
||||
return this.quickInputService.pick(pickEntries, {
|
||||
placeHolder,
|
||||
matchOnDescription: true,
|
||||
onDidTriggerItemButton: context => {
|
||||
let task = context.item.task;
|
||||
this.quickInputService.cancel();
|
||||
if (ContributedTask.is(task)) {
|
||||
this.customize(task, undefined, true);
|
||||
} else if (CustomTask.is(task)) {
|
||||
this.openConfig(task);
|
||||
|
||||
const picker: IQuickPick<TaskQuickPickEntry> = this.quickInputService.createQuickPick();
|
||||
picker.placeholder = placeHolder;
|
||||
picker.matchOnDescription = true;
|
||||
picker.ignoreFocusOut = true;
|
||||
|
||||
picker.onDidTriggerItemButton(context => {
|
||||
let task = context.item.task;
|
||||
this.quickInputService.cancel();
|
||||
if (ContributedTask.is(task)) {
|
||||
this.customize(task, undefined, true);
|
||||
} else if (CustomTask.is(task)) {
|
||||
this.openConfig(task);
|
||||
}
|
||||
});
|
||||
picker.busy = true;
|
||||
const progressManager = this._providerProgressManager;
|
||||
const progressTimeout = setTimeout(() => {
|
||||
if (progressManager) {
|
||||
progressManager.showProgress = (stillProviding, total) => {
|
||||
let message = undefined;
|
||||
if (stillProviding.length > 0) {
|
||||
message = nls.localize('pickProgressManager.description', 'Detecting tasks ({0} of {1}): {2} in progress', total - stillProviding.length, total, stillProviding.join(', '));
|
||||
}
|
||||
picker.description = message;
|
||||
};
|
||||
progressManager.addOnDoneListener(() => {
|
||||
picker.focusOnInput();
|
||||
picker.customButton = false;
|
||||
});
|
||||
if (!progressManager.isDone) {
|
||||
picker.customLabel = nls.localize('taskQuickPick.cancel', "Stop detecting");
|
||||
picker.onDidCustom(() => {
|
||||
this._providerProgressManager?.cancel();
|
||||
});
|
||||
picker.customButton = true;
|
||||
}
|
||||
}
|
||||
}, cancellationToken).then(async (selection) => {
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
// canceled when there's only one task
|
||||
const task = (await pickEntries)[0];
|
||||
if ((<any>task).task) {
|
||||
selection = <TaskQuickPickEntry>task;
|
||||
}, 1000);
|
||||
pickEntries.then(entries => {
|
||||
clearTimeout(progressTimeout);
|
||||
progressManager?.dispose();
|
||||
picker.busy = false;
|
||||
picker.items = entries;
|
||||
});
|
||||
picker.show();
|
||||
|
||||
return new Promise<TaskQuickPickEntry | undefined | null>(resolve => {
|
||||
this._register(picker.onDidAccept(async () => {
|
||||
let selection = picker.selectedItems ? picker.selectedItems[0] : undefined;
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
// canceled when there's only one task
|
||||
const task = (await pickEntries)[0];
|
||||
if ((<any>task).task) {
|
||||
selection = <TaskQuickPickEntry>task;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!selection) {
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-checks
|
||||
}
|
||||
return selection;
|
||||
picker.dispose();
|
||||
if (!selection) {
|
||||
resolve();
|
||||
}
|
||||
resolve(selection);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2138,7 +2224,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
|
||||
private doRunTaskCommand(tasks?: Task[]): void {
|
||||
this.showIgnoredFoldersMessage().then(() => {
|
||||
this.showQuickPick(tasks ? tasks : this.tasks(),
|
||||
let taskResult: { tasks: Promise<Task[]>, grouped: Promise<TaskMap> } | undefined = undefined;
|
||||
if (!tasks) {
|
||||
taskResult = this.tasksAndGroupedTasks();
|
||||
}
|
||||
this.showQuickPick(tasks ? tasks : taskResult!.tasks,
|
||||
nls.localize('TaskService.pickRunTask', 'Select the task to run'),
|
||||
{
|
||||
label: nls.localize('TaskService.noEntryToRun', 'No task to run found. Configure Tasks...'),
|
||||
@@ -2153,7 +2243,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||
if (task === null) {
|
||||
this.runConfigureTasks();
|
||||
} else {
|
||||
this.run(task, { attachProblemMatcher: true }, TaskRunSource.User).then(undefined, reason => {
|
||||
this.run(task, { attachProblemMatcher: true }, TaskRunSource.User, taskResult?.grouped).then(undefined, reason => {
|
||||
// eat the error, it has already been surfaced to the user and we don't care about it here
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TaskSet } from 'vs/workbench/contrib/tasks/common/tasks';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
export class ProviderProgressMananger extends Disposable {
|
||||
private _onProviderComplete: Emitter<string> = new Emitter();
|
||||
private _stillProviding: Set<string> = new Set();
|
||||
private _totalProviders: number = 0;
|
||||
private _onDone: Emitter<void> = new Emitter();
|
||||
private _isDone: boolean = false;
|
||||
private _showProgress: ((remaining: string[], total: number) => void) | undefined;
|
||||
public canceled: CancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._register(this._onProviderComplete.event(taskType => {
|
||||
this._stillProviding.delete(taskType);
|
||||
if (this._stillProviding.size === 0) {
|
||||
this._isDone = true;
|
||||
this._onDone.fire();
|
||||
}
|
||||
if (this._showProgress) {
|
||||
this._showProgress(Array.from(this._stillProviding), this._totalProviders);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public addProvider(taskType: string, provider: Promise<TaskSet>) {
|
||||
this._totalProviders++;
|
||||
this._stillProviding.add(taskType);
|
||||
provider.then(() => this._onProviderComplete.fire(taskType));
|
||||
}
|
||||
|
||||
public addOnDoneListener(onDoneListener: () => void) {
|
||||
this._register(this._onDone.event(onDoneListener));
|
||||
}
|
||||
|
||||
set showProgress(progressDisplayFunction: (remaining: string[], total: number) => void) {
|
||||
this._showProgress = progressDisplayFunction;
|
||||
this._showProgress(Array.from(this._stillProviding), this._totalProviders);
|
||||
}
|
||||
|
||||
get isDone(): boolean {
|
||||
return this._isDone;
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
this._isDone = true;
|
||||
if (this._showProgress) {
|
||||
this._showProgress([], 0);
|
||||
}
|
||||
this._onDone.fire();
|
||||
this.canceled.cancel();
|
||||
}
|
||||
}
|
||||
@@ -681,6 +681,7 @@ export namespace RunOnOptions {
|
||||
}
|
||||
|
||||
export namespace RunOptions {
|
||||
const properties: MetaData<Tasks.RunOptions, void>[] = [{ property: 'reevaluateOnRerun' }, { property: 'runOn' }, { property: 'instanceLimit' }];
|
||||
export function fromConfiguration(value: RunOptionsConfig | undefined): Tasks.RunOptions {
|
||||
return {
|
||||
reevaluateOnRerun: value ? value.reevaluateOnRerun : true,
|
||||
@@ -688,6 +689,14 @@ export namespace RunOptions {
|
||||
instanceLimit: value ? value.instanceLimit : 1
|
||||
};
|
||||
}
|
||||
|
||||
export function assignProperties(target: Tasks.RunOptions, source: Tasks.RunOptions | undefined): Tasks.RunOptions {
|
||||
return _assignProperties(target, source, properties)!;
|
||||
}
|
||||
|
||||
export function fillProperties(target: Tasks.RunOptions, source: Tasks.RunOptions | undefined): Tasks.RunOptions {
|
||||
return _fillProperties(target, source, properties)!;
|
||||
}
|
||||
}
|
||||
|
||||
interface ParseContext {
|
||||
@@ -1609,6 +1618,7 @@ namespace CustomTask {
|
||||
result.command.presentation = CommandConfiguration.PresentationOptions.assignProperties(
|
||||
result.command.presentation!, configuredProps.configurationProperties.presentation)!;
|
||||
result.command.options = CommandOptions.assignProperties(result.command.options, configuredProps.configurationProperties.options);
|
||||
result.runOptions = RunOptions.assignProperties(result.runOptions, configuredProps.runOptions);
|
||||
|
||||
let contributedConfigProps: Tasks.ConfigurationProperties = contributedTask.configurationProperties;
|
||||
fillProperty(resultConfigProps, contributedConfigProps, 'group');
|
||||
@@ -1621,6 +1631,7 @@ namespace CustomTask {
|
||||
result.command.presentation = CommandConfiguration.PresentationOptions.fillProperties(
|
||||
result.command.presentation!, contributedConfigProps.presentation)!;
|
||||
result.command.options = CommandOptions.fillProperties(result.command.options, contributedConfigProps.options);
|
||||
result.runOptions = RunOptions.fillProperties(result.runOptions, contributedTask.runOptions);
|
||||
|
||||
if (contributedTask.hasDefinedMatchers === true) {
|
||||
result.hasDefinedMatchers = true;
|
||||
|
||||
@@ -114,7 +114,7 @@ export class TerminalViewPane extends ViewPane {
|
||||
}));
|
||||
|
||||
// Force another layout (first is setContainers) since config has changed
|
||||
this.layoutBody(this._terminalContainer.offsetWidth, this._terminalContainer.offsetHeight);
|
||||
this.layoutBody(this._terminalContainer.offsetHeight, this._terminalContainer.offsetWidth);
|
||||
}
|
||||
|
||||
protected layoutBody(height: number, width: number): void {
|
||||
@@ -321,7 +321,7 @@ export class TerminalViewPane extends ViewPane {
|
||||
}
|
||||
// TODO: Can we support ligatures?
|
||||
// dom.toggleClass(this._parentDomElement, 'enable-ligatures', this._terminalService.configHelper.config.fontLigatures);
|
||||
this.layoutBody(this._parentDomElement.offsetWidth, this._parentDomElement.offsetHeight);
|
||||
this.layoutBody(this._parentDomElement.offsetHeight, this._parentDomElement.offsetWidth);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ export class TimelinePane extends ViewPane {
|
||||
}
|
||||
|
||||
this._tree.setChildren(null, undefined);
|
||||
this.message = localize('timeline.loading', 'Loading timeline for ${0}...', basename(uri.fsPath));
|
||||
this.message = localize('timeline.loading', 'Loading timeline for {0}...', basename(uri.fsPath));
|
||||
}, 500, this._uri);
|
||||
}
|
||||
}
|
||||
@@ -291,7 +291,7 @@ export class TimelinePane extends ViewPane {
|
||||
if (!reset) {
|
||||
// TODO: Handle pending request
|
||||
|
||||
if (cursors?.more === false) {
|
||||
if (cursors?.more !== true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -306,6 +306,10 @@ export class TimelinePane extends ViewPane {
|
||||
request?.tokenSource ?? new CancellationTokenSource(), { cacheResults: true }
|
||||
)!;
|
||||
|
||||
if (request === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._pendingRequests.set(source, request);
|
||||
if (!reusingToken) {
|
||||
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
|
||||
@@ -322,6 +326,10 @@ export class TimelinePane extends ViewPane {
|
||||
new CancellationTokenSource(), { cacheResults: true }
|
||||
)!;
|
||||
|
||||
if (request === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._pendingRequests.set(source, request);
|
||||
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
|
||||
|
||||
@@ -20,15 +21,15 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
|
||||
@IAuthenticationTokenService authTokenService: IAuthenticationTokenService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IHostService hostService: IHostService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService);
|
||||
super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService, telemetryService);
|
||||
|
||||
// Sync immediately if there is a local change.
|
||||
this._register(Event.debounce(Event.any<any>(
|
||||
userDataSyncService.onDidChangeLocal,
|
||||
this._register(Event.debounce<string, string[]>(Event.any<string>(
|
||||
Event.map(hostService.onDidChangeFocus, () => 'windowFocus'),
|
||||
instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync,
|
||||
hostService.onDidChangeFocus
|
||||
), () => undefined, 500)(() => this.triggerAutoSync()));
|
||||
userDataSyncService.onDidChangeLocal,
|
||||
), (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerAutoSync(sources)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -88,18 +88,18 @@ const getActivityTitle = (label: string, userDataSyncService: IUserDataSyncServi
|
||||
}
|
||||
return label;
|
||||
};
|
||||
const getIdentityTitle = (label: string, account?: AuthenticationSession): string => {
|
||||
return account ? `${label} (${account.accountName})` : label;
|
||||
const getIdentityTitle = (label: string, authenticationProviderId: string, account: AuthenticationSession | undefined, authenticationService: IAuthenticationService): string => {
|
||||
return account ? `${label} (${authenticationService.getDisplayName(authenticationProviderId)}:${account.accountName})` : label;
|
||||
};
|
||||
const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Sync: Turn on Sync") };
|
||||
const signInCommand = { id: 'workbench.userData.actions.signin', title: localize('sign in', "Sync: Sign in to sync") };
|
||||
const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title(account?: AuthenticationSession) { return getIdentityTitle(localize('stop sync', "Sync: Turn off Sync"), account); } };
|
||||
const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title(authenticationProviderId: string, account: AuthenticationSession | undefined, authenticationService: IAuthenticationService) { return getIdentityTitle(localize('stop sync', "Sync: Turn off Sync"), authenticationProviderId, account, authenticationService); } };
|
||||
const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Sync: Show Settings Conflicts") };
|
||||
const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Sync: Show Keybindings Conflicts") };
|
||||
const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Sync: Configure") };
|
||||
const showSyncActivityCommand = {
|
||||
id: 'workbench.userData.actions.showSyncActivity', title(userDataSyncService: IUserDataSyncService): string {
|
||||
return getActivityTitle(localize('show sync log', "Sync: Show Activity"), userDataSyncService);
|
||||
return getActivityTitle(localize('show sync log', "Sync: Show Log"), userDataSyncService);
|
||||
}
|
||||
};
|
||||
const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Sync: Settings"), };
|
||||
@@ -163,7 +163,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
this.registerActions();
|
||||
this.initializeActiveAccount().then(_ => {
|
||||
if (!isWeb) {
|
||||
this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => userDataAutoSyncService.triggerAutoSync()));
|
||||
this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(source => userDataAutoSyncService.triggerAutoSync([source])));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -226,7 +226,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
if (account) {
|
||||
try {
|
||||
const token = await account.accessToken();
|
||||
const token = await account.getAccessToken();
|
||||
this.authTokenService.setToken(token);
|
||||
this.authenticationState.set(AuthStatus.SignedIn);
|
||||
} catch (e) {
|
||||
@@ -290,7 +290,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
const conflictsEditorInput = this.getConflictsEditorInput(conflictsSource);
|
||||
if (!conflictsEditorInput && !this.conflictsDisposables.has(conflictsSource)) {
|
||||
const conflictsArea = getSyncAreaLabel(conflictsSource);
|
||||
const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea),
|
||||
const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea.toLowerCase()),
|
||||
[
|
||||
{
|
||||
label: localize('accept remote', "Accept Remote"),
|
||||
@@ -409,9 +409,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
const sourceArea = getSyncAreaLabel(error.source);
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('too large', "Disabled syncing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea, sourceArea, '100kb'),
|
||||
message: localize('too large', "Disabled syncing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea.toLowerCase(), sourceArea.toLowerCase(), '100kb'),
|
||||
actions: {
|
||||
primary: [new Action('open sync file', localize('open file', "Open {0} file", sourceArea), undefined, true,
|
||||
primary: [new Action('open sync file', localize('open file', "Open {0} File", sourceArea), undefined, true,
|
||||
() => error.source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))]
|
||||
}
|
||||
});
|
||||
@@ -450,22 +450,31 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
private handleInvalidContentError(source: SyncSource): void {
|
||||
if (!this.invalidContentErrorDisposables.has(source)) {
|
||||
const errorArea = getSyncAreaLabel(source);
|
||||
const handle = this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('errorInvalidConfiguration', "Unable to sync {0} because there are some errors/warnings in the file. Please open the file to correct errors/warnings in it.", errorArea),
|
||||
actions: {
|
||||
primary: [new Action('open sync file', localize('open file', "Open {0} file", errorArea), undefined, true,
|
||||
() => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))]
|
||||
}
|
||||
});
|
||||
this.invalidContentErrorDisposables.set(source, toDisposable(() => {
|
||||
// close the error warning notification
|
||||
handle.close();
|
||||
this.invalidContentErrorDisposables.delete(source);
|
||||
}));
|
||||
if (this.invalidContentErrorDisposables.has(source)) {
|
||||
return;
|
||||
}
|
||||
if (source !== SyncSource.Settings && source !== SyncSource.Keybindings) {
|
||||
return;
|
||||
}
|
||||
const resource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsResource : this.workbenchEnvironmentService.keybindingsResource;
|
||||
if (isEqual(resource, this.editorService.activeEditor?.resource)) {
|
||||
// Do not show notification if the file in error is active
|
||||
return;
|
||||
}
|
||||
const errorArea = getSyncAreaLabel(source);
|
||||
const handle = this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('errorInvalidConfiguration', "Unable to sync {0} because there are some errors/warnings in the file. Please open the file to correct errors/warnings in it.", errorArea.toLowerCase()),
|
||||
actions: {
|
||||
primary: [new Action('open sync file', localize('open file', "Open {0} File", errorArea), undefined, true,
|
||||
() => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))]
|
||||
}
|
||||
});
|
||||
this.invalidContentErrorDisposables.set(source, toDisposable(() => {
|
||||
// close the error warning notification
|
||||
handle.close();
|
||||
this.invalidContentErrorDisposables.delete(source);
|
||||
}));
|
||||
}
|
||||
|
||||
private async updateBadge(): Promise<void> {
|
||||
@@ -493,7 +502,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."),
|
||||
[
|
||||
localize('open doc', "Open Documentation"),
|
||||
localize('confirm', "Continue"),
|
||||
localize('turn on sync', "Turn on Sync"),
|
||||
localize('cancel', "Cancel"),
|
||||
],
|
||||
{
|
||||
@@ -509,11 +518,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
const disposables: DisposableStore = new DisposableStore();
|
||||
const quickPick = this.quickInputService.createQuickPick<ConfigureSyncQuickPickItem>();
|
||||
disposables.add(quickPick);
|
||||
quickPick.title = localize('turn on sync', "Turn on Sync");
|
||||
quickPick.title = localize('turn on title', "Sync: Turn On");
|
||||
quickPick.ok = false;
|
||||
quickPick.customButton = true;
|
||||
if (this.authenticationState.get() === AuthStatus.SignedIn) {
|
||||
quickPick.customLabel = localize('turn on', "Turn on");
|
||||
quickPick.customLabel = localize('turn on', "Turn On");
|
||||
} else {
|
||||
const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId);
|
||||
quickPick.description = localize('sign in and turn on sync detail', "Sign in with your {0} account to synchronize your data across devices.", displayName);
|
||||
@@ -538,6 +547,39 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
private async doTurnOn(): Promise<void> {
|
||||
if (this.authenticationState.get() === AuthStatus.SignedIn) {
|
||||
await new Promise((c, e) => {
|
||||
const disposables: DisposableStore = new DisposableStore();
|
||||
const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId);
|
||||
const quickPick = this.quickInputService.createQuickPick<{ id: string, label: string, description?: string, detail?: string }>();
|
||||
disposables.add(quickPick);
|
||||
const chooseAnotherItemId = 'chooseAnother';
|
||||
quickPick.title = localize('pick account', "{0}: Pick an account", displayName);
|
||||
quickPick.ok = false;
|
||||
quickPick.placeholder = localize('choose account placeholder', "Pick an account for syncing");
|
||||
quickPick.ignoreFocusOut = true;
|
||||
quickPick.items = [{
|
||||
id: 'existing',
|
||||
label: localize('existing', "{0}", this.activeAccount!.accountName),
|
||||
detail: localize('signed in', "Signed in"),
|
||||
}, {
|
||||
id: chooseAnotherItemId,
|
||||
label: localize('choose another', "Use another account")
|
||||
}];
|
||||
disposables.add(quickPick.onDidAccept(async () => {
|
||||
if (quickPick.selectedItems.length) {
|
||||
if (quickPick.selectedItems[0].id === chooseAnotherItemId) {
|
||||
await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount!.id);
|
||||
await this.setActiveAccount(undefined);
|
||||
}
|
||||
quickPick.hide();
|
||||
c();
|
||||
}
|
||||
}));
|
||||
disposables.add(quickPick.onDidHide(() => disposables.dispose()));
|
||||
quickPick.show();
|
||||
});
|
||||
}
|
||||
if (this.authenticationState.get() === AuthStatus.SignedOut) {
|
||||
await this.signIn();
|
||||
}
|
||||
@@ -608,7 +650,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
const result = await this.dialogService.show(
|
||||
Severity.Info,
|
||||
localize('firs time sync', "First time Sync"),
|
||||
localize('firs time sync', "Sync"),
|
||||
[
|
||||
localize('merge', "Merge"),
|
||||
localize('cancel', "Cancel"),
|
||||
@@ -616,7 +658,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
],
|
||||
{
|
||||
cancelId: 1,
|
||||
detail: localize('first time sync detail', "Synchronizing from this device for the first time.\nWould you like to merge or replace with the data from the cloud?"),
|
||||
detail: localize('first time sync detail', "It looks like this is the first time sync is set up.\nWould you like to merge or replace with the data from the cloud?"),
|
||||
}
|
||||
);
|
||||
switch (result.choice) {
|
||||
@@ -638,7 +680,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
type: 'info',
|
||||
message: localize('turn off sync confirmation', "Turn off Sync"),
|
||||
detail: localize('turn off sync detail', "Your settings, keybindings, extensions and UI State will no longer be synced."),
|
||||
primaryButton: localize('turn off', "Turn off"),
|
||||
primaryButton: localize('turn off', "Turn Off"),
|
||||
checkbox: {
|
||||
label: localize('turn off sync everywhere', "Turn off sync on all your devices and clear the data from the cloud.")
|
||||
}
|
||||
@@ -874,7 +916,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
return new Promise((c, e) => {
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const disposables = new DisposableStore();
|
||||
const quickPick = quickInputService.createQuickPick();
|
||||
disposables.add(quickPick);
|
||||
const items: Array<IQuickPickItem | IQuickPickSeparator> = [];
|
||||
if (that.userDataSyncService.conflictsSources.length) {
|
||||
for (const source of that.userDataSyncService.conflictsSources) {
|
||||
@@ -893,9 +937,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
items.push({ id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title });
|
||||
items.push({ id: showSyncActivityCommand.id, label: showSyncActivityCommand.title(that.userDataSyncService) });
|
||||
items.push({ type: 'separator' });
|
||||
items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title(that.activeAccount), });
|
||||
items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title(that.userDataSyncStore!.authenticationProviderId, that.activeAccount, that.authenticationService) });
|
||||
quickPick.items = items;
|
||||
const disposables = new DisposableStore();
|
||||
disposables.add(quickPick.onDidAccept(() => {
|
||||
if (quickPick.selectedItems[0] && quickPick.selectedItems[0].id) {
|
||||
commandService.executeCommand(quickPick.selectedItems[0].id);
|
||||
@@ -918,7 +961,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
constructor() {
|
||||
super({
|
||||
id: stopSyncCommand.id,
|
||||
title: stopSyncCommand.title(that.activeAccount),
|
||||
title: stopSyncCommand.title(that.userDataSyncStore!.authenticationProviderId, that.activeAccount, that.authenticationService),
|
||||
menu: {
|
||||
id: MenuId.CommandPalette,
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT),
|
||||
@@ -1091,8 +1134,8 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
|
||||
? localize('Sync accept remote', "Sync: {0}", acceptRemoteLabel)
|
||||
: localize('Sync accept local', "Sync: {0}", acceptLocalLabel),
|
||||
message: isRemote
|
||||
? localize('confirm replace and overwrite local', "Would you like to accept Remote {0} and replace Local {1}?", syncAreaLabel, syncAreaLabel)
|
||||
: localize('confirm replace and overwrite remote', "Would you like to accept Local {0} and replace Remote {1}?", syncAreaLabel, syncAreaLabel),
|
||||
? localize('confirm replace and overwrite local', "Would you like to accept Remote {0} and replace Local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase())
|
||||
: localize('confirm replace and overwrite remote', "Would you like to accept Local {0} and replace Remote {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()),
|
||||
primaryButton: isRemote ? acceptRemoteLabel : acceptLocalLabel
|
||||
});
|
||||
if (result.confirmed) {
|
||||
|
||||
@@ -16,8 +16,8 @@ import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
|
||||
export class UserDataSyncTrigger extends Disposable {
|
||||
|
||||
private readonly _onDidTriggerSync: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidTriggerSync: Event<void> = this._onDidTriggerSync.event;
|
||||
private readonly _onDidTriggerSync: Emitter<string> = this._register(new Emitter<string>());
|
||||
readonly onDidTriggerSync: Event<string> = this._onDidTriggerSync.event;
|
||||
|
||||
constructor(
|
||||
@IEditorService editorService: IEditorService,
|
||||
@@ -25,37 +25,44 @@ export class UserDataSyncTrigger extends Disposable {
|
||||
@IViewletService viewletService: IViewletService,
|
||||
) {
|
||||
super();
|
||||
this._register(Event.debounce(Event.any<any>(
|
||||
Event.filter(editorService.onDidActiveEditorChange, () => this.isUserDataEditorInput(editorService.activeEditor)),
|
||||
Event.filter(viewletService.onDidViewletOpen, viewlet => this.isUserDataViewlet(viewlet))
|
||||
), () => undefined, 500)(() => this._onDidTriggerSync.fire()));
|
||||
this._register(Event.any<string | undefined>(
|
||||
Event.map(editorService.onDidActiveEditorChange, () => this.getUserDataEditorInputSource(editorService.activeEditor)),
|
||||
Event.map(viewletService.onDidViewletOpen, viewlet => this.getUserDataViewletSource(viewlet))
|
||||
)(source => {
|
||||
if (source) {
|
||||
this._onDidTriggerSync.fire(source);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private isUserDataViewlet(viewlet: IViewlet): boolean {
|
||||
return viewlet.getId() === VIEWLET_ID;
|
||||
private getUserDataViewletSource(viewlet: IViewlet): string | undefined {
|
||||
if (viewlet.getId() === VIEWLET_ID) {
|
||||
return 'extensionsViewlet';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private isUserDataEditorInput(editorInput: IEditorInput | undefined): boolean {
|
||||
private getUserDataEditorInputSource(editorInput: IEditorInput | undefined): string | undefined {
|
||||
if (!editorInput) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
if (editorInput instanceof SettingsEditor2Input) {
|
||||
return true;
|
||||
return 'settingsEditor';
|
||||
}
|
||||
if (editorInput instanceof PreferencesEditorInput) {
|
||||
return true;
|
||||
return 'settingsEditor';
|
||||
}
|
||||
if (editorInput instanceof KeybindingsEditorInput) {
|
||||
return true;
|
||||
return 'keybindingsEditor';
|
||||
}
|
||||
const resource = editorInput.resource;
|
||||
if (isEqual(resource, this.workbenchEnvironmentService.settingsResource)) {
|
||||
return true;
|
||||
return 'settingsEditor';
|
||||
}
|
||||
if (isEqual(resource, this.workbenchEnvironmentService.keybindingsResource)) {
|
||||
return true;
|
||||
return 'keybindingsEditor';
|
||||
}
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ViewsWelcomeExtensionPoint, ViewWelcome, viewsWelcomeExtensionPointDescriptor } from './viewsWelcomeExtensionPoint';
|
||||
import { ViewsWelcomeExtensionPoint, ViewWelcome, viewsWelcomeExtensionPointDescriptor, ViewIdentifierMap } from './viewsWelcomeExtensionPoint';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views';
|
||||
import { Extensions as ViewContainerExtensions, IViewsRegistry, ViewContentPriority } from 'vs/workbench/common/views';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
|
||||
@@ -45,9 +45,11 @@ export class ViewsWelcomeContribution extends Disposable implements IWorkbenchCo
|
||||
}
|
||||
|
||||
for (const welcome of contribution.value) {
|
||||
const disposable = viewsRegistry.registerViewWelcomeContent(welcome.view, {
|
||||
const id = ViewIdentifierMap[welcome.view] ?? welcome.view;
|
||||
const disposable = viewsRegistry.registerViewWelcomeContent(id, {
|
||||
content: welcome.contents,
|
||||
when: ContextKeyExpr.deserialize(welcome.when)
|
||||
when: ContextKeyExpr.deserialize(welcome.when),
|
||||
priority: contribution.description.isBuiltin ? ViewContentPriority.Low : ViewContentPriority.Lowest
|
||||
});
|
||||
|
||||
this.viewWelcomeContents.set(welcome, disposable);
|
||||
|
||||
@@ -20,9 +20,15 @@ export interface ViewWelcome {
|
||||
|
||||
export type ViewsWelcomeExtensionPoint = ViewWelcome[];
|
||||
|
||||
export const ViewIdentifierMap: { [key: string]: string } = {
|
||||
'explorer': 'workbench.explorer.emptyView',
|
||||
'debug': 'workbench.debug.startView',
|
||||
'scm': 'workbench.scm',
|
||||
};
|
||||
|
||||
const viewsWelcomeExtensionPointSchema = Object.freeze<IConfigurationPropertySchema>({
|
||||
type: 'array',
|
||||
description: nls.localize('contributes.viewsWelcome', "Contributed views welcome content."),
|
||||
description: nls.localize('contributes.viewsWelcome', "Contributed views welcome content. Welcome content will be rendered in views whenever they have no meaningful content to display, ie. the File Explorer when no folder is open. Such content is useful as in-product documentation to drive users to use certain features before they are available. A good example would be a `Clone Repository` button in the File Explorer welcome view."),
|
||||
items: {
|
||||
type: 'object',
|
||||
description: nls.localize('contributes.viewsWelcome.view', "Contributed welcome content for a specific view."),
|
||||
@@ -33,15 +39,16 @@ const viewsWelcomeExtensionPointSchema = Object.freeze<IConfigurationPropertySch
|
||||
properties: {
|
||||
[ViewsWelcomeExtensionPointFields.view]: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.viewsWelcome.view.view', "View identifier for this welcome content."),
|
||||
description: nls.localize('contributes.viewsWelcome.view.view', "Target view identifier for this welcome content."),
|
||||
enum: Object.keys(ViewIdentifierMap),
|
||||
},
|
||||
[ViewsWelcomeExtensionPointFields.contents]: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.viewsWelcome.view.contents', "Welcome content."),
|
||||
description: nls.localize('contributes.viewsWelcome.view.contents', "Welcome content to be displayed. The format of the contents is a subset of Markdown, with support for links only."),
|
||||
},
|
||||
[ViewsWelcomeExtensionPointFields.when]: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.viewsWelcome.view.when', "When clause for this welcome content."),
|
||||
description: nls.localize('contributes.viewsWelcome.view.when', "Condition when the welcome content should be displayed."),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ class BulkEditModel implements IDisposable {
|
||||
private _tasks: ModelEditTask[] | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _label: string | undefined,
|
||||
private readonly _editor: ICodeEditor | undefined,
|
||||
private readonly _progress: IProgress<void>,
|
||||
edits: WorkspaceTextEdit[],
|
||||
@@ -232,7 +233,7 @@ class BulkEditModel implements IDisposable {
|
||||
}
|
||||
|
||||
const multiModelEditStackElement = new MultiModelEditStackElement(
|
||||
localize('workspaceEdit', "Workspace Edit"),
|
||||
this._label || localize('workspaceEdit', "Workspace Edit"),
|
||||
tasks.map(t => new EditStackElement(t.model, t.getBeforeCursorState()))
|
||||
);
|
||||
this._undoRedoService.pushElement(multiModelEditStackElement);
|
||||
@@ -250,11 +251,13 @@ type Edit = WorkspaceFileEdit | WorkspaceTextEdit;
|
||||
|
||||
class BulkEdit {
|
||||
|
||||
private readonly _label: string | undefined;
|
||||
private readonly _edits: Edit[] = [];
|
||||
private readonly _editor: ICodeEditor | undefined;
|
||||
private readonly _progress: IProgress<IProgressStep>;
|
||||
|
||||
constructor(
|
||||
label: string | undefined,
|
||||
editor: ICodeEditor | undefined,
|
||||
progress: IProgress<IProgressStep> | undefined,
|
||||
edits: Edit[],
|
||||
@@ -265,6 +268,7 @@ class BulkEdit {
|
||||
@IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) {
|
||||
this._label = label;
|
||||
this._editor = editor;
|
||||
this._progress = progress || Progress.None;
|
||||
this._edits = edits;
|
||||
@@ -361,7 +365,7 @@ class BulkEdit {
|
||||
private async _performTextEdits(edits: WorkspaceTextEdit[], progress: IProgress<void>): Promise<void> {
|
||||
this._logService.debug('_performTextEdits', JSON.stringify(edits));
|
||||
|
||||
const model = this._instaService.createInstance(BulkEditModel, this._editor, progress, edits);
|
||||
const model = this._instaService.createInstance(BulkEditModel, this._label, this._editor, progress, edits);
|
||||
|
||||
await model.prepare();
|
||||
|
||||
@@ -439,7 +443,7 @@ export class BulkEditService implements IBulkEditService {
|
||||
// If the code editor is readonly still allow bulk edits to be applied #68549
|
||||
codeEditor = undefined;
|
||||
}
|
||||
const bulkEdit = this._instaService.createInstance(BulkEdit, codeEditor, options?.progress, edits);
|
||||
const bulkEdit = this._instaService.createInstance(BulkEdit, options?.quotableLabel || options?.label, codeEditor, options?.progress, edits);
|
||||
return bulkEdit.perform().then(() => {
|
||||
return { ariaSummary: bulkEdit.ariaMessage() };
|
||||
}).catch(err => {
|
||||
|
||||
@@ -46,7 +46,7 @@ export const inputsSchema: IJSONSchema = {
|
||||
},
|
||||
password: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('JsonSchema.input.password', "Set to true to show a password prompt that will not show the typed value."),
|
||||
description: nls.localize('JsonSchema.input.password', "Controls if a password input is shown. Password input hides the typed text."),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -92,6 +92,10 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests)
|
||||
}
|
||||
|
||||
return this.doShowSaveConfirm(fileNamesOrResources);
|
||||
}
|
||||
|
||||
protected async doShowSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise<ConfirmResult> {
|
||||
if (fileNamesOrResources.length === 0) {
|
||||
return ConfirmResult.DONT_SAVE;
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ export class SimpleFileDialog {
|
||||
|
||||
public async showOpenDialog(options: IOpenDialogOptions = {}): Promise<URI | undefined> {
|
||||
this.scheme = this.getScheme(options.availableFileSystems, options.defaultUri);
|
||||
this.userHome = await this.remotePathService.userHome;
|
||||
this.userHome = await this.getUserHome();
|
||||
const newOptions = this.getOptions(options);
|
||||
if (!newOptions) {
|
||||
return Promise.resolve(undefined);
|
||||
@@ -167,7 +167,7 @@ export class SimpleFileDialog {
|
||||
|
||||
public async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
|
||||
this.scheme = this.getScheme(options.availableFileSystems, options.defaultUri);
|
||||
this.userHome = await this.remotePathService.userHome;
|
||||
this.userHome = await this.getUserHome();
|
||||
this.requiresTrailing = true;
|
||||
const newOptions = this.getOptions(options, true);
|
||||
if (!newOptions) {
|
||||
@@ -231,6 +231,13 @@ export class SimpleFileDialog {
|
||||
return this.remoteAgentEnvironment;
|
||||
}
|
||||
|
||||
private async getUserHome(): Promise<URI> {
|
||||
if (this.scheme !== Schemas.file) {
|
||||
return this.remotePathService.userHome;
|
||||
}
|
||||
return URI.from({ scheme: this.scheme, path: this.environmentService.userHome });
|
||||
}
|
||||
|
||||
private async pickResource(isSave: boolean = false): Promise<URI | undefined> {
|
||||
this.allowFolderSelection = !!this.options.canSelectFolders;
|
||||
this.allowFileSelection = !!this.options.canSelectFiles;
|
||||
|
||||
@@ -198,7 +198,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
}
|
||||
}
|
||||
|
||||
return super.showSaveConfirm(fileNamesOrResources);
|
||||
return super.doShowSaveConfirm(fileNamesOrResources);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
|
||||
get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); }
|
||||
|
||||
@memoize
|
||||
get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, '.sync'); }
|
||||
get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'sync'); }
|
||||
|
||||
@memoize
|
||||
get settingsSyncPreviewResource(): URI { return joinPath(this.userDataSyncHome, 'settings.json'); }
|
||||
|
||||
@@ -209,7 +209,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));
|
||||
}
|
||||
if (!isLanguagePackExtension(manifest) && !canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) {
|
||||
const error = new Error(localize('cannot be installed', "Cannot install '{0}' extension since it cannot be enabled in the remote server.", gallery.displayName || gallery.name));
|
||||
const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension has defined that it cannot run on the remote server.", gallery.displayName || gallery.name));
|
||||
error.name = INSTALL_ERROR_NOT_SUPPORTED;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./media/progressService';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose, DisposableStore, MutableDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, DisposableStore, MutableDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator, IProgressWindowOptions } from 'vs/platform/progress/common/progress';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { StatusbarAlignment, IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar';
|
||||
@@ -24,6 +24,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { EventHelper } from 'vs/base/browser/dom';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { parseLinkedText } from 'vs/base/common/linkedText';
|
||||
|
||||
export class ProgressService extends Disposable implements IProgressService {
|
||||
|
||||
@@ -191,6 +192,46 @@ export class ProgressService extends Disposable implements IProgressService {
|
||||
}
|
||||
};
|
||||
|
||||
const createWindowProgress = () => {
|
||||
|
||||
// Create a promise that we can resolve as needed
|
||||
// when the outside calls dispose on us
|
||||
let promiseResolve: () => void;
|
||||
const promise = new Promise<R>(resolve => promiseResolve = resolve);
|
||||
|
||||
this.withWindowProgress<R>({
|
||||
location: ProgressLocation.Window,
|
||||
title: options.title ? parseLinkedText(options.title).toString() : undefined, // convert markdown links => string
|
||||
command: 'notifications.showList'
|
||||
}, progress => {
|
||||
|
||||
function reportProgress(step: IProgressStep) {
|
||||
if (step.message) {
|
||||
progress.report({
|
||||
message: parseLinkedText(step.message).toString() // convert markdown links => string
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Apply any progress that was made already
|
||||
if (progressStateModel.step) {
|
||||
reportProgress(progressStateModel.step);
|
||||
}
|
||||
|
||||
// Continue to report progress as it happens
|
||||
const onDidReportListener = progressStateModel.onDidReport(step => reportProgress(step));
|
||||
promise.finally(() => onDidReportListener.dispose());
|
||||
|
||||
// When the progress model gets disposed, we are done as well
|
||||
Event.once(progressStateModel.onDispose)(() => promiseResolve());
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
// Dispose means completing our promise
|
||||
return toDisposable(() => promiseResolve());
|
||||
};
|
||||
|
||||
const createNotification = (message: string, increment?: number): INotificationHandle => {
|
||||
const notificationDisposables = new DisposableStore();
|
||||
|
||||
@@ -229,19 +270,34 @@ export class ProgressService extends Disposable implements IProgressService {
|
||||
primaryActions.push(cancelAction);
|
||||
}
|
||||
|
||||
const handle = this.notificationService.notify({
|
||||
const notification = this.notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
message,
|
||||
source: options.source,
|
||||
actions: { primary: primaryActions, secondary: secondaryActions }
|
||||
actions: { primary: primaryActions, secondary: secondaryActions },
|
||||
progress: typeof increment === 'number' && increment >= 0 ? { total: 100, worked: increment } : { infinite: true }
|
||||
});
|
||||
|
||||
updateProgress(handle, increment);
|
||||
// Switch to window based progress once the notification
|
||||
// changes visibility to hidden and is still ongoing.
|
||||
// Remove that window based progress once the notification
|
||||
// shows again.
|
||||
let windowProgressDisposable: IDisposable | undefined = undefined;
|
||||
notificationDisposables.add(notification.onDidChangeVisibility(visible => {
|
||||
|
||||
// Clear any previous running window progress
|
||||
dispose(windowProgressDisposable);
|
||||
|
||||
// Create new window progress if notification got hidden
|
||||
if (!visible && !progressStateModel.done) {
|
||||
windowProgressDisposable = createWindowProgress();
|
||||
}
|
||||
}));
|
||||
|
||||
// Clear upon dispose
|
||||
Event.once(handle.onDidClose)(() => notificationDisposables.dispose());
|
||||
Event.once(notification.onDidClose)(() => notificationDisposables.dispose());
|
||||
|
||||
return handle;
|
||||
return notification;
|
||||
};
|
||||
|
||||
const updateProgress = (notification: INotificationHandle, increment?: number): void => {
|
||||
|
||||
@@ -251,6 +251,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
async load(options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
|
||||
this.logService.trace('[text file model] load() - enter', this.resource.toString(true));
|
||||
|
||||
// Return early if we are disposed
|
||||
if (this.isDisposed()) {
|
||||
this.logService.trace('[text file model] load() - exit - without loading because model is disposed', this.resource.toString(true));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// It is very important to not reload the model when the model is dirty.
|
||||
// We also only want to reload the model from the disk if no save is pending
|
||||
// to avoid data loss.
|
||||
@@ -359,7 +366,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
private loadFromContent(content: ITextFileStreamContent, options?: ITextFileLoadOptions, fromBackup?: boolean): TextFileEditorModel {
|
||||
this.logService.trace('[text file model] load() - resolved content', this.resource.toString(true));
|
||||
this.logService.trace('[text file model] loadFromContent() - enter', this.resource.toString(true));
|
||||
|
||||
// Return early if we are disposed
|
||||
if (this.isDisposed()) {
|
||||
this.logService.trace('[text file model] loadFromContent() - exit - because model is disposed', this.resource.toString(true));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Update our resolved disk stat model
|
||||
this.updateLastResolvedFileStat({
|
||||
@@ -405,7 +419,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
private doCreateTextModel(resource: URI, value: ITextBufferFactory, fromBackup: boolean): void {
|
||||
this.logService.trace('[text file model] load() - created text editor model', this.resource.toString(true));
|
||||
this.logService.trace('[text file model] doCreateTextModel()', this.resource.toString(true));
|
||||
|
||||
// Create model
|
||||
const textModel = this.createTextEditorModel(value, resource, this.preferredMode);
|
||||
@@ -420,7 +434,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
private doUpdateTextModel(value: ITextBufferFactory): void {
|
||||
this.logService.trace('[text file model] load() - updated text editor model', this.resource.toString(true));
|
||||
this.logService.trace('[text file model] doUpdateTextModel()', this.resource.toString(true));
|
||||
|
||||
// Update model value in a block that ignores content change events for dirty tracking
|
||||
this.ignoreDirtyOnModelContentChange = true;
|
||||
@@ -703,7 +717,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveOptions): void {
|
||||
this.logService.trace(`[text file model] doSave(${versionId}) - after write()`, this.resource.toString(true));
|
||||
|
||||
// Updated resolved stat with updated stat
|
||||
this.updateLastResolvedFileStat(stat);
|
||||
@@ -906,6 +919,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.logService.trace('[text file model] dispose()', this.resource.toString(true));
|
||||
|
||||
this.disposed = true;
|
||||
this.inConflictMode = false;
|
||||
this.inOrphanMode = false;
|
||||
|
||||
@@ -24,8 +24,8 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
this.channel = sharedProcessService.getChannel('userDataAutoSync');
|
||||
}
|
||||
|
||||
triggerAutoSync(): Promise<void> {
|
||||
return this.channel.call('triggerAutoSync');
|
||||
triggerAutoSync(sources: string[]): Promise<void> {
|
||||
return this.channel.call('triggerAutoSync', [sources]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
private _onDidChangeStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
|
||||
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangeStatus.event;
|
||||
|
||||
get onDidChangeLocal(): Event<void> { return this.channel.listen('onDidChangeLocal'); }
|
||||
get onDidChangeLocal(): Event<SyncSource> { return this.channel.listen<SyncSource>('onDidChangeLocal'); }
|
||||
|
||||
private _conflictsSources: SyncSource[] = [];
|
||||
get conflictsSources(): SyncSource[] { return this._conflictsSources; }
|
||||
|
||||
@@ -23,6 +23,7 @@ suite('Notifications', () => {
|
||||
let item3 = NotificationViewItem.create({ severity: Severity.Info, message: 'Info Message' })!;
|
||||
let item4 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', source: 'Source' })!;
|
||||
let item5 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', actions: { primary: [new Action('id', 'label')] } })!;
|
||||
let item6 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', actions: { primary: [new Action('id', 'label')] }, progress: { infinite: true } })!;
|
||||
|
||||
assert.equal(item1.equals(item1), true);
|
||||
assert.equal(item2.equals(item2), true);
|
||||
@@ -35,6 +36,10 @@ suite('Notifications', () => {
|
||||
assert.equal(item1.equals(item4), false);
|
||||
assert.equal(item1.equals(item5), false);
|
||||
|
||||
// Progress
|
||||
assert.equal(item1.hasProgress, false);
|
||||
assert.equal(item6.hasProgress, true);
|
||||
|
||||
// Message Box
|
||||
assert.equal(item5.canCollapse, false);
|
||||
assert.equal(item5.expanded, true);
|
||||
@@ -93,6 +98,17 @@ suite('Notifications', () => {
|
||||
|
||||
assert.equal(called, 1);
|
||||
|
||||
called = 0;
|
||||
item1.onDidChangeVisibility(e => {
|
||||
called++;
|
||||
});
|
||||
|
||||
item1.updateVisibility(true);
|
||||
item1.updateVisibility(false);
|
||||
item1.updateVisibility(false);
|
||||
|
||||
assert.equal(called, 2);
|
||||
|
||||
called = 0;
|
||||
item1.onDidClose(() => {
|
||||
called++;
|
||||
@@ -102,8 +118,8 @@ suite('Notifications', () => {
|
||||
assert.equal(called, 1);
|
||||
|
||||
// Error with Action
|
||||
let item6 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!;
|
||||
assert.equal(item6.actions!.primary!.length, 1);
|
||||
let item7 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!;
|
||||
assert.equal(item7.actions!.primary!.length, 1);
|
||||
|
||||
// Filter
|
||||
let item8 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.SILENT)!;
|
||||
|
||||
Reference in New Issue
Block a user