mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-12 02:58:31 -05:00
Merge from vscode 79a1f5a5ca0c6c53db617aa1fa5a2396d2caebe2
This commit is contained in:
@@ -7,7 +7,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as nls from 'vs/nls';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { IAuthenticationService, AllowedExtension, readAllowedExtensions } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
@@ -18,43 +18,56 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
|
||||
interface AllowedExtension {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const accountUsages = new Map<string, { [accountName: string]: string[] }>();
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
|
||||
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git'];
|
||||
|
||||
function addAccountUsage(providerId: string, accountName: string, extensionOrFeatureName: string) {
|
||||
const providerAccountUsage = accountUsages.get(providerId);
|
||||
if (!providerAccountUsage) {
|
||||
accountUsages.set(providerId, { [accountName]: [extensionOrFeatureName] });
|
||||
} else {
|
||||
if (providerAccountUsage[accountName]) {
|
||||
if (!providerAccountUsage[accountName].includes(extensionOrFeatureName)) {
|
||||
providerAccountUsage[accountName].push(extensionOrFeatureName);
|
||||
}
|
||||
} else {
|
||||
providerAccountUsage[accountName] = [extensionOrFeatureName];
|
||||
}
|
||||
|
||||
accountUsages.set(providerId, providerAccountUsage);
|
||||
}
|
||||
interface IAccountUsage {
|
||||
extensionId: string;
|
||||
extensionName: string;
|
||||
lastUsed: number;
|
||||
}
|
||||
|
||||
function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] {
|
||||
let trustedExtensions: AllowedExtension[] = [];
|
||||
try {
|
||||
const trustedExtensionSrc = storageService.get(`${providerId}-${accountName}`, StorageScope.GLOBAL);
|
||||
if (trustedExtensionSrc) {
|
||||
trustedExtensions = JSON.parse(trustedExtensionSrc);
|
||||
function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] {
|
||||
const accountKey = `${providerId}-${accountName}-usages`;
|
||||
const storedUsages = storageService.get(accountKey, StorageScope.GLOBAL);
|
||||
let usages: IAccountUsage[] = [];
|
||||
if (storedUsages) {
|
||||
try {
|
||||
usages = JSON.parse(storedUsages);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
} catch (err) { }
|
||||
}
|
||||
|
||||
return trustedExtensions;
|
||||
return usages;
|
||||
}
|
||||
|
||||
function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void {
|
||||
const accountKey = `${providerId}-${accountName}-usages`;
|
||||
storageService.remove(accountKey, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
function addAccountUsage(storageService: IStorageService, providerId: string, accountName: string, extensionId: string, extensionName: string) {
|
||||
const accountKey = `${providerId}-${accountName}-usages`;
|
||||
const usages = readAccountUsages(storageService, providerId, accountName);
|
||||
|
||||
const existingUsageIndex = usages.findIndex(usage => usage.extensionId === extensionId);
|
||||
if (existingUsageIndex > -1) {
|
||||
usages.splice(existingUsageIndex, 1, {
|
||||
extensionId,
|
||||
extensionName,
|
||||
lastUsed: Date.now()
|
||||
});
|
||||
} else {
|
||||
usages.push({
|
||||
extensionId,
|
||||
extensionName,
|
||||
lastUsed: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
storageService.store(accountKey, JSON.stringify(usages), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
export class MainThreadAuthenticationProvider extends Disposable {
|
||||
@@ -66,8 +79,10 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
private readonly _proxy: ExtHostAuthenticationShape,
|
||||
public readonly id: string,
|
||||
public readonly displayName: string,
|
||||
public readonly supportsMultipleAccounts: boolean,
|
||||
private readonly notificationService: INotificationService,
|
||||
private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
|
||||
private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
private readonly storageService: IStorageService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -81,12 +96,17 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
}
|
||||
|
||||
private manageTrustedExtensions(quickInputService: IQuickInputService, storageService: IStorageService, accountName: string) {
|
||||
const quickPick = quickInputService.createQuickPick<{ label: string, extension: AllowedExtension }>();
|
||||
const quickPick = quickInputService.createQuickPick<{ label: string, description: string, extension: AllowedExtension }>();
|
||||
quickPick.canSelectMany = true;
|
||||
const allowedExtensions = readAllowedExtensions(storageService, this.id, accountName);
|
||||
const usages = readAccountUsages(storageService, this.id, accountName);
|
||||
const items = allowedExtensions.map(extension => {
|
||||
const usage = usages.find(usage => extension.id === usage.extensionId);
|
||||
return {
|
||||
label: extension.name,
|
||||
description: usage
|
||||
? nls.localize('accountLastUsedDate', "Last used this account {0}", fromNow(usage.lastUsed, true))
|
||||
: nls.localize('notUsed', "Has not used this account"),
|
||||
extension
|
||||
};
|
||||
});
|
||||
@@ -110,24 +130,6 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
quickPick.show();
|
||||
}
|
||||
|
||||
private showUsage(quickInputService: IQuickInputService, accountName: string) {
|
||||
const quickPick = quickInputService.createQuickPick();
|
||||
const providerUsage = accountUsages.get(this.id);
|
||||
const accountUsage = (providerUsage || {})[accountName] || [];
|
||||
|
||||
quickPick.items = accountUsage.map(extensionOrFeature => {
|
||||
return {
|
||||
label: extensionOrFeature
|
||||
};
|
||||
});
|
||||
|
||||
quickPick.onDidHide(() => {
|
||||
quickPick.dispose();
|
||||
});
|
||||
|
||||
quickPick.show();
|
||||
}
|
||||
|
||||
private async registerCommandsAndContextMenuItems(): Promise<void> {
|
||||
const sessions = await this._proxy.$getSessions(this.id);
|
||||
sessions.forEach(session => this.registerSession(session));
|
||||
@@ -163,10 +165,9 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
const dialogService = accessor.get(IDialogService);
|
||||
|
||||
const quickPick = quickInputService.createQuickPick();
|
||||
const showUsage = nls.localize('showUsage', "Show Extensions and Features Using This Account");
|
||||
const manage = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions");
|
||||
const signOut = nls.localize('signOut', "Sign Out");
|
||||
const items = ([{ label: showUsage }, { label: manage }, { label: signOut }]);
|
||||
const items = ([{ label: manage }, { label: signOut }]);
|
||||
|
||||
quickPick.items = items;
|
||||
|
||||
@@ -180,10 +181,6 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
this.manageTrustedExtensions(quickInputService, storageService, session.account.displayName);
|
||||
}
|
||||
|
||||
if (selected.label === showUsage) {
|
||||
this.showUsage(quickInputService, session.account.displayName);
|
||||
}
|
||||
|
||||
quickPick.dispose();
|
||||
});
|
||||
|
||||
@@ -199,39 +196,24 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
}
|
||||
|
||||
async signOut(dialogService: IDialogService, session: modes.AuthenticationSession): Promise<void> {
|
||||
const providerUsage = accountUsages.get(this.id);
|
||||
const accountUsage = (providerUsage || {})[session.account.displayName] || [];
|
||||
const accountUsages = readAccountUsages(this.storageService, this.id, session.account.displayName);
|
||||
const sessionsForAccount = this._accounts.get(session.account.displayName);
|
||||
|
||||
// Skip dialog if nothing is using the account
|
||||
if (!accountUsage.length) {
|
||||
accountUsages.set(this.id, { [session.account.displayName]: [] });
|
||||
sessionsForAccount?.forEach(sessionId => this.logout(sessionId));
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await dialogService.confirm({
|
||||
title: nls.localize('signOutConfirm', "Sign out of {0}", session.account.displayName),
|
||||
message: nls.localize('signOutMessage', "The account {0} is currently used by: \n\n{1}\n\n Sign out of these features?", session.account.displayName, accountUsage.join('\n'))
|
||||
message: accountUsages.length
|
||||
? nls.localize('signOutMessagve', "The account {0} has been used by: \n\n{1}\n\n Sign out of these features?", session.account.displayName, accountUsages.map(usage => usage.extensionName).join('\n'))
|
||||
: nls.localize('signOutMessageSimple', "Sign out of {0}?", session.account.displayName)
|
||||
});
|
||||
|
||||
if (result.confirmed) {
|
||||
accountUsages.set(this.id, { [session.account.displayName]: [] });
|
||||
sessionsForAccount?.forEach(sessionId => this.logout(sessionId));
|
||||
removeAccountUsage(this.storageService, this.id, session.account.displayName);
|
||||
}
|
||||
}
|
||||
|
||||
async getSessions(): Promise<ReadonlyArray<modes.AuthenticationSession>> {
|
||||
return (await this._proxy.$getSessions(this.id)).map(session => {
|
||||
return {
|
||||
id: session.id,
|
||||
account: session.account,
|
||||
getAccessToken: () => {
|
||||
addAccountUsage(this.id, session.account.displayName, nls.localize('sync', "Preferences Sync"));
|
||||
return this._proxy.$getSessionAccessToken(this.id, session.id);
|
||||
}
|
||||
};
|
||||
});
|
||||
return this._proxy.$getSessions(this.id);
|
||||
}
|
||||
|
||||
async updateSessionItems(event: modes.AuthenticationSessionsChangeEvent): Promise<void> {
|
||||
@@ -262,13 +244,7 @@ export class MainThreadAuthenticationProvider extends Disposable {
|
||||
}
|
||||
|
||||
login(scopes: string[]): Promise<modes.AuthenticationSession> {
|
||||
return this._proxy.$login(this.id, scopes).then(session => {
|
||||
return {
|
||||
id: session.id,
|
||||
account: session.account,
|
||||
getAccessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id)
|
||||
};
|
||||
});
|
||||
return this._proxy.$login(this.id, scopes);
|
||||
}
|
||||
|
||||
async logout(sessionId: string): Promise<void> {
|
||||
@@ -294,14 +270,31 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication);
|
||||
|
||||
this._register(this.authenticationService.onDidChangeSessions(e => {
|
||||
this._proxy.$onDidChangeAuthenticationSessions(e.providerId, e.event);
|
||||
}));
|
||||
|
||||
this._register(this.authenticationService.onDidRegisterAuthenticationProvider(providerId => {
|
||||
this._proxy.$onDidChangeAuthenticationProviders([providerId], []);
|
||||
}));
|
||||
|
||||
this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(providerId => {
|
||||
this._proxy.$onDidChangeAuthenticationProviders([], [providerId]);
|
||||
}));
|
||||
}
|
||||
|
||||
async $registerAuthenticationProvider(id: string, displayName: string): Promise<void> {
|
||||
const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, this.notificationService, this.storageKeysSyncRegistryService);
|
||||
$getProviderIds(): Promise<string[]> {
|
||||
return Promise.resolve(this.authenticationService.getProviderIds());
|
||||
}
|
||||
|
||||
async $registerAuthenticationProvider(id: string, displayName: string, supportsMultipleAccounts: boolean): Promise<void> {
|
||||
const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, supportsMultipleAccounts, this.notificationService, this.storageKeysSyncRegistryService, this.storageService);
|
||||
await provider.initialize();
|
||||
this.authenticationService.registerAuthenticationProvider(id, provider);
|
||||
}
|
||||
@@ -310,21 +303,142 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
this.authenticationService.unregisterAuthenticationProvider(id);
|
||||
}
|
||||
|
||||
$onDidChangeSessions(id: string, event: modes.AuthenticationSessionsChangeEvent): void {
|
||||
$sendDidChangeSessions(id: string, event: modes.AuthenticationSessionsChangeEvent): void {
|
||||
this.authenticationService.sessionsUpdate(id, event);
|
||||
}
|
||||
|
||||
async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
|
||||
addAccountUsage(providerId, accountName, extensionName);
|
||||
$getSessions(id: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
|
||||
return this.authenticationService.getSessions(id);
|
||||
}
|
||||
|
||||
$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession> {
|
||||
return this.authenticationService.login(providerId, scopes);
|
||||
}
|
||||
|
||||
$logout(providerId: string, sessionId: string): Promise<void> {
|
||||
return this.authenticationService.logout(providerId, sessionId);
|
||||
}
|
||||
|
||||
async $requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void> {
|
||||
return this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName);
|
||||
}
|
||||
|
||||
async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone: boolean, clearSessionPreference: boolean }): Promise<modes.AuthenticationSession | undefined> {
|
||||
const orderedScopes = scopes.sort().join(' ');
|
||||
const sessions = (await this.$getSessions(providerId)).filter(session => session.scopes.sort().join(' ') === orderedScopes);
|
||||
const displayName = this.authenticationService.getDisplayName(providerId);
|
||||
|
||||
if (sessions.length) {
|
||||
if (!this.authenticationService.supportsMultipleAccounts(providerId)) {
|
||||
const session = sessions[0];
|
||||
const allowed = await this.$getSessionsPrompt(providerId, session.account.displayName, displayName, extensionId, extensionName);
|
||||
if (allowed) {
|
||||
return session;
|
||||
} else {
|
||||
throw new Error('User did not consent to login.');
|
||||
}
|
||||
}
|
||||
|
||||
// On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid
|
||||
const selected = await this.$selectSession(providerId, displayName, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
|
||||
return sessions.find(session => session.id === selected.id);
|
||||
} else {
|
||||
if (options.createIfNone) {
|
||||
const isAllowed = await this.$loginPrompt(displayName, extensionName);
|
||||
if (!isAllowed) {
|
||||
throw new Error('User did not consent to login.');
|
||||
}
|
||||
|
||||
const session = await this.authenticationService.login(providerId, scopes);
|
||||
await this.$setTrustedExtension(providerId, session.account.displayName, extensionId, extensionName);
|
||||
return session;
|
||||
} else {
|
||||
await this.$requestNewSession(providerId, scopes, extensionId, extensionName);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async $selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession> {
|
||||
if (!potentialSessions.length) {
|
||||
throw new Error('No potential sessions found');
|
||||
}
|
||||
|
||||
if (clearSessionPreference) {
|
||||
this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
|
||||
} else {
|
||||
const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
|
||||
if (existingSessionPreference) {
|
||||
const matchingSession = potentialSessions.find(session => session.id === existingSessionPreference);
|
||||
if (matchingSession) {
|
||||
const allowed = await this.$getSessionsPrompt(providerId, matchingSession.account.displayName, providerName, extensionId, extensionName);
|
||||
if (allowed) {
|
||||
return matchingSession;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const quickPick = this.quickInputService.createQuickPick<{ label: string, session?: modes.AuthenticationSession }>();
|
||||
quickPick.ignoreFocusOut = true;
|
||||
const items: { label: string, session?: modes.AuthenticationSession }[] = potentialSessions.map(session => {
|
||||
return {
|
||||
label: session.account.displayName,
|
||||
session
|
||||
};
|
||||
});
|
||||
|
||||
items.push({
|
||||
label: nls.localize('useOtherAccount', "Sign in to another account")
|
||||
});
|
||||
|
||||
quickPick.items = items;
|
||||
quickPick.title = nls.localize('selectAccount', "The extension '{0}' wants to access a {1} account", extensionName, providerName);
|
||||
quickPick.placeholder = nls.localize('getSessionPlateholder', "Select an account for '{0}' to use or Esc to cancel", extensionName);
|
||||
|
||||
quickPick.onDidAccept(async _ => {
|
||||
const selected = quickPick.selectedItems[0];
|
||||
|
||||
const session = selected.session ?? await this.authenticationService.login(providerId, scopes);
|
||||
|
||||
const accountName = session.account.displayName;
|
||||
|
||||
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
|
||||
if (!allowList.find(allowed => allowed.id === extensionId)) {
|
||||
allowList.push({ id: extensionId, name: extensionName });
|
||||
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL);
|
||||
|
||||
quickPick.dispose();
|
||||
resolve(session);
|
||||
});
|
||||
|
||||
quickPick.onDidHide(_ => {
|
||||
if (!quickPick.selectedItems[0]) {
|
||||
reject('User did not consent to account access');
|
||||
}
|
||||
|
||||
quickPick.dispose();
|
||||
});
|
||||
|
||||
quickPick.show();
|
||||
});
|
||||
}
|
||||
|
||||
async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
|
||||
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
|
||||
const extensionData = allowList.find(extension => extension.id === extensionId);
|
||||
if (extensionData) {
|
||||
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
|
||||
return true;
|
||||
}
|
||||
|
||||
const remoteConnection = this.remoteAgentService.getConnection();
|
||||
if (remoteConnection && remoteConnection.remoteAuthority && remoteConnection.remoteAuthority.startsWith('vsonline') && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) {
|
||||
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -339,6 +453,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
|
||||
const allow = choice === 0;
|
||||
if (allow) {
|
||||
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
|
||||
allowList.push({ id: extensionId, name: extensionName });
|
||||
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
@@ -94,8 +94,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
|
||||
}, {
|
||||
allowScripts: options.enableScripts,
|
||||
localResourceRoots: options.localResourceRoots ? options.localResourceRoots.map(uri => URI.revive(uri)) : undefined
|
||||
});
|
||||
webview.extension = { id: extensionId, location: URI.revive(extensionLocation) };
|
||||
}, { id: extensionId, location: URI.revive(extensionLocation) });
|
||||
|
||||
const webviewZone = new EditorWebviewZone(editor, line, height, webview);
|
||||
|
||||
@@ -128,7 +127,10 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
|
||||
|
||||
$setOptions(handle: number, options: modes.IWebviewOptions): void {
|
||||
const inset = this.getInset(handle);
|
||||
inset.webview.contentOptions = options;
|
||||
inset.webview.contentOptions = {
|
||||
...options,
|
||||
localResourceRoots: options.localResourceRoots?.map(components => URI.from(components)),
|
||||
};
|
||||
}
|
||||
|
||||
async $postMessage(handle: number, value: any): Promise<boolean> {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { MainThreadDiagnosticsShape, MainContext, IExtHostContext, ExtHostDiagnosticsShape, ExtHostContext } from '../common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadDiagnostics)
|
||||
export class MainThreadDiagnostics implements MainThreadDiagnosticsShape {
|
||||
@@ -15,15 +16,15 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape {
|
||||
private readonly _activeOwners = new Set<string>();
|
||||
|
||||
private readonly _proxy: ExtHostDiagnosticsShape;
|
||||
private readonly _markerService: IMarkerService;
|
||||
private readonly _markerListener: IDisposable;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IMarkerService markerService: IMarkerService
|
||||
@IMarkerService private readonly _markerService: IMarkerService,
|
||||
@IUriIdentityService private readonly _uriIdentService: IUriIdentityService,
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDiagnostics);
|
||||
this._markerService = markerService;
|
||||
|
||||
this._markerListener = this._markerService.onMarkerChanged(this._forwardMarkers, this);
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape {
|
||||
}
|
||||
}
|
||||
}
|
||||
this._markerService.changeOne(owner, URI.revive(uri), markers);
|
||||
this._markerService.changeOne(owner, this._uriIdentService.asCanonicalUri(URI.revive(uri)), markers);
|
||||
}
|
||||
this._activeOwners.add(owner);
|
||||
}
|
||||
|
||||
@@ -10,17 +10,19 @@ import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IModelService, shouldSynchronizeModel } from 'vs/editor/common/services/modelService';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IFileService, FileOperation } from 'vs/platform/files/common/files';
|
||||
import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors';
|
||||
import { ExtHostContext, ExtHostDocumentsShape, IExtHostContext, MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ITextEditorModel } from 'vs/workbench/common/editor';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { toLocalResource } from 'vs/base/common/resources';
|
||||
import { toLocalResource, isEqualOrParent, extUri } from 'vs/base/common/resources';
|
||||
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
|
||||
export class BoundModelReferenceCollection {
|
||||
|
||||
private _data = new Array<{ length: number, dispose(): void }>();
|
||||
private _data = new Array<{ uri: URI, length: number, dispose(): void }>();
|
||||
private _length = 0;
|
||||
|
||||
constructor(
|
||||
@@ -34,10 +36,18 @@ export class BoundModelReferenceCollection {
|
||||
this._data = dispose(this._data);
|
||||
}
|
||||
|
||||
add(ref: IReference<ITextEditorModel>): void {
|
||||
remove(uri: URI): void {
|
||||
for (const entry of [...this._data] /* copy array because dispose will modify it */) {
|
||||
if (isEqualOrParent(entry.uri, uri)) {
|
||||
entry.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(uri: URI, ref: IReference<ITextEditorModel>): void {
|
||||
const length = ref.object.textEditorModel.getValueLength();
|
||||
let handle: any;
|
||||
let entry: { length: number, dispose(): void };
|
||||
let entry: { uri: URI, length: number, dispose(): void };
|
||||
const dispose = () => {
|
||||
const idx = this._data.indexOf(entry);
|
||||
if (idx >= 0) {
|
||||
@@ -48,7 +58,7 @@ export class BoundModelReferenceCollection {
|
||||
}
|
||||
};
|
||||
handle = setTimeout(dispose, this._maxAge);
|
||||
entry = { length, dispose };
|
||||
entry = { uri, length, dispose };
|
||||
|
||||
this._data.push(entry);
|
||||
this._length += length;
|
||||
@@ -69,12 +79,13 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
private readonly _textFileService: ITextFileService;
|
||||
private readonly _fileService: IFileService;
|
||||
private readonly _environmentService: IWorkbenchEnvironmentService;
|
||||
private readonly _uriIdentityService: IUriIdentityService;
|
||||
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private _modelToDisposeMap: { [modelUrl: string]: IDisposable; };
|
||||
private readonly _proxy: ExtHostDocumentsShape;
|
||||
private readonly _modelIsSynced = new Set<string>();
|
||||
private _modelReferenceCollection = new BoundModelReferenceCollection();
|
||||
private readonly _modelReferenceCollection = new BoundModelReferenceCollection();
|
||||
|
||||
constructor(
|
||||
documentsAndEditors: MainThreadDocumentsAndEditors,
|
||||
@@ -83,13 +94,16 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IFileService fileService: IFileService,
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IUriIdentityService uriIdentityService: IUriIdentityService,
|
||||
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
|
||||
) {
|
||||
this._modelService = modelService;
|
||||
this._textModelResolverService = textModelResolverService;
|
||||
this._textFileService = textFileService;
|
||||
this._fileService = fileService;
|
||||
this._environmentService = environmentService;
|
||||
this._uriIdentityService = uriIdentityService;
|
||||
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocuments);
|
||||
|
||||
@@ -109,6 +123,12 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
}
|
||||
}));
|
||||
|
||||
this._toDispose.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => {
|
||||
if (e.source && (e.operation === FileOperation.MOVE || e.operation === FileOperation.DELETE)) {
|
||||
this._modelReferenceCollection.remove(e.source);
|
||||
}
|
||||
}));
|
||||
|
||||
this._modelToDisposeMap = Object.create(null);
|
||||
}
|
||||
|
||||
@@ -163,33 +183,37 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
return this._textFileService.save(URI.revive(uri)).then(target => !!target);
|
||||
}
|
||||
|
||||
$tryOpenDocument(_uri: UriComponents): Promise<any> {
|
||||
const uri = URI.revive(_uri);
|
||||
if (!uri.scheme || !(uri.fsPath || uri.authority)) {
|
||||
$tryOpenDocument(uriData: UriComponents): Promise<URI> {
|
||||
const inputUri = URI.revive(uriData);
|
||||
if (!inputUri.scheme || !(inputUri.fsPath || inputUri.authority)) {
|
||||
return Promise.reject(new Error(`Invalid uri. Scheme and authority or path must be set.`));
|
||||
}
|
||||
|
||||
let promise: Promise<boolean>;
|
||||
switch (uri.scheme) {
|
||||
const canonicalUri = this._uriIdentityService.asCanonicalUri(inputUri);
|
||||
|
||||
let promise: Promise<URI>;
|
||||
switch (canonicalUri.scheme) {
|
||||
case Schemas.untitled:
|
||||
promise = this._handleUntitledScheme(uri);
|
||||
promise = this._handleUntitledScheme(canonicalUri);
|
||||
break;
|
||||
case Schemas.file:
|
||||
default:
|
||||
promise = this._handleAsResourceInput(uri);
|
||||
promise = this._handleAsResourceInput(canonicalUri);
|
||||
break;
|
||||
}
|
||||
|
||||
return promise.then(success => {
|
||||
if (!success) {
|
||||
return Promise.reject(new Error('cannot open ' + uri.toString()));
|
||||
} else if (!this._modelIsSynced.has(uri.toString())) {
|
||||
return Promise.reject(new Error('cannot open ' + uri.toString() + '. Detail: Files above 50MB cannot be synchronized with extensions.'));
|
||||
return promise.then(documentUri => {
|
||||
if (!documentUri) {
|
||||
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}`));
|
||||
} else if (!extUri.isEqual(documentUri, canonicalUri)) {
|
||||
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}. Detail: Actual document opened as ${documentUri.toString()}`));
|
||||
} else if (!this._modelIsSynced.has(canonicalUri.toString())) {
|
||||
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}. Detail: Files above 50MB cannot be synchronized with extensions.`));
|
||||
} else {
|
||||
return undefined;
|
||||
return canonicalUri;
|
||||
}
|
||||
}, err => {
|
||||
return Promise.reject(new Error('cannot open ' + uri.toString() + '. Detail: ' + toErrorMessage(err)));
|
||||
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}. Detail: ${toErrorMessage(err)}`));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -197,21 +221,20 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
return this._doCreateUntitled(undefined, options ? options.language : undefined, options ? options.content : undefined);
|
||||
}
|
||||
|
||||
private _handleAsResourceInput(uri: URI): Promise<boolean> {
|
||||
private _handleAsResourceInput(uri: URI): Promise<URI> {
|
||||
return this._textModelResolverService.createModelReference(uri).then(ref => {
|
||||
this._modelReferenceCollection.add(ref);
|
||||
const result = !!ref.object;
|
||||
return result;
|
||||
this._modelReferenceCollection.add(uri, ref);
|
||||
return ref.object.textEditorModel.uri;
|
||||
});
|
||||
}
|
||||
|
||||
private _handleUntitledScheme(uri: URI): Promise<boolean> {
|
||||
private _handleUntitledScheme(uri: URI): Promise<URI> {
|
||||
const asLocalUri = toLocalResource(uri, this._environmentService.configuration.remoteAuthority);
|
||||
return this._fileService.resolve(asLocalUri).then(stats => {
|
||||
// don't create a new file ontop of an existing file
|
||||
return Promise.reject(new Error('file already exists'));
|
||||
}, err => {
|
||||
return this._doCreateUntitled(Boolean(uri.path) ? uri : undefined).then(resource => !!resource);
|
||||
return this._doCreateUntitled(Boolean(uri.path) ? uri : undefined);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
|
||||
namespace delta {
|
||||
|
||||
@@ -326,11 +328,13 @@ export class MainThreadDocumentsAndEditors {
|
||||
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
|
||||
@IBulkEditService bulkEditService: IBulkEditService,
|
||||
@IPanelService panelService: IPanelService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
|
||||
@IUriIdentityService uriIdentityService: IUriIdentityService,
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors);
|
||||
|
||||
const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService));
|
||||
const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService));
|
||||
extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments);
|
||||
|
||||
const mainThreadTextEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService));
|
||||
|
||||
@@ -8,12 +8,9 @@ import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/c
|
||||
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
|
||||
@extHostCustomer
|
||||
@@ -25,9 +22,6 @@ export class MainThreadFileSystemEventService {
|
||||
extHostContext: IExtHostContext,
|
||||
@IFileService fileService: IFileService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IProgressService progressService: IProgressService,
|
||||
@IConfigurationService configService: IConfigurationService,
|
||||
@ILogService logService: ILogService,
|
||||
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
|
||||
) {
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { MainThreadLanguagesShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { StandardTokenType } from 'vs/editor/common/modes';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadLanguages)
|
||||
export class MainThreadLanguages implements MainThreadLanguagesShape {
|
||||
@@ -40,4 +43,19 @@ export class MainThreadLanguages implements MainThreadLanguagesShape {
|
||||
this._modelService.setMode(model, this._modeService.create(languageId));
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
async $tokensAtPosition(resource: UriComponents, position: IPosition): Promise<undefined | { type: StandardTokenType, range: IRange }> {
|
||||
const uri = URI.revive(resource);
|
||||
const model = this._modelService.getModel(uri);
|
||||
if (!model) {
|
||||
return undefined;
|
||||
}
|
||||
model.tokenizeIfCheap(position.lineNumber);
|
||||
const tokens = model.getLineTokens(position.lineNumber);
|
||||
const idx = tokens.findTokenIndexAtOffset(position.column - 1);
|
||||
return {
|
||||
type: tokens.getStandardTokenType(idx),
|
||||
range: new Range(position.lineNumber, 1 + tokens.getStartOffset(idx), position.lineNumber, 1 + tokens.getEndOffset(idx))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext } from '../common/extHost.protocol';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta, INotebookModelAddedData } from '../common/extHost.protocol';
|
||||
import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IRelativePattern } from 'vs/base/common/glob';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export class MainThreadNotebookDocument extends Disposable {
|
||||
private _textModel: NotebookTextModel;
|
||||
@@ -27,37 +29,103 @@ export class MainThreadNotebookDocument extends Disposable {
|
||||
private readonly _proxy: ExtHostNotebookShape,
|
||||
public handle: number,
|
||||
public viewType: string,
|
||||
public uri: URI
|
||||
public uri: URI,
|
||||
readonly notebookService: INotebookService
|
||||
) {
|
||||
super();
|
||||
this._textModel = new NotebookTextModel(handle, viewType, uri);
|
||||
this._register(this._textModel.onDidModelChange(e => {
|
||||
this._register(this._textModel.onDidModelChangeProxy(e => {
|
||||
this._proxy.$acceptModelChanged(this.uri, e);
|
||||
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: { selections: this._textModel.selections }, metadata: null });
|
||||
}));
|
||||
this._register(this._textModel.onDidSelectionChange(e => {
|
||||
const selectionsChange = e ? { selections: e } : null;
|
||||
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: selectionsChange });
|
||||
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: selectionsChange, metadata: null });
|
||||
}));
|
||||
}
|
||||
|
||||
applyEdit(modelVersionId: number, edits: ICellEditOperation[]): boolean {
|
||||
return this._textModel.applyEdit(modelVersionId, edits);
|
||||
async applyEdit(modelVersionId: number, edits: ICellEditOperation[]): Promise<boolean> {
|
||||
await this.notebookService.transformEditsOutputs(this.textModel, edits);
|
||||
return this._textModel.$applyEdit(modelVersionId, edits);
|
||||
}
|
||||
|
||||
updateRenderers(renderers: number[]) {
|
||||
this._textModel.updateRenderers(renderers);
|
||||
async spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[]) {
|
||||
await this.notebookService.transformSpliceOutputs(this.textModel, splices);
|
||||
this._textModel.$spliceNotebookCellOutputs(cellHandle, splices);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._textModel.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentAndEditorState {
|
||||
static ofMaps<K, V>(before: Map<K, V>, after: Map<K, V>): { removed: V[], added: V[] } {
|
||||
const removed: V[] = [];
|
||||
const added: V[] = [];
|
||||
before.forEach((value, index) => {
|
||||
if (!after.has(index)) {
|
||||
removed.push(value);
|
||||
}
|
||||
});
|
||||
after.forEach((value, index) => {
|
||||
if (!before.has(index)) {
|
||||
added.push(value);
|
||||
}
|
||||
});
|
||||
return { removed, added };
|
||||
}
|
||||
|
||||
static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): INotebookDocumentsAndEditorsDelta {
|
||||
if (!before) {
|
||||
const apiEditors = [];
|
||||
for (let id in after.textEditors) {
|
||||
const editor = after.textEditors.get(id)!;
|
||||
apiEditors.push({ id, documentUri: editor.uri!, selections: editor!.textModel!.selections });
|
||||
}
|
||||
|
||||
return {
|
||||
addedDocuments: [],
|
||||
addedEditors: apiEditors
|
||||
};
|
||||
}
|
||||
// const documentDelta = delta.ofSets(before.documents, after.documents);
|
||||
const editorDelta = DocumentAndEditorState.ofMaps(before.textEditors, after.textEditors);
|
||||
const addedAPIEditors = editorDelta.added.map(add => ({
|
||||
id: add.getId(),
|
||||
documentUri: add.uri!,
|
||||
selections: add.textModel!.selections
|
||||
}));
|
||||
|
||||
const removedAPIEditors = editorDelta.removed.map(removed => removed.getId());
|
||||
|
||||
// const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined;
|
||||
const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
|
||||
|
||||
return {
|
||||
addedEditors: addedAPIEditors,
|
||||
removedEditors: removedAPIEditors,
|
||||
newActiveEditor: newActiveEditor
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly documents: Set<URI>,
|
||||
readonly textEditors: Map<string, IEditor>,
|
||||
readonly activeEditor: string | null | undefined,
|
||||
) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadNotebook)
|
||||
export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape {
|
||||
private readonly _notebookProviders = new Map<string, MainThreadNotebookController>();
|
||||
private readonly _notebookKernels = new Map<string, MainThreadNotebookKernel>();
|
||||
private readonly _notebookRenderers = new Map<string, MainThreadNotebookRenderer>();
|
||||
private readonly _proxy: ExtHostNotebookShape;
|
||||
private _toDisposeOnEditorRemove = new Map<string, IDisposable>();
|
||||
private _currentState?: DocumentAndEditorState;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@@ -83,12 +151,30 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
}
|
||||
|
||||
registerListeners() {
|
||||
this._notebookService.listNotebookEditors().forEach((e) => {
|
||||
this._addNotebookEditor(e);
|
||||
});
|
||||
|
||||
this._register(this._notebookService.onDidChangeActiveEditor(e => {
|
||||
this._proxy.$acceptDocumentAndEditorsDelta({
|
||||
newActiveEditor: e.uri
|
||||
newActiveEditor: e
|
||||
});
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onDidChangeVisibleEditors(e => {
|
||||
this._proxy.$acceptDocumentAndEditorsDelta({
|
||||
visibleEditors: e
|
||||
});
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onNotebookEditorAdd(editor => {
|
||||
this._addNotebookEditor(editor);
|
||||
}));
|
||||
|
||||
this._register(this._notebookService.onNotebookEditorRemove(editor => {
|
||||
this._removeNotebookEditor(editor);
|
||||
}));
|
||||
|
||||
const updateOrder = () => {
|
||||
let userOrder = this.configurationService.getValue<string[]>('notebook.displayOrder');
|
||||
this._proxy.$acceptDisplayOrder({
|
||||
@@ -108,29 +194,120 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => {
|
||||
updateOrder();
|
||||
}));
|
||||
|
||||
const activeEditorPane = this.editorService.activeEditorPane as any | undefined;
|
||||
const notebookEditor = activeEditorPane?.isNotebookEditor ? activeEditorPane.getControl() : undefined;
|
||||
this._updateState(notebookEditor);
|
||||
}
|
||||
|
||||
async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise<void> {
|
||||
this._notebookService.registerNotebookRenderer(handle, extension, type, selectors, preloads.map(uri => URI.revive(uri)));
|
||||
async addNotebookDocument(data: INotebookModelAddedData) {
|
||||
await this._proxy.$acceptDocumentAndEditorsDelta({
|
||||
addedDocuments: [data]
|
||||
});
|
||||
}
|
||||
|
||||
async $unregisterNotebookRenderer(handle: number): Promise<void> {
|
||||
this._notebookService.unregisterNotebookRenderer(handle);
|
||||
private _addNotebookEditor(e: IEditor) {
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable(
|
||||
e.onDidChangeModel(() => this._updateState()),
|
||||
e.onDidFocusEditorWidget(() => {
|
||||
this._updateState(e);
|
||||
}),
|
||||
));
|
||||
|
||||
const activeEditorPane = this.editorService.activeEditorPane as any | undefined;
|
||||
const notebookEditor = activeEditorPane?.isNotebookEditor ? activeEditorPane.getControl() : undefined;
|
||||
this._updateState(notebookEditor);
|
||||
}
|
||||
|
||||
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise<void> {
|
||||
let controller = new MainThreadNotebookController(this._proxy, this, viewType);
|
||||
private _removeNotebookEditor(e: IEditor) {
|
||||
const sub = this._toDisposeOnEditorRemove.get(e.getId());
|
||||
if (sub) {
|
||||
this._toDisposeOnEditorRemove.delete(e.getId());
|
||||
sub.dispose();
|
||||
this._updateState();
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateState(focusedNotebookEditor?: IEditor) {
|
||||
const documents = new Set<URI>();
|
||||
this._notebookService.listNotebookDocuments().forEach(document => {
|
||||
documents.add(document.uri);
|
||||
});
|
||||
|
||||
const editors = new Map<string, IEditor>();
|
||||
let activeEditor: string | null = null;
|
||||
|
||||
for (const editor of this._notebookService.listNotebookEditors()) {
|
||||
if (editor.hasModel()) {
|
||||
editors.set(editor.getId(), editor);
|
||||
if (editor.hasFocus()) {
|
||||
activeEditor = editor.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!activeEditor && focusedNotebookEditor) {
|
||||
activeEditor = focusedNotebookEditor.getId();
|
||||
}
|
||||
|
||||
// editors always have view model attached, which means there is already a document in exthost.
|
||||
const newState = new DocumentAndEditorState(documents, editors, activeEditor);
|
||||
const delta = DocumentAndEditorState.compute(this._currentState, newState);
|
||||
// const isEmptyChange = (!delta.addedDocuments || delta.addedDocuments.length === 0)
|
||||
// && (!delta.removedDocuments || delta.removedDocuments.length === 0)
|
||||
// && (!delta.addedEditors || delta.addedEditors.length === 0)
|
||||
// && (!delta.removedEditors || delta.removedEditors.length === 0)
|
||||
// && (delta.newActiveEditor === undefined)
|
||||
|
||||
// if (!isEmptyChange) {
|
||||
this._currentState = newState;
|
||||
await this._proxy.$acceptDocumentAndEditorsDelta(delta);
|
||||
// }
|
||||
}
|
||||
|
||||
async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise<void> {
|
||||
const renderer = new MainThreadNotebookRenderer(this._proxy, type, extension.id, URI.revive(extension.location), selectors, preloads.map(uri => URI.revive(uri)));
|
||||
this._notebookRenderers.set(type, renderer);
|
||||
this._notebookService.registerNotebookRenderer(type, renderer);
|
||||
}
|
||||
|
||||
async $unregisterNotebookRenderer(id: string): Promise<void> {
|
||||
this._notebookService.unregisterNotebookRenderer(id);
|
||||
}
|
||||
|
||||
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, kernel: INotebookKernelInfoDto | undefined): Promise<void> {
|
||||
let controller = new MainThreadNotebookController(this._proxy, this, viewType, kernel, this._notebookService);
|
||||
this._notebookProviders.set(viewType, controller);
|
||||
this._notebookService.registerNotebookController(viewType, extension, controller);
|
||||
return;
|
||||
}
|
||||
|
||||
async $onNotebookChange(viewType: string, uri: UriComponents): Promise<void> {
|
||||
let controller = this._notebookProviders.get(viewType);
|
||||
if (controller) {
|
||||
controller.handleNotebookChange(uri);
|
||||
}
|
||||
}
|
||||
|
||||
async $unregisterNotebookProvider(viewType: string): Promise<void> {
|
||||
this._notebookProviders.delete(viewType);
|
||||
this._notebookService.unregisterNotebookProvider(viewType);
|
||||
return;
|
||||
}
|
||||
|
||||
async $registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise<void> {
|
||||
const kernel = new MainThreadNotebookKernel(this._proxy, id, label, selectors, extension.id, URI.revive(extension.location), preloads.map(preload => URI.revive(preload)));
|
||||
this._notebookKernels.set(id, kernel);
|
||||
this._notebookService.registerNotebookKernel(kernel);
|
||||
return;
|
||||
}
|
||||
|
||||
async $unregisterNotebookKernel(id: string): Promise<void> {
|
||||
this._notebookKernels.delete(id);
|
||||
this._notebookService.unregisterNotebookKernel(id);
|
||||
return;
|
||||
}
|
||||
|
||||
async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void> {
|
||||
let controller = this._notebookProviders.get(viewType);
|
||||
|
||||
@@ -157,18 +334,18 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
|
||||
async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void> {
|
||||
let controller = this._notebookProviders.get(viewType);
|
||||
controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers);
|
||||
await controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers);
|
||||
}
|
||||
|
||||
async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise<void> {
|
||||
return this._proxy.$executeNotebook(viewType, uri, undefined, token);
|
||||
async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
|
||||
return this._proxy.$executeNotebook(viewType, uri, undefined, useAttachedKernel, token);
|
||||
}
|
||||
|
||||
async $postMessage(handle: number, value: any): Promise<boolean> {
|
||||
|
||||
const activeEditorPane = this.editorService.activeEditorPane as any | undefined;
|
||||
if (activeEditorPane?.isNotebookEditor) {
|
||||
const notebookEditor = (activeEditorPane as INotebookEditor);
|
||||
const notebookEditor = (activeEditorPane.getControl() as INotebookEditor);
|
||||
|
||||
if (notebookEditor.viewModel?.handle === handle) {
|
||||
notebookEditor.postMessage(value);
|
||||
@@ -187,11 +364,14 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
constructor(
|
||||
private readonly _proxy: ExtHostNotebookShape,
|
||||
private _mainThreadNotebook: MainThreadNotebooks,
|
||||
private _viewType: string
|
||||
private _viewType: string,
|
||||
readonly kernel: INotebookKernelInfoDto | undefined,
|
||||
readonly notebookService: INotebookService,
|
||||
|
||||
) {
|
||||
}
|
||||
|
||||
async createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise<NotebookTextModel | undefined> {
|
||||
async createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string): Promise<NotebookTextModel | undefined> {
|
||||
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
|
||||
|
||||
if (mainthreadNotebook) {
|
||||
@@ -203,7 +383,7 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
|
||||
mainthreadNotebook.textModel.languages = data.languages;
|
||||
mainthreadNotebook.textModel.metadata = data.metadata;
|
||||
mainthreadNotebook.textModel.applyEdit(mainthreadNotebook.textModel.versionId, [
|
||||
mainthreadNotebook.textModel.$applyEdit(mainthreadNotebook.textModel.versionId, [
|
||||
{ editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 },
|
||||
{ editType: CellEditType.Insert, index: 0, cells: data.cells }
|
||||
]);
|
||||
@@ -211,10 +391,43 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
return mainthreadNotebook.textModel;
|
||||
}
|
||||
|
||||
let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri);
|
||||
await this.createNotebookDocument(document);
|
||||
let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri, this.notebookService);
|
||||
this._mapping.set(document.uri.toString(), document);
|
||||
|
||||
if (backup) {
|
||||
// trigger events
|
||||
document.textModel.metadata = backup.metadata;
|
||||
document.textModel.languages = backup.languages;
|
||||
|
||||
document.textModel.$applyEdit(document.textModel.versionId, [
|
||||
{
|
||||
editType: CellEditType.Insert,
|
||||
index: 0,
|
||||
cells: backup.cells || []
|
||||
}
|
||||
]);
|
||||
|
||||
await this._mainThreadNotebook.addNotebookDocument({
|
||||
viewType: document.viewType,
|
||||
handle: document.handle,
|
||||
uri: document.uri,
|
||||
metadata: document.textModel.metadata,
|
||||
versionId: document.textModel.versionId,
|
||||
cells: document.textModel.cells.map(cell => ({
|
||||
handle: cell.handle,
|
||||
uri: cell.uri,
|
||||
source: cell.textBuffer.getLinesContent(),
|
||||
language: cell.language,
|
||||
cellKind: cell.cellKind,
|
||||
outputs: cell.outputs,
|
||||
metadata: cell.metadata
|
||||
})),
|
||||
attachedEditor: editorId ? {
|
||||
id: editorId,
|
||||
selections: document.textModel.selections
|
||||
} : undefined
|
||||
});
|
||||
|
||||
if (forBackup) {
|
||||
return document.textModel;
|
||||
}
|
||||
|
||||
@@ -226,7 +439,36 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
|
||||
document.textModel.languages = data.languages;
|
||||
document.textModel.metadata = data.metadata;
|
||||
document.textModel.initialize(data!.cells);
|
||||
|
||||
if (data.cells.length) {
|
||||
document.textModel.initialize(data!.cells);
|
||||
} else {
|
||||
const mainCell = document.textModel.createCellTextModel([''], document.textModel.languages.length ? document.textModel.languages[0] : '', CellKind.Code, [], undefined);
|
||||
document.textModel.insertTemplateCell(mainCell);
|
||||
}
|
||||
|
||||
await this._mainThreadNotebook.addNotebookDocument({
|
||||
viewType: document.viewType,
|
||||
handle: document.handle,
|
||||
uri: document.uri,
|
||||
metadata: document.textModel.metadata,
|
||||
versionId: document.textModel.versionId,
|
||||
cells: document.textModel.cells.map(cell => ({
|
||||
handle: cell.handle,
|
||||
uri: cell.uri,
|
||||
source: cell.textBuffer.getLinesContent(),
|
||||
language: cell.language,
|
||||
cellKind: cell.cellKind,
|
||||
outputs: cell.outputs,
|
||||
metadata: cell.metadata
|
||||
})),
|
||||
attachedEditor: editorId ? {
|
||||
id: editorId,
|
||||
selections: document.textModel.selections
|
||||
} : undefined
|
||||
});
|
||||
|
||||
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: null, metadata: document.textModel.metadata });
|
||||
|
||||
return document.textModel;
|
||||
}
|
||||
@@ -235,37 +477,23 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
|
||||
|
||||
if (mainthreadNotebook) {
|
||||
mainthreadNotebook.updateRenderers(renderers);
|
||||
return mainthreadNotebook.applyEdit(modelVersionId, edits);
|
||||
return await mainthreadNotebook.applyEdit(modelVersionId, edits);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): void {
|
||||
async spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void> {
|
||||
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
|
||||
mainthreadNotebook?.textModel.updateRenderers(renderers);
|
||||
mainthreadNotebook?.textModel.$spliceNotebookCellOutputs(cellHandle, splices);
|
||||
await mainthreadNotebook?.spliceNotebookCellOutputs(cellHandle, splices);
|
||||
}
|
||||
|
||||
async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise<void> {
|
||||
this._mainThreadNotebook.executeNotebook(viewType, uri, token);
|
||||
async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
|
||||
return this._mainThreadNotebook.executeNotebook(viewType, uri, useAttachedKernel, token);
|
||||
}
|
||||
|
||||
onDidReceiveMessage(uri: UriComponents, message: any): void {
|
||||
this._proxy.$onDidReceiveMessage(uri, message);
|
||||
}
|
||||
|
||||
async createNotebookDocument(document: MainThreadNotebookDocument): Promise<void> {
|
||||
this._mapping.set(document.uri.toString(), document);
|
||||
|
||||
await this._proxy.$acceptDocumentAndEditorsDelta({
|
||||
addedDocuments: [{
|
||||
viewType: document.viewType,
|
||||
handle: document.handle,
|
||||
uri: document.uri
|
||||
}]
|
||||
});
|
||||
onDidReceiveMessage(editorId: string, message: any): void {
|
||||
this._proxy.$onDidReceiveMessage(editorId, message);
|
||||
}
|
||||
|
||||
async removeNotebookDocument(notebook: INotebookTextModel): Promise<void> {
|
||||
@@ -282,6 +510,11 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
|
||||
// Methods for ExtHost
|
||||
|
||||
handleNotebookChange(resource: UriComponents) {
|
||||
let document = this._mapping.get(URI.from(resource).toString());
|
||||
document?.textModel.handleUnknownChange();
|
||||
}
|
||||
|
||||
updateLanguages(resource: UriComponents, languages: string[]) {
|
||||
let document = this._mapping.get(URI.from(resource).toString());
|
||||
document?.textModel.updateLanguages(languages);
|
||||
@@ -297,13 +530,8 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
document?.textModel.updateNotebookCellMetadata(handle, metadata);
|
||||
}
|
||||
|
||||
updateNotebookRenderers(resource: UriComponents, renderers: number[]): void {
|
||||
let document = this._mapping.get(URI.from(resource).toString());
|
||||
document?.textModel.updateRenderers(renderers);
|
||||
}
|
||||
|
||||
async executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise<void> {
|
||||
return this._proxy.$executeNotebook(this._viewType, uri, handle, token);
|
||||
async executeNotebookCell(uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
|
||||
return this._proxy.$executeNotebook(this._viewType, uri, handle, useAttachedKernel, token);
|
||||
}
|
||||
|
||||
async save(uri: URI, token: CancellationToken): Promise<boolean> {
|
||||
@@ -315,3 +543,41 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class MainThreadNotebookKernel implements INotebookKernelInfo {
|
||||
constructor(
|
||||
private readonly _proxy: ExtHostNotebookShape,
|
||||
readonly id: string,
|
||||
readonly label: string,
|
||||
readonly selectors: (string | IRelativePattern)[],
|
||||
readonly extension: ExtensionIdentifier,
|
||||
readonly extensionLocation: URI,
|
||||
readonly preloads: URI[]
|
||||
) {
|
||||
}
|
||||
|
||||
async executeNotebook(viewType: string, uri: URI, handle: number | undefined, token: CancellationToken): Promise<void> {
|
||||
return this._proxy.$executeNotebook2(this.id, viewType, uri, handle, token);
|
||||
}
|
||||
}
|
||||
|
||||
export class MainThreadNotebookRenderer implements INotebookRendererInfo {
|
||||
constructor(
|
||||
private readonly _proxy: ExtHostNotebookShape,
|
||||
readonly id: string,
|
||||
readonly extensionId: ExtensionIdentifier,
|
||||
readonly extensionLocation: URI,
|
||||
readonly selectors: INotebookMimeTypeSelector,
|
||||
readonly preloads: URI[]
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
render(uri: URI, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined> {
|
||||
return this._proxy.$renderOutputs(uri, this.id, request);
|
||||
}
|
||||
|
||||
render2<T>(uri: URI, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined> {
|
||||
return this._proxy.$renderOutputs2(uri, this.id, request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { Command } from 'vs/editor/common/modes';
|
||||
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadStatusBar)
|
||||
export class MainThreadStatusBar implements MainThreadStatusBarShape {
|
||||
@@ -25,9 +26,14 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape {
|
||||
this.entries.clear();
|
||||
}
|
||||
|
||||
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined): void {
|
||||
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation): void {
|
||||
// if there are icons in the text use the tooltip for the aria label
|
||||
const ariaLabel = text.indexOf('$(') === -1 ? text : tooltip || text;
|
||||
let ariaLabel: string;
|
||||
if (accessibilityInformation) {
|
||||
ariaLabel = accessibilityInformation.label;
|
||||
} else {
|
||||
ariaLabel = text.indexOf('$(') === -1 ? text : tooltip || text;
|
||||
}
|
||||
const entry: IStatusbarEntry = { text, tooltip, command, color, ariaLabel };
|
||||
|
||||
if (typeof priority === 'undefined') {
|
||||
|
||||
@@ -25,6 +25,7 @@ export class MainThreadTheming implements MainThreadThemingShape {
|
||||
this._themeChangeListener = this._themeService.onDidColorThemeChange(e => {
|
||||
this._proxy.$onColorThemeChange(this._themeService.getColorTheme().type);
|
||||
});
|
||||
this._proxy.$onColorThemeChange(this._themeService.getColorTheme().type);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
@@ -325,13 +325,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
throw new Error(`Provider for ${viewType} already registered`);
|
||||
}
|
||||
|
||||
this._customEditorService.registerCustomEditorCapabilities(viewType, {
|
||||
supportsMultipleEditorsPerDocument
|
||||
});
|
||||
|
||||
const extension = reviveWebviewExtension(extensionData);
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
disposables.add(this._customEditorService.registerCustomEditorCapabilities(viewType, {
|
||||
supportsMultipleEditorsPerDocument
|
||||
}));
|
||||
|
||||
disposables.add(this._webviewWorkbenchService.registerResolver({
|
||||
canResolve: (webviewInput) => {
|
||||
return webviewInput instanceof CustomEditorInput && webviewInput.viewType === viewType;
|
||||
@@ -360,6 +361,17 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
}
|
||||
|
||||
webviewInput.webview.onDispose(() => {
|
||||
// If the model is still dirty, make sure we have time to save it
|
||||
if (modelRef.object.isDirty()) {
|
||||
const sub = modelRef.object.onDidChangeDirty(() => {
|
||||
if (!modelRef.object.isDirty()) {
|
||||
sub.dispose();
|
||||
modelRef.dispose();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
modelRef.dispose();
|
||||
});
|
||||
|
||||
@@ -649,10 +661,11 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
|
||||
) {
|
||||
super();
|
||||
|
||||
this._fromBackup = fromBackup;
|
||||
|
||||
if (_editable) {
|
||||
this._register(workingCopyService.registerWorkingCopy(this));
|
||||
}
|
||||
this._fromBackup = fromBackup;
|
||||
}
|
||||
|
||||
get editorResource() {
|
||||
@@ -710,7 +723,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
|
||||
//#endregion
|
||||
|
||||
public isReadonly() {
|
||||
return this._editable;
|
||||
return !this._editable;
|
||||
}
|
||||
|
||||
public get viewType() {
|
||||
|
||||
@@ -80,6 +80,9 @@ interface IUserFriendlyViewDescriptor {
|
||||
name: string;
|
||||
when?: string;
|
||||
|
||||
icon?: string;
|
||||
contextualTitle?: string;
|
||||
|
||||
// From 'remoteViewDescriptor' type
|
||||
group?: string;
|
||||
remoteName?: string | string[];
|
||||
@@ -100,6 +103,14 @@ const viewDescriptor: IJSONSchema = {
|
||||
description: localize('vscode.extension.contributes.view.when', 'Condition which must be true to show this view'),
|
||||
type: 'string'
|
||||
},
|
||||
icon: {
|
||||
description: localize('vscode.extension.contributes.view.icon', "Path to the view icon. View icons are displayed when the name of the view cannot be shown. It is recommended that icons be in SVG, though any image file type is accepted."),
|
||||
type: 'string'
|
||||
},
|
||||
contextualTitle: {
|
||||
description: localize('vscode.extension.contributes.view.contextualTitle', "Human-readable context for when the view is moved out of its original location. By default, the view's container name will be used. Will be shown"),
|
||||
type: 'string'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -406,12 +417,14 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
? container.viewOrderDelegate.getOrder(item.group)
|
||||
: undefined;
|
||||
|
||||
const icon = item.icon ? resources.joinPath(extension.description.extensionLocation, item.icon) : undefined;
|
||||
const viewDescriptor = <ICustomViewDescriptor>{
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: ContextKeyExpr.deserialize(item.when),
|
||||
containerIcon: viewContainer?.icon,
|
||||
containerIcon: icon || viewContainer?.icon,
|
||||
containerTitle: item.contextualTitle || viewContainer?.name,
|
||||
canToggleVisibility: true,
|
||||
canMoveView: true,
|
||||
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name),
|
||||
@@ -468,6 +481,14 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
|
||||
return false;
|
||||
}
|
||||
if (descriptor.icon && typeof descriptor.icon !== 'string') {
|
||||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'icon'));
|
||||
return false;
|
||||
}
|
||||
if (descriptor.contextualTitle && typeof descriptor.contextualTitle !== 'string') {
|
||||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'contextualTitle'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -8,11 +8,12 @@ import * as objects from 'vs/base/common/objects';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
|
||||
@@ -116,7 +117,13 @@ defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => {
|
||||
const addedDefaultConfigurations = added.map(extension => {
|
||||
const id = extension.description.identifier;
|
||||
const name = extension.description.name;
|
||||
const defaults = objects.deepClone(extension.value);
|
||||
const defaults: IStringDictionary<any> = objects.deepClone(extension.value);
|
||||
for (const key of Object.keys(defaults)) {
|
||||
if (!OVERRIDE_PROPERTY_PATTERN.test(key) || typeof defaults[key] !== 'object') {
|
||||
extension.collector.warn(nls.localize('config.property.defaultConfiguration.warning', "Cannot register configuration defaults for '{0}'. Only defaults for language specific settings are supported.", key));
|
||||
delete defaults[key];
|
||||
}
|
||||
}
|
||||
return <IDefaultConfigurationExtension>{
|
||||
id, name, defaults
|
||||
};
|
||||
|
||||
@@ -135,7 +135,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
|
||||
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
|
||||
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol));
|
||||
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors));
|
||||
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment));
|
||||
const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol));
|
||||
const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol));
|
||||
const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands));
|
||||
@@ -196,9 +196,18 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
get onDidChangeAuthenticationProviders(): Event<vscode.AuthenticationProvidersChangeEvent> {
|
||||
return extHostAuthentication.onDidChangeAuthenticationProviders;
|
||||
},
|
||||
getProviderIds(): Thenable<ReadonlyArray<string>> {
|
||||
return extHostAuthentication.getProviderIds();
|
||||
},
|
||||
get providerIds(): string[] {
|
||||
return extHostAuthentication.providerIds;
|
||||
},
|
||||
hasSessions(providerId: string, scopes: string[]): Thenable<boolean> {
|
||||
return extHostAuthentication.hasSessions(providerId, scopes);
|
||||
},
|
||||
getSession(providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions) {
|
||||
return extHostAuthentication.getSession(extension, providerId, scopes, options as any);
|
||||
},
|
||||
getSessions(providerId: string, scopes: string[]): Thenable<readonly vscode.AuthenticationSession[]> {
|
||||
return extHostAuthentication.getSessions(extension, providerId, scopes);
|
||||
},
|
||||
@@ -265,7 +274,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
get sessionId() { return initData.telemetryInfo.sessionId; },
|
||||
get language() { return initData.environment.appLanguage; },
|
||||
get appName() { return initData.environment.appName; },
|
||||
get appRoot() { return initData.environment.appRoot!.fsPath; },
|
||||
get appRoot() { return initData.environment.appRoot?.fsPath ?? '<UNKNOWN_APP_ROOT>'; },
|
||||
get uriScheme() { return initData.environment.appUriScheme; },
|
||||
get logLevel() {
|
||||
checkProposedApiEnabled(extension);
|
||||
@@ -428,6 +437,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
},
|
||||
setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => {
|
||||
return extHostLanguageFeatures.setLanguageConfiguration(extension, language, configuration);
|
||||
},
|
||||
getTokenInformationAtPosition(doc: vscode.TextDocument, pos: vscode.Position) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostLanguages.tokenAtPosition(doc, pos);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -528,12 +541,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
let id: string;
|
||||
let name: string;
|
||||
let alignment: number | undefined;
|
||||
let accessibilityInformation: vscode.AccessibilityInformation | undefined = undefined;
|
||||
|
||||
if (alignmentOrOptions && typeof alignmentOrOptions !== 'number') {
|
||||
id = alignmentOrOptions.id;
|
||||
name = alignmentOrOptions.name;
|
||||
alignment = alignmentOrOptions.alignment;
|
||||
priority = alignmentOrOptions.priority;
|
||||
accessibilityInformation = alignmentOrOptions.accessibilityInformation;
|
||||
} else {
|
||||
id = extension.identifier.value;
|
||||
name = nls.localize('extensionLabel', "{0} (Extension)", extension.displayName || extension.name);
|
||||
@@ -541,7 +556,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
priority = priority;
|
||||
}
|
||||
|
||||
return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority);
|
||||
return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority, accessibilityInformation);
|
||||
},
|
||||
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): vscode.Disposable {
|
||||
return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable);
|
||||
@@ -703,8 +718,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
}
|
||||
|
||||
return uriPromise.then(uri => {
|
||||
return extHostDocuments.ensureDocumentData(uri).then(() => {
|
||||
return extHostDocuments.getDocument(uri);
|
||||
return extHostDocuments.ensureDocumentData(uri).then(documentData => {
|
||||
return documentData.document;
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -931,10 +946,20 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidCloseNotebookDocument;
|
||||
},
|
||||
get visibleNotebookEditors() {
|
||||
return extHostNotebook.visibleNotebookEditors;
|
||||
},
|
||||
get onDidChangeVisibleNotebookEditors() {
|
||||
return extHostNotebook.onDidChangeVisibleNotebookEditors;
|
||||
},
|
||||
registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider);
|
||||
},
|
||||
registerNotebookKernel: (id: string, selector: vscode.GlobPattern[], kernel: vscode.NotebookKernel) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.registerNotebookKernel(extension, id, selector, kernel);
|
||||
},
|
||||
registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer);
|
||||
@@ -947,9 +972,21 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.activeNotebookEditor;
|
||||
},
|
||||
onDidChangeNotebookDocument(listener, thisArgs?, disposables?) {
|
||||
onDidChangeActiveNotebookEditor(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeNotebookDocument(listener, thisArgs, disposables);
|
||||
return extHostNotebook.onDidChangeActiveNotebookEditor(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeNotebookCells(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeNotebookCells(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeCellOutputs(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeCellOutputs(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeCellLanguage(listener, thisArgs?, disposables?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.onDidChangeCellLanguage(listener, thisArgs, disposables);
|
||||
},
|
||||
createConcatTextDocument(notebook, selector) {
|
||||
checkProposedApiEnabled(extension);
|
||||
@@ -1050,6 +1087,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
SnippetString: extHostTypes.SnippetString,
|
||||
SourceBreakpoint: extHostTypes.SourceBreakpoint,
|
||||
SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
|
||||
StandardTokenType: extHostTypes.StandardTokenType,
|
||||
StatusBarAlignment: extHostTypes.StatusBarAlignment,
|
||||
SymbolInformation: extHostTypes.SymbolInformation,
|
||||
SymbolKind: extHostTypes.SymbolKind,
|
||||
@@ -1086,7 +1124,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
TimelineItem: extHostTypes.TimelineItem,
|
||||
CellKind: extHostTypes.CellKind,
|
||||
CellOutputKind: extHostTypes.CellOutputKind,
|
||||
NotebookCellRunState: extHostTypes.NotebookCellRunState
|
||||
NotebookCellRunState: extHostTypes.NotebookCellRunState,
|
||||
AuthenticationSession2: extHostTypes.AuthenticationSession
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,11 +51,12 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse, IRawOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||
import { Dto } from 'vs/base/common/types';
|
||||
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
|
||||
@@ -161,12 +162,20 @@ export interface MainThreadCommentsShape extends IDisposable {
|
||||
}
|
||||
|
||||
export interface MainThreadAuthenticationShape extends IDisposable {
|
||||
$registerAuthenticationProvider(id: string, displayName: string): void;
|
||||
$registerAuthenticationProvider(id: string, displayName: string, supportsMultipleAccounts: boolean): void;
|
||||
$unregisterAuthenticationProvider(id: string): void;
|
||||
$onDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void;
|
||||
$getProviderIds(): Promise<string[]>;
|
||||
$sendDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void;
|
||||
$getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone?: boolean, clearSessionPreference?: boolean }): Promise<modes.AuthenticationSession | undefined>;
|
||||
$selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession>;
|
||||
$getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean>;
|
||||
$loginPrompt(providerName: string, extensionName: string): Promise<boolean>;
|
||||
$setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<void>;
|
||||
$requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void>;
|
||||
|
||||
$getSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>>;
|
||||
$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession>;
|
||||
$logout(providerId: string, sessionId: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadConfigurationShape extends IDisposable {
|
||||
@@ -215,7 +224,7 @@ export interface MainThreadDocumentContentProvidersShape extends IDisposable {
|
||||
|
||||
export interface MainThreadDocumentsShape extends IDisposable {
|
||||
$tryCreateDocument(options?: { language?: string; content?: string; }): Promise<UriComponents>;
|
||||
$tryOpenDocument(uri: UriComponents): Promise<void>;
|
||||
$tryOpenDocument(uri: UriComponents): Promise<UriComponents>;
|
||||
$trySaveDocument(uri: UriComponents): Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -394,6 +403,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
||||
export interface MainThreadLanguagesShape extends IDisposable {
|
||||
$getLanguages(): Promise<string[]>;
|
||||
$changeLanguage(resource: UriComponents, languageId: string): Promise<void>;
|
||||
$tokensAtPosition(resource: UriComponents, position: IPosition): Promise<undefined | { type: modes.StandardTokenType, range: IRange }>;
|
||||
}
|
||||
|
||||
export interface MainThreadMessageOptions {
|
||||
@@ -547,7 +557,7 @@ export interface MainThreadQuickOpenShape extends IDisposable {
|
||||
}
|
||||
|
||||
export interface MainThreadStatusBarShape extends IDisposable {
|
||||
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: ICommandDto | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void;
|
||||
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: ICommandDto | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): void;
|
||||
$dispose(id: number): void;
|
||||
}
|
||||
|
||||
@@ -674,7 +684,7 @@ export interface ICellDto {
|
||||
source: string[];
|
||||
language: string;
|
||||
cellKind: CellKind;
|
||||
outputs: IOutput[];
|
||||
outputs: IProcessedOutput[];
|
||||
metadata?: NotebookCellMetadata;
|
||||
}
|
||||
|
||||
@@ -687,14 +697,17 @@ export type NotebookCellsSplice = [
|
||||
export type NotebookCellOutputsSplice = [
|
||||
number /* start */,
|
||||
number /* delete count */,
|
||||
IOutput[]
|
||||
IRawOutput[]
|
||||
];
|
||||
|
||||
export interface MainThreadNotebookShape extends IDisposable {
|
||||
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise<void>;
|
||||
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, kernelInfoDto: INotebookKernelInfoDto | undefined): Promise<void>;
|
||||
$onNotebookChange(viewType: string, resource: UriComponents): Promise<void>;
|
||||
$unregisterNotebookProvider(viewType: string): Promise<void>;
|
||||
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise<void>;
|
||||
$unregisterNotebookRenderer(handle: number): Promise<void>;
|
||||
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise<void>;
|
||||
$unregisterNotebookRenderer(id: string): Promise<void>;
|
||||
$registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise<void>;
|
||||
$unregisterNotebookKernel(id: string): Promise<void>;
|
||||
$tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise<boolean>;
|
||||
$updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void>;
|
||||
$updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise<void>;
|
||||
@@ -1006,6 +1019,8 @@ export interface ExtHostAuthenticationShape {
|
||||
$getSessionAccessToken(id: string, sessionId: string): Promise<string>;
|
||||
$login(id: string, scopes: string[]): Promise<modes.AuthenticationSession>;
|
||||
$logout(id: string, sessionId: string): Promise<void>;
|
||||
$onDidChangeAuthenticationSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): Promise<void>;
|
||||
$onDidChangeAuthenticationProviders(added: string[], removed: string[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostSearchShape {
|
||||
@@ -1539,30 +1554,44 @@ export interface INotebookSelectionChangeEvent {
|
||||
|
||||
export interface INotebookEditorPropertiesChangeData {
|
||||
selections: INotebookSelectionChangeEvent | null;
|
||||
metadata: NotebookDocumentMetadata | null;
|
||||
}
|
||||
|
||||
export interface INotebookModelAddedData {
|
||||
uri: UriComponents;
|
||||
handle: number;
|
||||
// versionId: number;
|
||||
versionId: number;
|
||||
cells: IMainCellDto[],
|
||||
viewType: string;
|
||||
metadata?: NotebookDocumentMetadata;
|
||||
attachedEditor?: { id: string; selections: number[]; }
|
||||
}
|
||||
|
||||
export interface INotebookEditorAddData {
|
||||
id: string;
|
||||
documentUri: UriComponents;
|
||||
selections: number[];
|
||||
}
|
||||
|
||||
export interface INotebookDocumentsAndEditorsDelta {
|
||||
removedDocuments?: UriComponents[];
|
||||
addedDocuments?: INotebookModelAddedData[];
|
||||
// removedEditors?: string[];
|
||||
// addedEditors?: ITextEditorAddData[];
|
||||
newActiveEditor?: UriComponents | null;
|
||||
removedEditors?: string[];
|
||||
addedEditors?: INotebookEditorAddData[];
|
||||
newActiveEditor?: string | null;
|
||||
visibleEditors?: string[];
|
||||
}
|
||||
|
||||
export interface ExtHostNotebookShape {
|
||||
$resolveNotebookData(viewType: string, uri: UriComponents): Promise<NotebookDataDto | undefined>;
|
||||
$executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void>;
|
||||
$executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise<void>;
|
||||
$executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void>;
|
||||
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
|
||||
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
|
||||
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
|
||||
$onDidReceiveMessage(uri: UriComponents, message: any): void;
|
||||
$renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined>;
|
||||
$renderOutputs2<T>(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined>;
|
||||
$onDidReceiveMessage(editorId: string, message: any): void;
|
||||
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void;
|
||||
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void;
|
||||
$acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise<void>;
|
||||
|
||||
@@ -24,6 +24,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication);
|
||||
}
|
||||
|
||||
getProviderIds(): Promise<ReadonlyArray<string>> {
|
||||
return this._proxy.$getProviderIds();
|
||||
}
|
||||
|
||||
get providerIds(): string[] {
|
||||
const ids: string[] = [];
|
||||
this._authenticationProviders.forEach(provider => {
|
||||
@@ -33,16 +37,74 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
return ids;
|
||||
}
|
||||
|
||||
async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<readonly vscode.AuthenticationSession[]> {
|
||||
private async resolveSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
|
||||
const provider = this._authenticationProviders.get(providerId);
|
||||
|
||||
let sessions;
|
||||
if (!provider) {
|
||||
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
|
||||
sessions = await this._proxy.$getSessions(providerId);
|
||||
} else {
|
||||
sessions = await provider.getSessions();
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
async hasSessions(providerId: string, scopes: string[]): Promise<boolean> {
|
||||
const orderedScopes = scopes.sort().join(' ');
|
||||
const sessions = await this.resolveSessions(providerId);
|
||||
return !!(sessions.filter(session => session.scopes.sort().join(' ') === orderedScopes).length);
|
||||
}
|
||||
|
||||
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions & { createIfNone: true }): Promise<vscode.AuthenticationSession2>;
|
||||
async getSession(requestingExtension: IExtensionDescription, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions): Promise<vscode.AuthenticationSession2 | undefined> {
|
||||
const provider = this._authenticationProviders.get(providerId);
|
||||
const extensionName = requestingExtension.displayName || requestingExtension.name;
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
|
||||
if (!provider) {
|
||||
return this._proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
|
||||
}
|
||||
|
||||
const orderedScopes = scopes.sort().join(' ');
|
||||
const sessions = (await provider.getSessions()).filter(session => session.scopes.sort().join(' ') === orderedScopes);
|
||||
|
||||
if (sessions.length) {
|
||||
if (!provider.supportsMultipleAccounts) {
|
||||
const session = sessions[0];
|
||||
const allowed = await this._proxy.$getSessionsPrompt(providerId, session.account.displayName, provider.displayName, extensionId, extensionName);
|
||||
if (allowed) {
|
||||
return session;
|
||||
} else {
|
||||
throw new Error('User did not consent to login.');
|
||||
}
|
||||
}
|
||||
|
||||
// On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid
|
||||
const selected = await this._proxy.$selectSession(providerId, provider.displayName, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
|
||||
return sessions.find(session => session.id === selected.id);
|
||||
} else {
|
||||
if (options.createIfNone) {
|
||||
const isAllowed = await this._proxy.$loginPrompt(provider.displayName, extensionName);
|
||||
if (!isAllowed) {
|
||||
throw new Error('User did not consent to login.');
|
||||
}
|
||||
|
||||
const session = await provider.login(scopes);
|
||||
await this._proxy.$setTrustedExtension(providerId, session.account.displayName, extensionId, extensionName);
|
||||
return session;
|
||||
} else {
|
||||
await this._proxy.$requestNewSession(providerId, scopes, extensionId, extensionName);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<readonly vscode.AuthenticationSession[]> {
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
const orderedScopes = scopes.sort().join(' ');
|
||||
|
||||
return (await provider.getSessions())
|
||||
const sessions = await this.resolveSessions(providerId);
|
||||
return sessions
|
||||
.filter(session => session.scopes.sort().join(' ') === orderedScopes)
|
||||
.map(session => {
|
||||
return {
|
||||
@@ -51,9 +113,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
scopes: session.scopes,
|
||||
getAccessToken: async () => {
|
||||
const isAllowed = await this._proxy.$getSessionsPrompt(
|
||||
provider.id,
|
||||
providerId,
|
||||
session.account.displayName,
|
||||
provider.displayName,
|
||||
'', // TODO
|
||||
// provider.displayName,
|
||||
extensionId,
|
||||
requestingExtension.displayName || requestingExtension.name);
|
||||
|
||||
@@ -61,7 +124,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
throw new Error('User did not consent to token access.');
|
||||
}
|
||||
|
||||
return session.getAccessToken();
|
||||
return session.accessToken;
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -97,7 +160,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
throw new Error('User did not consent to token access.');
|
||||
}
|
||||
|
||||
return session.getAccessToken();
|
||||
return session.accessToken;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -105,7 +168,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
async logout(providerId: string, sessionId: string): Promise<void> {
|
||||
const provider = this._authenticationProviders.get(providerId);
|
||||
if (!provider) {
|
||||
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
|
||||
return this._proxy.$logout(providerId, sessionId);
|
||||
}
|
||||
|
||||
return provider.logout(sessionId);
|
||||
@@ -119,18 +182,15 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
this._authenticationProviders.set(provider.id, provider);
|
||||
|
||||
const listener = provider.onDidChangeSessions(e => {
|
||||
this._proxy.$onDidChangeSessions(provider.id, e);
|
||||
this._onDidChangeSessions.fire({ [provider.id]: e });
|
||||
this._proxy.$sendDidChangeSessions(provider.id, e);
|
||||
});
|
||||
|
||||
this._proxy.$registerAuthenticationProvider(provider.id, provider.displayName);
|
||||
this._onDidChangeAuthenticationProviders.fire({ added: [provider.id], removed: [] });
|
||||
this._proxy.$registerAuthenticationProvider(provider.id, provider.displayName, provider.supportsMultipleAccounts);
|
||||
|
||||
return new Disposable(() => {
|
||||
listener.dispose();
|
||||
this._authenticationProviders.delete(provider.id);
|
||||
this._proxy.$unregisterAuthenticationProvider(provider.id);
|
||||
this._onDidChangeAuthenticationProviders.fire({ added: [], removed: [provider.id] });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -167,7 +227,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
const sessions = await authProvider.getSessions();
|
||||
const session = sessions.find(session => session.id === sessionId);
|
||||
if (session) {
|
||||
return session.getAccessToken();
|
||||
return session.accessToken;
|
||||
}
|
||||
|
||||
throw new Error(`Unable to find session with id: ${sessionId}`);
|
||||
@@ -175,4 +235,14 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
|
||||
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
|
||||
}
|
||||
|
||||
$onDidChangeAuthenticationSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent) {
|
||||
this._onDidChangeSessions.fire({ [providerId]: event });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
$onDidChangeAuthenticationProviders(added: string[], removed: string[]) {
|
||||
this._onDidChangeAuthenticationProviders.fire({ added, removed });
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import { MainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, Decor
|
||||
import { Disposable, Decoration } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { asArray } from 'vs/base/common/arrays';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { asArray } from 'vs/base/common/arrays';
|
||||
|
||||
interface ProviderData {
|
||||
provider: vscode.DecorationProvider;
|
||||
@@ -40,7 +40,9 @@ export class ExtHostDecorations implements IExtHostDecorations {
|
||||
this._proxy.$registerDecorationProvider(handle, extensionId.value);
|
||||
|
||||
const listener = provider.onDidChangeDecorations(e => {
|
||||
this._proxy.$onDidChange(handle, !e ? null : asArray(e));
|
||||
this._proxy.$onDidChange(handle, !e || (Array.isArray(e) && e.length > 250)
|
||||
? null
|
||||
: asArray(e));
|
||||
});
|
||||
|
||||
return new Disposable(() => {
|
||||
|
||||
@@ -84,9 +84,10 @@ export class ExtHostDocuments implements ExtHostDocumentsShape {
|
||||
|
||||
let promise = this._documentLoader.get(uri.toString());
|
||||
if (!promise) {
|
||||
promise = this._proxy.$tryOpenDocument(uri).then(() => {
|
||||
promise = this._proxy.$tryOpenDocument(uri).then(uriData => {
|
||||
this._documentLoader.delete(uri.toString());
|
||||
return assertIsDefined(this._documentsAndEditors.getDocument(uri));
|
||||
const canonicalUri = URI.revive(uriData);
|
||||
return assertIsDefined(this._documentsAndEditors.getDocument(canonicalUri));
|
||||
}, err => {
|
||||
this._documentLoader.delete(uri.toString());
|
||||
return Promise.reject(err);
|
||||
|
||||
@@ -857,16 +857,11 @@ class SuggestAdapter {
|
||||
private _cache = new Cache<vscode.CompletionItem>('CompletionItem');
|
||||
private _disposables = new Map<number, DisposableStore>();
|
||||
|
||||
private _didWarnMust: boolean = false;
|
||||
private _didWarnShould: boolean = false;
|
||||
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _commands: CommandsConverter,
|
||||
private readonly _provider: vscode.CompletionItemProvider,
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _apiDeprecation: IExtHostApiDeprecationService,
|
||||
private readonly _telemetry: extHostProtocol.MainThreadTelemetryShape,
|
||||
private readonly _extension: IExtensionDescription,
|
||||
) { }
|
||||
|
||||
@@ -930,41 +925,12 @@ class SuggestAdapter {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const _mustNotChange = SuggestAdapter._mustNotChangeHash(item);
|
||||
const _mayNotChange = SuggestAdapter._mayNotChangeHash(item);
|
||||
|
||||
const resolvedItem = await asPromise(() => this._provider.resolveCompletionItem!(item, token));
|
||||
|
||||
if (!resolvedItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
type BlameExtension = {
|
||||
extensionId: string;
|
||||
kind: string;
|
||||
index: string;
|
||||
};
|
||||
|
||||
type BlameExtensionMeta = {
|
||||
extensionId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
|
||||
kind: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
|
||||
index: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
|
||||
};
|
||||
|
||||
let _mustNotChangeIndex = !this._didWarnMust && SuggestAdapter._mustNotChangeDiff(_mustNotChange, resolvedItem);
|
||||
if (typeof _mustNotChangeIndex === 'string') {
|
||||
this._logService.warn(`[${this._extension.identifier.value}] INVALID result from 'resolveCompletionItem', extension MUST NOT change any of: label, sortText, filterText, insertText, or textEdit`);
|
||||
this._telemetry.$publicLog2<BlameExtension, BlameExtensionMeta>('badresolvecompletion', { extensionId: this._extension.identifier.value, kind: 'must', index: _mustNotChangeIndex });
|
||||
this._didWarnMust = true;
|
||||
}
|
||||
|
||||
let _mayNotChangeIndex = !this._didWarnShould && SuggestAdapter._mayNotChangeDiff(_mayNotChange, resolvedItem);
|
||||
if (typeof _mayNotChangeIndex === 'string') {
|
||||
this._logService.info(`[${this._extension.identifier.value}] UNSAVE result from 'resolveCompletionItem', extension SHOULD NOT change any of: additionalTextEdits, or command`);
|
||||
this._telemetry.$publicLog2<BlameExtension, BlameExtensionMeta>('badresolvecompletion', { extensionId: this._extension.identifier.value, kind: 'should', index: _mayNotChangeIndex });
|
||||
this._didWarnShould = true;
|
||||
}
|
||||
|
||||
return this._convertCompletionItem(resolvedItem, id);
|
||||
}
|
||||
|
||||
@@ -1035,45 +1001,6 @@ class SuggestAdapter {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _mustNotChangeHash(item: vscode.CompletionItem) {
|
||||
const res = JSON.stringify([item.label, item.sortText, item.filterText, item.insertText, item.range]);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static _mustNotChangeDiff(hash: string, item: vscode.CompletionItem): string | void {
|
||||
const thisArr = [item.label, item.sortText, item.filterText, item.insertText, item.range];
|
||||
const thisHash = JSON.stringify(thisArr);
|
||||
if (hash === thisHash) {
|
||||
return;
|
||||
}
|
||||
const arr = JSON.parse(hash);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (JSON.stringify(arr[i] !== JSON.stringify(thisArr[i]))) {
|
||||
return i.toString();
|
||||
}
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
private static _mayNotChangeHash(item: vscode.CompletionItem) {
|
||||
return JSON.stringify([item.additionalTextEdits, item.command]);
|
||||
}
|
||||
|
||||
private static _mayNotChangeDiff(hash: string, item: vscode.CompletionItem): string | void {
|
||||
const thisArr = [item.additionalTextEdits, item.command];
|
||||
const thisHash = JSON.stringify(thisArr);
|
||||
if (hash === thisHash) {
|
||||
return;
|
||||
}
|
||||
const arr = JSON.parse(hash);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (JSON.stringify(arr[i] !== JSON.stringify(thisArr[i]))) {
|
||||
return i.toString();
|
||||
}
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
class SignatureHelpAdapter {
|
||||
@@ -1392,7 +1319,6 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
|
||||
private readonly _uriTransformer: IURITransformer | null;
|
||||
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape;
|
||||
private readonly _telemetryShape: extHostProtocol.MainThreadTelemetryShape;
|
||||
private _documents: ExtHostDocuments;
|
||||
private _commands: ExtHostCommands;
|
||||
private _diagnostics: ExtHostDiagnostics;
|
||||
@@ -1411,7 +1337,6 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
) {
|
||||
this._uriTransformer = uriTransformer;
|
||||
this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadLanguageFeatures);
|
||||
this._telemetryShape = mainContext.getProxy(extHostProtocol.MainContext.MainThreadTelemetry);
|
||||
this._documents = documents;
|
||||
this._commands = commands;
|
||||
this._diagnostics = diagnostics;
|
||||
@@ -1780,7 +1705,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
// --- suggestion
|
||||
|
||||
registerCompletionItemProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable {
|
||||
const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._logService, this._apiDeprecation, this._telemetryShape, extension), extension);
|
||||
const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._apiDeprecation, extension), extension);
|
||||
this._proxy.$registerSuggestSupport(handle, this._transformDocumentSelector(selector), triggerCharacters, SuggestAdapter.supportsResolving(provider), extension.identifier);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
import { MainContext, MainThreadLanguagesShape, IMainContext } from './extHost.protocol';
|
||||
import type * as vscode from 'vscode';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { StandardTokenType, Range, Position } from 'vs/workbench/api/common/extHostTypes';
|
||||
|
||||
export class ExtHostLanguages {
|
||||
|
||||
@@ -32,4 +34,31 @@ export class ExtHostLanguages {
|
||||
}
|
||||
return data.document;
|
||||
}
|
||||
|
||||
async tokenAtPosition(document: vscode.TextDocument, position: vscode.Position): Promise<vscode.TokenInformation> {
|
||||
const versionNow = document.version;
|
||||
const pos = typeConvert.Position.from(position);
|
||||
const info = await this._proxy.$tokensAtPosition(document.uri, pos);
|
||||
const defaultRange = {
|
||||
type: StandardTokenType.Other,
|
||||
range: document.getWordRangeAtPosition(position) ?? new Range(position.line, position.character, position.line, position.character)
|
||||
};
|
||||
if (!info) {
|
||||
// no result
|
||||
return defaultRange;
|
||||
}
|
||||
const result = {
|
||||
range: typeConvert.Range.to(info.range),
|
||||
type: typeConvert.TokenType.to(info.type)
|
||||
};
|
||||
if (!result.range.contains(<Position>position)) {
|
||||
// bogous result
|
||||
return defaultRange;
|
||||
}
|
||||
if (versionNow !== document.version) {
|
||||
// concurrent change
|
||||
return defaultRange;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,16 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc
|
||||
import { ISplice } from 'vs/base/common/sequence';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { CellKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { Disposable as VSCodeDisposable } from './extHostTypes';
|
||||
import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, INotebookDisplayOrder, INotebookEditData, NotebookCellsChangedEvent, NotebookCellsSplice2, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseOutputInfo, IOutputRenderResponseCellInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
|
||||
import { NotImplementedProxy } from 'vs/base/common/types';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
|
||||
|
||||
interface IObservable<T> {
|
||||
proxy: T;
|
||||
@@ -40,6 +42,12 @@ function getObservable<T extends Object>(obj: T): IObservable<T> {
|
||||
};
|
||||
}
|
||||
|
||||
interface INotebookEventEmitter {
|
||||
emitModelChange(events: vscode.NotebookCellsChangeEvent): void;
|
||||
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void;
|
||||
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void;
|
||||
}
|
||||
|
||||
export class ExtHostCell extends Disposable implements vscode.NotebookCell {
|
||||
|
||||
// private originalSource: string[];
|
||||
@@ -59,14 +67,17 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell {
|
||||
return this._documentData.document;
|
||||
}
|
||||
|
||||
get notebook(): vscode.NotebookDocument {
|
||||
return this._notebook;
|
||||
}
|
||||
|
||||
get source() {
|
||||
// todo@jrieken remove this
|
||||
return this._documentData.getText();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly viewType: string,
|
||||
private readonly documentUri: URI,
|
||||
private readonly _notebook: ExtHostNotebookDocument,
|
||||
readonly handle: number,
|
||||
readonly uri: URI,
|
||||
content: string,
|
||||
@@ -133,7 +144,7 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell {
|
||||
}
|
||||
|
||||
private updateMetadata(): Promise<void> {
|
||||
return this._proxy.$updateNotebookCellMetadata(this.viewType, this.documentUri, this.handle, this._metadata);
|
||||
return this._proxy.$updateNotebookCellMetadata(this._notebook.viewType, this._notebook.uri, this.handle, this._metadata);
|
||||
}
|
||||
|
||||
attachTextDocument(document: ExtHostDocumentData) {
|
||||
@@ -217,9 +228,12 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
return this._versionId;
|
||||
}
|
||||
|
||||
private _disposed = false;
|
||||
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadNotebookShape,
|
||||
private _documentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private _emitter: INotebookEventEmitter,
|
||||
public viewType: string,
|
||||
public uri: URI,
|
||||
public renderingHandler: ExtHostNotebookOutputRenderingHandler
|
||||
@@ -238,6 +252,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposed = true;
|
||||
super.dispose();
|
||||
this._cellDisposableMapping.forEach(cell => cell.dispose());
|
||||
}
|
||||
@@ -246,7 +261,8 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
|
||||
get isDirty() { return false; }
|
||||
|
||||
accpetModelChanged(event: NotebookCellsChangedEvent) {
|
||||
accpetModelChanged(event: NotebookCellsChangedEvent): void {
|
||||
this._versionId = event.versionId;
|
||||
if (event.kind === NotebookCellsChangeType.ModelChange) {
|
||||
this.$spliceNotebookCells(event.changes);
|
||||
} else if (event.kind === NotebookCellsChangeType.Move) {
|
||||
@@ -258,19 +274,19 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
} else if (event.kind === NotebookCellsChangeType.ChangeLanguage) {
|
||||
this.$changeCellLanguage(event.index, event.language);
|
||||
}
|
||||
|
||||
this._versionId = event.versionId;
|
||||
}
|
||||
|
||||
private $spliceNotebookCells(splices: NotebookCellsSplice2[]): void {
|
||||
if (!splices.length) {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let contentChangeEvents: vscode.NotebookCellsChangeData[] = [];
|
||||
|
||||
splices.reverse().forEach(splice => {
|
||||
let cellDtos = splice[2];
|
||||
let newCells = cellDtos.map(cell => {
|
||||
const extCell = new ExtHostCell(this.viewType, this.uri, cell.handle, URI.revive(cell.uri), cell.source.join('\n'), cell.cellKind, cell.language, cell.outputs, cell.metadata, this._proxy);
|
||||
const extCell = new ExtHostCell(this, cell.handle, URI.revive(cell.uri), cell.source.join('\n'), cell.cellKind, cell.language, cell.outputs, cell.metadata, this._proxy);
|
||||
const documentData = this._documentsAndEditors.getDocument(URI.revive(cell.uri));
|
||||
|
||||
if (documentData) {
|
||||
@@ -297,107 +313,88 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
|
||||
}
|
||||
|
||||
this.cells.splice(splice[0], splice[1], ...newCells);
|
||||
|
||||
const event: vscode.NotebookCellsChangeData = {
|
||||
start: splice[0],
|
||||
deletedCount: splice[1],
|
||||
items: newCells
|
||||
};
|
||||
|
||||
contentChangeEvents.push(event);
|
||||
});
|
||||
|
||||
this._emitter.emitModelChange({
|
||||
document: this,
|
||||
changes: contentChangeEvents
|
||||
});
|
||||
}
|
||||
|
||||
private $moveCell(index: number, newIdx: number) {
|
||||
private $moveCell(index: number, newIdx: number): void {
|
||||
const cells = this.cells.splice(index, 1);
|
||||
this.cells.splice(newIdx, 0, ...cells);
|
||||
const changes: vscode.NotebookCellsChangeData[] = [{
|
||||
start: index,
|
||||
deletedCount: 1,
|
||||
items: []
|
||||
}, {
|
||||
start: newIdx,
|
||||
deletedCount: 0,
|
||||
items: cells
|
||||
}];
|
||||
this._emitter.emitModelChange({
|
||||
document: this,
|
||||
changes
|
||||
});
|
||||
}
|
||||
|
||||
private $clearCellOutputs(index: number) {
|
||||
private $clearCellOutputs(index: number): void {
|
||||
const cell = this.cells[index];
|
||||
cell.outputs = [];
|
||||
const event: vscode.NotebookCellOutputsChangeEvent = { document: this, cells: [cell] };
|
||||
this._emitter.emitCellOutputsChange(event);
|
||||
}
|
||||
|
||||
private $clearAllCellOutputs() {
|
||||
this.cells.forEach(cell => cell.outputs = []);
|
||||
private $clearAllCellOutputs(): void {
|
||||
const modifedCells: vscode.NotebookCell[] = [];
|
||||
this.cells.forEach(cell => {
|
||||
if (cell.outputs.length !== 0) {
|
||||
cell.outputs = [];
|
||||
modifedCells.push(cell);
|
||||
}
|
||||
});
|
||||
const event: vscode.NotebookCellOutputsChangeEvent = { document: this, cells: modifedCells };
|
||||
this._emitter.emitCellOutputsChange(event);
|
||||
}
|
||||
|
||||
private $changeCellLanguage(index: number, language: string) {
|
||||
private $changeCellLanguage(index: number, language: string): void {
|
||||
const cell = this.cells[index];
|
||||
cell.language = language;
|
||||
const event: vscode.NotebookCellLanguageChangeEvent = { document: this, cell, language };
|
||||
this._emitter.emitCellLanguageChange(event);
|
||||
}
|
||||
|
||||
eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice<vscode.CellOutput>[]) {
|
||||
async eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice<vscode.CellOutput>[]) {
|
||||
let renderers = new Set<number>();
|
||||
let outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => {
|
||||
let outputs = diff.toInsert;
|
||||
|
||||
let transformedOutputs = outputs.map(output => {
|
||||
if (output.outputKind === CellOutputKind.Rich) {
|
||||
const ret = this.transformMimeTypes(output);
|
||||
|
||||
if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) {
|
||||
renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return output as IStreamOutput | IErrorOutput;
|
||||
}
|
||||
});
|
||||
|
||||
return [diff.start, diff.deleteCount, transformedOutputs];
|
||||
return [diff.start, diff.deleteCount, outputs];
|
||||
});
|
||||
|
||||
this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers));
|
||||
}
|
||||
|
||||
transformMimeTypes(output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto {
|
||||
let mimeTypes = Object.keys(output.data);
|
||||
let coreDisplayOrder = this.renderingHandler.outputDisplayOrder;
|
||||
const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], this._displayOrder, coreDisplayOrder?.defaultOrder || []);
|
||||
|
||||
let orderMimeTypes: IOrderedMimeType[] = [];
|
||||
|
||||
sorted.forEach(mimeType => {
|
||||
let handlers = this.renderingHandler.findBestMatchedRenderer(mimeType);
|
||||
|
||||
if (handlers.length) {
|
||||
let renderedOutput = handlers[0].render(this, output, mimeType);
|
||||
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: true,
|
||||
rendererId: handlers[0].handle,
|
||||
output: renderedOutput
|
||||
});
|
||||
|
||||
for (let i = 1; i < handlers.length; i++) {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false,
|
||||
rendererId: handlers[i].handle
|
||||
});
|
||||
}
|
||||
|
||||
if (mimeTypeSupportedByCore(mimeType)) {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false,
|
||||
rendererId: -1
|
||||
});
|
||||
}
|
||||
} else {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false
|
||||
});
|
||||
}
|
||||
await this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers));
|
||||
this._emitter.emitCellOutputsChange({
|
||||
document: this,
|
||||
cells: [cell]
|
||||
});
|
||||
|
||||
return {
|
||||
outputKind: output.outputKind,
|
||||
data: output.data,
|
||||
orderedMimeTypes: orderMimeTypes,
|
||||
pickedMimeTypeIndex: 0
|
||||
};
|
||||
}
|
||||
|
||||
getCell(cellHandle: number) {
|
||||
return this.cells.find(cell => cell.handle === cellHandle);
|
||||
}
|
||||
|
||||
getCell2(cellUri: UriComponents) {
|
||||
return this.cells.find(cell => cell.uri.fragment === cellUri.fragment);
|
||||
}
|
||||
|
||||
attachCellTextDocument(textDocument: ExtHostDocumentData) {
|
||||
let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString());
|
||||
if (cell) {
|
||||
@@ -448,25 +445,10 @@ export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellE
|
||||
source: sourceArr,
|
||||
language,
|
||||
cellKind: type,
|
||||
outputs: (outputs as any[]), // TODO@rebornix
|
||||
outputs: outputs,
|
||||
metadata
|
||||
};
|
||||
|
||||
const transformedOutputs = outputs.map(output => {
|
||||
if (output.outputKind === CellOutputKind.Rich) {
|
||||
const ret = this.editor.document.transformMimeTypes(output);
|
||||
|
||||
if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) {
|
||||
this._renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return output as IStreamOutput | IErrorOutput;
|
||||
}
|
||||
});
|
||||
|
||||
cell.outputs = transformedOutputs;
|
||||
|
||||
this._collectedEdits.push({
|
||||
editType: CellEditType.Insert,
|
||||
index,
|
||||
@@ -489,6 +471,35 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
|
||||
private _viewColumn: vscode.ViewColumn | undefined;
|
||||
|
||||
selection?: ExtHostCell = undefined;
|
||||
|
||||
private _active: boolean = false;
|
||||
get active(): boolean {
|
||||
return this._active;
|
||||
}
|
||||
|
||||
set active(_state: boolean) {
|
||||
throw readonly('active');
|
||||
}
|
||||
|
||||
private _visible: boolean = false;
|
||||
get visible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
set visible(_state: boolean) {
|
||||
throw readonly('visible');
|
||||
}
|
||||
|
||||
_acceptVisibility(value: boolean) {
|
||||
this._visible = value;
|
||||
}
|
||||
|
||||
_acceptActive(value: boolean) {
|
||||
this._active = value;
|
||||
}
|
||||
|
||||
private _onDidDispose = new Emitter<void>();
|
||||
readonly onDidDispose: Event<void> = this._onDidDispose.event;
|
||||
onDidReceiveMessage: vscode.Event<any> = this._onDidReceiveMessage.event;
|
||||
|
||||
constructor(
|
||||
@@ -497,6 +508,7 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
|
||||
public uri: URI,
|
||||
private _proxy: MainThreadNotebookShape,
|
||||
private _onDidReceiveMessage: Emitter<any>,
|
||||
private _webviewInitData: WebviewInitData,
|
||||
public document: ExtHostNotebookDocument,
|
||||
private _documentsAndEditors: ExtHostDocumentsAndEditors
|
||||
) {
|
||||
@@ -584,6 +596,13 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
|
||||
return this._proxy.$postMessage(this.document.handle, message);
|
||||
}
|
||||
|
||||
asWebviewUri(localResource: vscode.Uri): vscode.Uri {
|
||||
return asWebviewUri(this._webviewInitData, this.id, localResource);
|
||||
}
|
||||
dispose() {
|
||||
this._onDidDispose.fire();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostNotebookOutputRenderer {
|
||||
@@ -620,16 +639,21 @@ export interface ExtHostNotebookOutputRenderingHandler {
|
||||
}
|
||||
|
||||
export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler {
|
||||
private static _handlePool: number = 0;
|
||||
|
||||
private readonly _proxy: MainThreadNotebookShape;
|
||||
private readonly _notebookContentProviders = new Map<string, { readonly provider: vscode.NotebookContentProvider, readonly extension: IExtensionDescription; }>();
|
||||
private readonly _notebookKernels = new Map<string, { readonly kernel: vscode.NotebookKernel, readonly extension: IExtensionDescription; }>();
|
||||
private readonly _documents = new Map<string, ExtHostNotebookDocument>();
|
||||
private readonly _unInitializedDocuments = new Map<string, ExtHostNotebookDocument>();
|
||||
private readonly _editors = new Map<string, { editor: ExtHostNotebookEditor, onDidReceiveMessage: Emitter<any>; }>();
|
||||
private readonly _notebookOutputRenderers = new Map<number, ExtHostNotebookOutputRenderer>();
|
||||
|
||||
private readonly _onDidChangeNotebookDocument = new Emitter<{ document: ExtHostNotebookDocument, changes: NotebookCellsChangedEvent[]; }>();
|
||||
readonly onDidChangeNotebookDocument: Event<{ document: ExtHostNotebookDocument, changes: NotebookCellsChangedEvent[]; }> = this._onDidChangeNotebookDocument.event;
|
||||
private readonly _notebookOutputRenderers = new Map<string, ExtHostNotebookOutputRenderer>();
|
||||
private readonly _onDidChangeNotebookCells = new Emitter<vscode.NotebookCellsChangeEvent>();
|
||||
readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event;
|
||||
private readonly _onDidChangeCellOutputs = new Emitter<vscode.NotebookCellOutputsChangeEvent>();
|
||||
readonly onDidChangeCellOutputs = this._onDidChangeCellOutputs.event;
|
||||
private readonly _onDidChangeCellLanguage = new Emitter<vscode.NotebookCellLanguageChangeEvent>();
|
||||
readonly onDidChangeCellLanguage = this._onDidChangeCellLanguage.event;
|
||||
private readonly _onDidChangeActiveNotebookEditor = new Emitter<vscode.NotebookEditor | undefined>();
|
||||
readonly onDidChangeActiveNotebookEditor = this._onDidChangeActiveNotebookEditor.event;
|
||||
|
||||
private _outputDisplayOrder: INotebookDisplayOrder | undefined;
|
||||
|
||||
@@ -653,8 +677,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
onDidOpenNotebookDocument: Event<vscode.NotebookDocument> = this._onDidOpenNotebookDocument.event;
|
||||
private _onDidCloseNotebookDocument = new Emitter<vscode.NotebookDocument>();
|
||||
onDidCloseNotebookDocument: Event<vscode.NotebookDocument> = this._onDidCloseNotebookDocument.event;
|
||||
visibleNotebookEditors: ExtHostNotebookEditor[] = [];
|
||||
private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>();
|
||||
onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event;
|
||||
|
||||
constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors) {
|
||||
constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors, private readonly _webviewInitData: WebviewInitData) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook);
|
||||
|
||||
commands.registerArgumentProcessor({
|
||||
@@ -683,15 +710,85 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
filter: vscode.NotebookOutputSelector,
|
||||
renderer: vscode.NotebookOutputRenderer
|
||||
): vscode.Disposable {
|
||||
if (this._notebookKernels.has(type)) {
|
||||
throw new Error(`Notebook renderer for '${type}' already registered`);
|
||||
}
|
||||
|
||||
let extHostRenderer = new ExtHostNotebookOutputRenderer(type, filter, renderer);
|
||||
this._notebookOutputRenderers.set(extHostRenderer.handle, extHostRenderer);
|
||||
this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, extHostRenderer.handle, renderer.preloads || []);
|
||||
return new VSCodeDisposable(() => {
|
||||
this._notebookOutputRenderers.delete(extHostRenderer.handle);
|
||||
this._proxy.$unregisterNotebookRenderer(extHostRenderer.handle);
|
||||
this._notebookOutputRenderers.set(extHostRenderer.type, extHostRenderer);
|
||||
this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, renderer.preloads || []);
|
||||
return new extHostTypes.Disposable(() => {
|
||||
this._notebookOutputRenderers.delete(extHostRenderer.type);
|
||||
this._proxy.$unregisterNotebookRenderer(extHostRenderer.type);
|
||||
});
|
||||
}
|
||||
|
||||
async $renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined> {
|
||||
if (!this._notebookOutputRenderers.has(id)) {
|
||||
throw new Error(`Notebook renderer for '${id}' is not registered`);
|
||||
}
|
||||
|
||||
const document = this._documents.get(URI.revive(uriComponents).toString());
|
||||
|
||||
if (!document) {
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-checks
|
||||
}
|
||||
|
||||
const renderer = this._notebookOutputRenderers.get(id)!;
|
||||
const cellsResponse: IOutputRenderResponseCellInfo<UriComponents>[] = request.items.map(cellInfo => {
|
||||
const cell = document.getCell2(cellInfo.key);
|
||||
const outputResponse: IOutputRenderResponseOutputInfo[] = cellInfo.outputs.map(output => {
|
||||
return {
|
||||
index: output.index,
|
||||
mimeType: output.mimeType,
|
||||
handlerId: id,
|
||||
transformedOutput: renderer.render(document, cell!.outputs[output.index] as vscode.CellDisplayOutput, output.mimeType)
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
key: cellInfo.key,
|
||||
outputs: outputResponse
|
||||
};
|
||||
});
|
||||
|
||||
return { items: cellsResponse };
|
||||
}
|
||||
|
||||
/**
|
||||
* The request carry the raw data for outputs so we don't look up in the existing document
|
||||
*/
|
||||
async $renderOutputs2<T>(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined> {
|
||||
if (!this._notebookOutputRenderers.has(id)) {
|
||||
throw new Error(`Notebook renderer for '${id}' is not registered`);
|
||||
}
|
||||
|
||||
const document = this._documents.get(URI.revive(uriComponents).toString());
|
||||
|
||||
if (!document) {
|
||||
return undefined; // {{SQL CARBON EDIT}} strict-null-checks
|
||||
}
|
||||
|
||||
const renderer = this._notebookOutputRenderers.get(id)!;
|
||||
const cellsResponse: IOutputRenderResponseCellInfo<T>[] = request.items.map(cellInfo => {
|
||||
const outputResponse: IOutputRenderResponseOutputInfo[] = cellInfo.outputs.map(output => {
|
||||
return {
|
||||
index: output.index,
|
||||
mimeType: output.mimeType,
|
||||
handlerId: id,
|
||||
transformedOutput: renderer.render(document, output.output! as vscode.CellDisplayOutput, output.mimeType)
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
key: cellInfo.key,
|
||||
outputs: outputResponse
|
||||
};
|
||||
});
|
||||
|
||||
return { items: cellsResponse };
|
||||
}
|
||||
|
||||
findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[] {
|
||||
let matches: ExtHostNotebookOutputRenderer[] = [];
|
||||
for (let renderer of this._notebookOutputRenderers) {
|
||||
@@ -713,50 +810,70 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
throw new Error(`Notebook provider for '${viewType}' already registered`);
|
||||
}
|
||||
|
||||
// if ((<any>provider).executeCell) {
|
||||
// throw new Error('NotebookContentKernel.executeCell is removed, please use vscode.notebook.registerNotebookKernel instead.');
|
||||
// }
|
||||
|
||||
this._notebookContentProviders.set(viewType, { extension, provider });
|
||||
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType);
|
||||
return new VSCodeDisposable(() => {
|
||||
|
||||
const listener = provider.onDidChangeNotebook
|
||||
? provider.onDidChangeNotebook(e => this._proxy.$onNotebookChange(viewType, e.document.uri))
|
||||
: Disposable.None;
|
||||
|
||||
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined);
|
||||
return new extHostTypes.Disposable(() => {
|
||||
listener.dispose();
|
||||
this._notebookContentProviders.delete(viewType);
|
||||
this._proxy.$unregisterNotebookProvider(viewType);
|
||||
});
|
||||
}
|
||||
|
||||
async $resolveNotebookData(viewType: string, uri: UriComponents): Promise<NotebookDataDto | undefined> {
|
||||
let provider = this._notebookContentProviders.get(viewType);
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
registerNotebookKernel(extension: IExtensionDescription, id: string, selectors: vscode.GlobPattern[], kernel: vscode.NotebookKernel): vscode.Disposable {
|
||||
if (this._notebookKernels.has(id)) {
|
||||
throw new Error(`Notebook kernel for '${id}' already registered`);
|
||||
}
|
||||
|
||||
this._notebookKernels.set(id, { kernel, extension });
|
||||
const transformedSelectors = selectors.map(selector => typeConverters.GlobPattern.from(selector));
|
||||
|
||||
this._proxy.$registerNotebookKernel({ id: extension.identifier, location: extension.extensionLocation }, id, kernel.label, transformedSelectors, kernel.preloads || []);
|
||||
return new extHostTypes.Disposable(() => {
|
||||
this._notebookKernels.delete(id);
|
||||
this._proxy.$unregisterNotebookKernel(id);
|
||||
});
|
||||
}
|
||||
|
||||
async $resolveNotebookData(viewType: string, uri: UriComponents): Promise<NotebookDataDto | undefined> {
|
||||
const provider = this._notebookContentProviders.get(viewType);
|
||||
const revivedUri = URI.revive(uri);
|
||||
|
||||
if (provider) {
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
|
||||
if (!document) {
|
||||
const that = this;
|
||||
document = this._unInitializedDocuments.get(revivedUri.toString()) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, {
|
||||
emitModelChange(event: vscode.NotebookCellsChangeEvent): void {
|
||||
that._onDidChangeNotebookCells.fire(event);
|
||||
},
|
||||
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void {
|
||||
that._onDidChangeCellOutputs.fire(event);
|
||||
},
|
||||
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void {
|
||||
that._onDidChangeCellLanguage.fire(event);
|
||||
}
|
||||
}, viewType, revivedUri, this);
|
||||
this._unInitializedDocuments.set(revivedUri.toString(), document);
|
||||
}
|
||||
|
||||
if (provider && document) {
|
||||
const rawCells = await provider.provider.openNotebook(URI.revive(uri));
|
||||
const renderers = new Set<number>();
|
||||
const dto = {
|
||||
metadata: {
|
||||
...notebookDocumentMetadataDefaults,
|
||||
...rawCells.metadata
|
||||
},
|
||||
languages: rawCells.languages,
|
||||
cells: rawCells.cells.map(cell => {
|
||||
let transformedOutputs = cell.outputs.map(output => {
|
||||
if (output.outputKind === CellOutputKind.Rich) {
|
||||
// TODO display string[]
|
||||
const ret = this._transformMimeTypes(document!, (rawCells.metadata.displayOrder as string[]) || [], output);
|
||||
|
||||
if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) {
|
||||
renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return output as IStreamOutput | IErrorOutput;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
language: cell.language,
|
||||
cellKind: cell.cellKind,
|
||||
metadata: cell.metadata,
|
||||
source: cell.source,
|
||||
outputs: transformedOutputs
|
||||
};
|
||||
})
|
||||
cells: rawCells.cells,
|
||||
};
|
||||
|
||||
return dto;
|
||||
@@ -765,58 +882,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
return undefined; // {{SQL CARBON EDIT}}
|
||||
}
|
||||
|
||||
private _transformMimeTypes(document: ExtHostNotebookDocument, displayOrder: string[], output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto {
|
||||
let mimeTypes = Object.keys(output.data);
|
||||
let coreDisplayOrder = this.outputDisplayOrder;
|
||||
const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], displayOrder, coreDisplayOrder?.defaultOrder || []);
|
||||
|
||||
let orderMimeTypes: IOrderedMimeType[] = [];
|
||||
|
||||
sorted.forEach(mimeType => {
|
||||
let handlers = this.findBestMatchedRenderer(mimeType);
|
||||
|
||||
if (handlers.length) {
|
||||
let renderedOutput = handlers[0].render(document, output, mimeType);
|
||||
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: true,
|
||||
rendererId: handlers[0].handle,
|
||||
output: renderedOutput
|
||||
});
|
||||
|
||||
for (let i = 1; i < handlers.length; i++) {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false,
|
||||
rendererId: handlers[i].handle
|
||||
});
|
||||
}
|
||||
|
||||
if (mimeTypeSupportedByCore(mimeType)) {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false,
|
||||
rendererId: -1
|
||||
});
|
||||
}
|
||||
} else {
|
||||
orderMimeTypes.push({
|
||||
mimeType: mimeType,
|
||||
isResolved: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
outputKind: output.outputKind,
|
||||
data: output.data,
|
||||
orderedMimeTypes: orderMimeTypes,
|
||||
pickedMimeTypeIndex: 0
|
||||
};
|
||||
}
|
||||
|
||||
async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void> {
|
||||
async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise<void> {
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
|
||||
if (!document) {
|
||||
@@ -824,9 +890,38 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
}
|
||||
|
||||
if (this._notebookContentProviders.has(viewType)) {
|
||||
let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
const provider = this._notebookContentProviders.get(viewType)!.provider;
|
||||
|
||||
return this._notebookContentProviders.get(viewType)!.provider.executeCell(document, cell, token);
|
||||
if (provider.kernel && useAttachedKernel) {
|
||||
if (cell) {
|
||||
return provider.kernel.executeCell(document, cell, token);
|
||||
} else {
|
||||
return provider.kernel.executeAllCells(document, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void> {
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
|
||||
if (!document || document.viewType !== viewType) {
|
||||
return;
|
||||
}
|
||||
|
||||
let kernelInfo = this._notebookKernels.get(kernelId);
|
||||
|
||||
if (!kernelInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
|
||||
if (cell) {
|
||||
return kernelInfo.kernel.executeCell(document, cell, token);
|
||||
} else {
|
||||
return kernelInfo.kernel.executeAllCells(document, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,8 +967,21 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
this._outputDisplayOrder = displayOrder;
|
||||
}
|
||||
|
||||
$onDidReceiveMessage(uri: UriComponents, message: any): void {
|
||||
let editor = this._editors.get(URI.revive(uri).toString());
|
||||
// TODO: remove document - editor one on one mapping
|
||||
private _getEditorFromURI(uriComponents: UriComponents) {
|
||||
const uriStr = URI.revive(uriComponents).toString();
|
||||
let editor: { editor: ExtHostNotebookEditor, onDidReceiveMessage: Emitter<any>; } | undefined;
|
||||
this._editors.forEach(e => {
|
||||
if (e.editor.uri.toString() === uriStr) {
|
||||
editor = e;
|
||||
}
|
||||
});
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
$onDidReceiveMessage(editorId: string, message: any): void {
|
||||
let editor = this._editors.get(editorId);
|
||||
|
||||
if (editor) {
|
||||
editor.onDidReceiveMessage.fire(message);
|
||||
@@ -881,20 +989,15 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
}
|
||||
|
||||
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void {
|
||||
let editor = this._editors.get(URI.revive(uriComponents).toString());
|
||||
const document = this._documents.get(URI.revive(uriComponents).toString());
|
||||
|
||||
if (editor) {
|
||||
editor.editor.document.accpetModelChanged(event);
|
||||
this._onDidChangeNotebookDocument.fire({
|
||||
document: editor.editor.document,
|
||||
changes: [event]
|
||||
});
|
||||
if (document) {
|
||||
document.accpetModelChanged(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void {
|
||||
let editor = this._editors.get(URI.revive(uriComponents).toString());
|
||||
let editor = this._getEditorFromURI(uriComponents);
|
||||
|
||||
if (!editor) {
|
||||
return;
|
||||
@@ -910,61 +1013,196 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
editor.editor.selection = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.metadata) {
|
||||
editor.editor.document.metadata = {
|
||||
...notebookDocumentMetadataDefaults,
|
||||
...data.metadata
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _createExtHostEditor(document: ExtHostNotebookDocument, editorId: string, selections: number[]) {
|
||||
const onDidReceiveMessage = new Emitter<any>();
|
||||
const revivedUri = document.uri;
|
||||
|
||||
let editor = new ExtHostNotebookEditor(
|
||||
document.viewType,
|
||||
editorId,
|
||||
revivedUri,
|
||||
this._proxy,
|
||||
onDidReceiveMessage,
|
||||
this._webviewInitData,
|
||||
document,
|
||||
this._documentsAndEditors
|
||||
);
|
||||
|
||||
const cells = editor.document.cells;
|
||||
|
||||
if (selections.length) {
|
||||
const firstCell = selections[0];
|
||||
editor.selection = cells.find(cell => cell.handle === firstCell);
|
||||
} else {
|
||||
editor.selection = undefined;
|
||||
}
|
||||
|
||||
this._editors.get(editorId)?.editor.dispose();
|
||||
|
||||
this._editors.set(editorId, { editor, onDidReceiveMessage });
|
||||
}
|
||||
|
||||
async $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta) {
|
||||
let editorChanged = false;
|
||||
|
||||
if (delta.removedDocuments) {
|
||||
delta.removedDocuments.forEach((uri) => {
|
||||
let document = this._documents.get(URI.revive(uri).toString());
|
||||
const revivedUri = URI.revive(uri);
|
||||
const revivedUriStr = revivedUri.toString();
|
||||
let document = this._documents.get(revivedUriStr);
|
||||
|
||||
if (document) {
|
||||
document.dispose();
|
||||
this._documents.delete(URI.revive(uri).toString());
|
||||
this._documents.delete(revivedUriStr);
|
||||
this._onDidCloseNotebookDocument.fire(document);
|
||||
}
|
||||
|
||||
let editor = this._editors.get(URI.revive(uri).toString());
|
||||
|
||||
if (editor) {
|
||||
editor.editor.dispose();
|
||||
editor.onDidReceiveMessage.dispose();
|
||||
this._editors.delete(URI.revive(uri).toString());
|
||||
}
|
||||
[...this._editors.values()].forEach((e) => {
|
||||
if (e.editor.uri.toString() === revivedUriStr) {
|
||||
e.editor.dispose();
|
||||
e.onDidReceiveMessage.dispose();
|
||||
this._editors.delete(e.editor.id);
|
||||
editorChanged = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (delta.addedDocuments) {
|
||||
delta.addedDocuments.forEach(modelData => {
|
||||
const revivedUri = URI.revive(modelData.uri);
|
||||
const revivedUriStr = revivedUri.toString();
|
||||
const viewType = modelData.viewType;
|
||||
if (!this._documents.has(revivedUri.toString())) {
|
||||
let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this);
|
||||
this._documents.set(revivedUri.toString(), document);
|
||||
if (!this._documents.has(revivedUriStr)) {
|
||||
const that = this;
|
||||
let document = this._unInitializedDocuments.get(revivedUriStr) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, {
|
||||
emitModelChange(event: vscode.NotebookCellsChangeEvent): void {
|
||||
that._onDidChangeNotebookCells.fire(event);
|
||||
},
|
||||
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void {
|
||||
that._onDidChangeCellOutputs.fire(event);
|
||||
},
|
||||
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void {
|
||||
that._onDidChangeCellLanguage.fire(event);
|
||||
}
|
||||
}, viewType, revivedUri, this);
|
||||
|
||||
this._unInitializedDocuments.delete(revivedUriStr);
|
||||
if (modelData.metadata) {
|
||||
document.metadata = {
|
||||
...notebookDocumentMetadataDefaults,
|
||||
...modelData.metadata
|
||||
};
|
||||
}
|
||||
|
||||
document.accpetModelChanged({
|
||||
kind: NotebookCellsChangeType.ModelChange,
|
||||
versionId: modelData.versionId,
|
||||
changes: [[
|
||||
0,
|
||||
0,
|
||||
modelData.cells
|
||||
]]
|
||||
});
|
||||
|
||||
this._documents.get(revivedUriStr)?.dispose();
|
||||
this._documents.set(revivedUriStr, document);
|
||||
|
||||
// create editor if populated
|
||||
if (modelData.attachedEditor) {
|
||||
this._createExtHostEditor(document, modelData.attachedEditor.id, modelData.attachedEditor.selections);
|
||||
editorChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
const onDidReceiveMessage = new Emitter<any>();
|
||||
const document = this._documents.get(revivedUri.toString())!;
|
||||
|
||||
let editor = new ExtHostNotebookEditor(
|
||||
viewType,
|
||||
`${ExtHostNotebookController._handlePool++}`,
|
||||
revivedUri,
|
||||
this._proxy,
|
||||
onDidReceiveMessage,
|
||||
document,
|
||||
this._documentsAndEditors
|
||||
);
|
||||
|
||||
const document = this._documents.get(revivedUriStr)!;
|
||||
this._onDidOpenNotebookDocument.fire(document);
|
||||
|
||||
// TODO, does it already exist?
|
||||
this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage });
|
||||
});
|
||||
}
|
||||
|
||||
if (delta.newActiveEditor) {
|
||||
this._activeNotebookDocument = this._documents.get(URI.revive(delta.newActiveEditor).toString());
|
||||
this._activeNotebookEditor = this._editors.get(URI.revive(delta.newActiveEditor).toString())?.editor;
|
||||
if (delta.addedEditors) {
|
||||
delta.addedEditors.forEach(editorModelData => {
|
||||
if (this._editors.has(editorModelData.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const revivedUri = URI.revive(editorModelData.documentUri);
|
||||
const document = this._documents.get(revivedUri.toString());
|
||||
|
||||
if (document) {
|
||||
this._createExtHostEditor(document, editorModelData.id, editorModelData.selections);
|
||||
editorChanged = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const removedEditors: { editor: ExtHostNotebookEditor, onDidReceiveMessage: Emitter<any>; }[] = [];
|
||||
|
||||
if (delta.removedEditors) {
|
||||
delta.removedEditors.forEach(editorid => {
|
||||
const editor = this._editors.get(editorid);
|
||||
|
||||
if (editor) {
|
||||
editorChanged = true;
|
||||
this._editors.delete(editorid);
|
||||
|
||||
if (this.activeNotebookEditor?.id === editor.editor.id) {
|
||||
this._activeNotebookEditor = undefined;
|
||||
this._activeNotebookDocument = undefined;
|
||||
}
|
||||
|
||||
removedEditors.push(editor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (editorChanged) {
|
||||
removedEditors.forEach(e => {
|
||||
e.editor.dispose();
|
||||
e.onDidReceiveMessage.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
if (delta.visibleEditors) {
|
||||
this.visibleNotebookEditors = delta.visibleEditors.map(id => this._editors.get(id)?.editor).filter(editor => !!editor) as ExtHostNotebookEditor[];
|
||||
const visibleEditorsSet = new Set<string>();
|
||||
this.visibleNotebookEditors.forEach(editor => visibleEditorsSet.add(editor.id));
|
||||
|
||||
[...this._editors.values()].forEach((e) => {
|
||||
const newValue = visibleEditorsSet.has(e.editor.id);
|
||||
e.editor._acceptVisibility(newValue);
|
||||
});
|
||||
|
||||
this.visibleNotebookEditors = [...this._editors.values()].map(e => e.editor).filter(e => e.visible);
|
||||
this._onDidChangeVisibleNotebookEditors.fire(this.visibleNotebookEditors);
|
||||
}
|
||||
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
if (delta.newActiveEditor) {
|
||||
this._activeNotebookEditor = this._editors.get(delta.newActiveEditor)?.editor;
|
||||
this._activeNotebookEditor?._acceptActive(true);
|
||||
this._activeNotebookDocument = this._activeNotebookEditor ? this._documents.get(this._activeNotebookEditor!.uri.toString()) : undefined;
|
||||
} else {
|
||||
this._activeNotebookEditor = undefined;
|
||||
this._activeNotebookDocument = undefined;
|
||||
}
|
||||
|
||||
this._onDidChangeActiveNotebookEditor.fire(this._activeNotebookEditor);
|
||||
}
|
||||
|
||||
[...this._editors.values()].forEach((e) => {
|
||||
if (e.editor !== this.activeNotebookEditor) {
|
||||
e.editor._acceptActive(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +44,17 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
}));
|
||||
this._disposables.add(extHostNotebooks.onDidChangeNotebookDocument(e => {
|
||||
if (e.document === this._notebook) {
|
||||
const documentChange = (document: vscode.NotebookDocument) => {
|
||||
if (document === this._notebook) {
|
||||
this._init();
|
||||
this._versionId += 1;
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
this._disposables.add(extHostNotebooks.onDidChangeCellLanguage(e => documentChange(e.document)));
|
||||
this._disposables.add(extHostNotebooks.onDidChangeCellOutputs(e => documentChange(e.document)));
|
||||
this._disposables.add(extHostNotebooks.onDidChangeNotebookCells(e => documentChange(e.document)));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
@@ -35,8 +35,9 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
private _timeoutHandle: any;
|
||||
private _proxy: MainThreadStatusBarShape;
|
||||
private _commands: CommandsConverter;
|
||||
private _accessibilityInformation?: vscode.AccessibilityInformation;
|
||||
|
||||
constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) {
|
||||
constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation) {
|
||||
this._id = ExtHostStatusBarEntry.ID_GEN++;
|
||||
this._proxy = proxy;
|
||||
this._commands = commands;
|
||||
@@ -44,6 +45,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
this._statusName = name;
|
||||
this._alignment = alignment;
|
||||
this._priority = priority;
|
||||
this._accessibilityInformation = accessibilityInformation;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
@@ -74,6 +76,10 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
return this._command?.fromApi;
|
||||
}
|
||||
|
||||
public get accessibilityInformation(): vscode.AccessibilityInformation | undefined {
|
||||
return this._accessibilityInformation;
|
||||
}
|
||||
|
||||
public set text(text: string) {
|
||||
this._text = text;
|
||||
this.update();
|
||||
@@ -136,7 +142,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
// Set to status bar
|
||||
this._proxy.$setEntry(this.id, this._statusId, this._statusName, this.text, this.tooltip, this._command?.internal, this.color,
|
||||
this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT,
|
||||
this._priority);
|
||||
this._priority, this._accessibilityInformation);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -196,8 +202,8 @@ export class ExtHostStatusBar {
|
||||
this._statusMessage = new StatusBarMessage(this);
|
||||
}
|
||||
|
||||
createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem {
|
||||
return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority);
|
||||
createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation): vscode.StatusBarItem {
|
||||
return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority, accessibilityInformation);
|
||||
}
|
||||
|
||||
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): Disposable {
|
||||
|
||||
@@ -384,6 +384,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
|
||||
protected _handleCounter: number;
|
||||
protected _handlers: Map<number, HandlerData>;
|
||||
protected _taskExecutions: Map<string, TaskExecutionImpl>;
|
||||
protected _taskExecutionPromises: Map<string, Promise<TaskExecutionImpl>>;
|
||||
protected _providedCustomExecutions2: Map<string, types.CustomExecution>;
|
||||
private _notProvidedCustomExecutions: Set<string>; // Used for custom executions tasks that are created and run through executeTask.
|
||||
protected _activeCustomExecutions2: Map<string, types.CustomExecution>;
|
||||
@@ -412,6 +413,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
|
||||
this._handleCounter = 0;
|
||||
this._handlers = new Map<number, HandlerData>();
|
||||
this._taskExecutions = new Map<string, TaskExecutionImpl>();
|
||||
this._taskExecutionPromises = new Map<string, Promise<TaskExecutionImpl>>();
|
||||
this._providedCustomExecutions2 = new Map<string, types.CustomExecution>();
|
||||
this._notProvidedCustomExecutions = new Set<string>();
|
||||
this._activeCustomExecutions2 = new Map<string, types.CustomExecution>();
|
||||
@@ -496,6 +498,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
|
||||
|
||||
public async $OnDidEndTask(execution: tasks.TaskExecutionDTO): Promise<void> {
|
||||
const _execution = await this.getTaskExecution(execution);
|
||||
this._taskExecutionPromises.delete(execution.id);
|
||||
this._taskExecutions.delete(execution.id);
|
||||
this.customExecutionComplete(execution);
|
||||
this._onDidTerminateTask.fire({
|
||||
@@ -626,17 +629,24 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape {
|
||||
return taskExecution;
|
||||
}
|
||||
|
||||
let result: TaskExecutionImpl | undefined = this._taskExecutions.get(execution.id);
|
||||
let result: Promise<TaskExecutionImpl> | undefined = this._taskExecutionPromises.get(execution.id);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider);
|
||||
if (!taskToCreate) {
|
||||
throw new Error('Unexpected: Task does not exist.');
|
||||
}
|
||||
const createdResult: TaskExecutionImpl = new TaskExecutionImpl(this, execution.id, taskToCreate);
|
||||
this._taskExecutions.set(execution.id, createdResult);
|
||||
return createdResult;
|
||||
const createdResult: Promise<TaskExecutionImpl> = new Promise(async (resolve, reject) => {
|
||||
const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider);
|
||||
if (!taskToCreate) {
|
||||
reject('Unexpected: Task does not exist.');
|
||||
} else {
|
||||
resolve(new TaskExecutionImpl(this, execution.id, taskToCreate));
|
||||
}
|
||||
});
|
||||
|
||||
this._taskExecutionPromises.set(execution.id, createdResult);
|
||||
return createdResult.then(result => {
|
||||
this._taskExecutions.set(execution.id, result);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
protected checkDeprecation(task: vscode.Task, handler: HandlerData) {
|
||||
|
||||
@@ -152,7 +152,8 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined,
|
||||
icon: icon,
|
||||
iconDark: iconDark,
|
||||
themeIcon: themeIcon
|
||||
themeIcon: themeIcon,
|
||||
accessibilityInformation: item.accessibilityInformation
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -188,4 +189,3 @@ export class ExtHostTimeline implements IExtHostTimeline {
|
||||
function getUriKey(uri: URI | undefined): string | undefined {
|
||||
return uri?.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -498,7 +498,7 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
return node;
|
||||
}
|
||||
|
||||
protected createTreeNode(element: T, extensionTreeItem: azdata.TreeItem, parent: TreeNode | Root): TreeNode { // {{SQL CARBON EDIT}} change to protected, change to azdata.TreeItem
|
||||
protected createTreeNode(element: T, extensionTreeItem: azdata.TreeItem2, parent: TreeNode | Root): TreeNode { // {{SQL CARBON EDIT}} change to protected, change to azdata.TreeItem
|
||||
const disposable = new DisposableStore();
|
||||
const handle = this.createHandle(element, extensionTreeItem, parent);
|
||||
const icon = this.getLightIconPath(extensionTreeItem);
|
||||
@@ -515,10 +515,10 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
|
||||
themeIcon: extensionTreeItem.iconPath instanceof ThemeIcon ? { id: extensionTreeItem.iconPath.id } : undefined,
|
||||
collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState,
|
||||
// {{SQL CARBON EDIT}}
|
||||
payload: extensionTreeItem.payload,
|
||||
childProvider: extensionTreeItem.childProvider,
|
||||
type: extensionTreeItem.type
|
||||
accessibilityInformation: extensionTreeItem.accessibilityInformation,
|
||||
payload: extensionTreeItem.payload, // {{SQL CARBON EDIT}}
|
||||
childProvider: extensionTreeItem.childProvider, // {{SQL CARBON EDIT}}
|
||||
type: extensionTreeItem.type // {{SQL CARBON EDIT}}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -95,11 +95,22 @@ export namespace Range {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace TokenType {
|
||||
export function to(type: modes.StandardTokenType): types.StandardTokenType {
|
||||
switch (type) {
|
||||
case modes.StandardTokenType.Comment: return types.StandardTokenType.Comment;
|
||||
case modes.StandardTokenType.Other: return types.StandardTokenType.Other;
|
||||
case modes.StandardTokenType.RegEx: return types.StandardTokenType.RegEx;
|
||||
case modes.StandardTokenType.String: return types.StandardTokenType.String;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Position {
|
||||
export function to(position: IPosition): types.Position {
|
||||
return new types.Position(position.lineNumber - 1, position.column - 1);
|
||||
}
|
||||
export function from(position: types.Position): IPosition {
|
||||
export function from(position: types.Position | vscode.Position): IPosition {
|
||||
return { lineNumber: position.line + 1, column: position.character + 1 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2766,3 +2766,17 @@ export enum ExtensionMode {
|
||||
}
|
||||
|
||||
//#endregion ExtensionContext
|
||||
|
||||
|
||||
//#region Authentication
|
||||
export class AuthenticationSession implements vscode.AuthenticationSession2 {
|
||||
constructor(public id: string, public accessToken: string, public account: { displayName: string, id: string }, public scopes: string[]) { }
|
||||
}
|
||||
|
||||
//#endregion Authentication
|
||||
export enum StandardTokenType {
|
||||
Other = 0,
|
||||
Comment = 1,
|
||||
String = 2,
|
||||
RegEx = 4
|
||||
}
|
||||
|
||||
@@ -573,7 +573,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
}
|
||||
const { serializer, extension } = entry;
|
||||
|
||||
const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension, this._deprecationService);
|
||||
const webview = new ExtHostWebview(webviewHandle, this._proxy, reviveOptions(options), this.initData, this.workspace, extension, this._deprecationService);
|
||||
const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
|
||||
this._webviewPanels.set(webviewHandle, revivedPanel);
|
||||
await serializer.deserializeWebviewPanel(revivedPanel, state);
|
||||
@@ -628,7 +628,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
throw new Error(`No provider found for '${viewType}'`);
|
||||
}
|
||||
|
||||
const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, entry.extension, this._deprecationService);
|
||||
const webview = new ExtHostWebview(handle, this._proxy, reviveOptions(options), this.initData, this.workspace, entry.extension, this._deprecationService);
|
||||
const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
|
||||
this._webviewPanels.set(handle, revivedPanel);
|
||||
|
||||
@@ -761,6 +761,15 @@ function convertWebviewOptions(
|
||||
};
|
||||
}
|
||||
|
||||
function reviveOptions(
|
||||
options: modes.IWebviewOptions & modes.IWebviewPanelOptions
|
||||
): vscode.WebviewOptions {
|
||||
return {
|
||||
...options,
|
||||
localResourceRoots: options.localResourceRoots?.map(components => URI.from(components)),
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultLocalResourceRoots(
|
||||
extension: IExtensionDescription,
|
||||
workspace: IExtHostWorkspace | undefined,
|
||||
|
||||
@@ -7,10 +7,9 @@ import { generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import * as http from 'http';
|
||||
import * as fs from 'fs';
|
||||
import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
import { IWindowOpenable, IOpenWindowOptions } from 'vs/platform/windows/common/windows';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export interface OpenCommandPipeArgs {
|
||||
@@ -123,7 +122,7 @@ export class CLIServer {
|
||||
if (urisToOpen.length) {
|
||||
const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined;
|
||||
const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode;
|
||||
const windowOpenArgs: INativeOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI };
|
||||
const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI };
|
||||
this._commands.executeCommand('_files.windowOpen', urisToOpen, windowOpenArgs);
|
||||
}
|
||||
res.writeHead(200);
|
||||
|
||||
@@ -50,7 +50,8 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
}
|
||||
|
||||
// fetch JS sources as text and create a new function around it
|
||||
const initFn = new Function('module', 'exports', 'require', 'window', await response.text());
|
||||
const source = await response.text();
|
||||
const initFn = new Function('module', 'exports', 'require', 'window', `${source}\n//# sourceURL=${module.toString(true)}`);
|
||||
|
||||
// define commonjs globals: `module`, `exports`, and `require`
|
||||
const _exports = {};
|
||||
|
||||
@@ -141,15 +141,25 @@ class ToggleScreencastModeAction extends Action {
|
||||
const keyboardMarker = append(container, $('.screencast-keyboard'));
|
||||
disposables.add(toDisposable(() => keyboardMarker.remove()));
|
||||
|
||||
const updateKeyboardFontSize = () => {
|
||||
keyboardMarker.style.fontSize = `${clamp(this.configurationService.getValue<number>('screencastMode.fontSize') || 56, 20, 100)}px`;
|
||||
};
|
||||
|
||||
const updateKeyboardMarker = () => {
|
||||
keyboardMarker.style.bottom = `${clamp(this.configurationService.getValue<number>('screencastMode.verticalOffset') || 0, 0, 90)}%`;
|
||||
};
|
||||
|
||||
updateKeyboardFontSize();
|
||||
updateKeyboardMarker();
|
||||
|
||||
disposables.add(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('screencastMode.verticalOffset')) {
|
||||
updateKeyboardMarker();
|
||||
}
|
||||
|
||||
if (e.affectsConfiguration('screencastMode.fontSize')) {
|
||||
updateKeyboardFontSize();
|
||||
}
|
||||
}));
|
||||
|
||||
const onKeyDown = domEvent(window, 'keydown', true);
|
||||
@@ -261,6 +271,13 @@ configurationRegistry.registerConfiguration({
|
||||
maximum: 90,
|
||||
description: nls.localize('screencastMode.location.verticalPosition', "Controls the vertical offset of the screencast mode overlay from the bottom as a percentage of the workbench height.")
|
||||
},
|
||||
'screencastMode.fontSize': {
|
||||
type: 'number',
|
||||
default: 56,
|
||||
minimum: 20,
|
||||
maximum: 100,
|
||||
description: nls.localize('screencastMode.fontSize', "Controls the font size (in pixels) of the screencast mode keyboard.")
|
||||
},
|
||||
'screencastMode.onlyKeyboardShortcuts': {
|
||||
type: 'boolean',
|
||||
description: nls.localize('screencastMode.onlyKeyboardShortcuts', "Only show keyboard shortcuts in Screencast Mode."),
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
|
||||
@@ -542,6 +542,117 @@ export abstract class ToggleViewAction extends Action {
|
||||
}
|
||||
|
||||
// --- Move View with Command
|
||||
export class MoveViewAction extends Action {
|
||||
static readonly ID = 'workbench.action.moveView';
|
||||
static readonly LABEL = nls.localize('moveView', "Move View");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IViewDescriptorService private viewDescriptorService: IViewDescriptorService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IQuickInputService private quickInputService: IQuickInputService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IActivityBarService private activityBarService: IActivityBarService,
|
||||
@IPanelService private panelService: IPanelService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
private getViewItems(): Array<IQuickPickItem | IQuickPickSeparator> {
|
||||
const results: Array<IQuickPickItem | IQuickPickSeparator> = [];
|
||||
|
||||
const viewlets = this.activityBarService.getVisibleViewContainerIds();
|
||||
viewlets.forEach(viewletId => {
|
||||
const container = this.viewDescriptorService.getViewContainerById(viewletId)!;
|
||||
const containerModel = this.viewDescriptorService.getViewContainerModel(container);
|
||||
|
||||
let hasAddedView = false;
|
||||
containerModel.visibleViewDescriptors.forEach(viewDescriptor => {
|
||||
if (viewDescriptor.canMoveView) {
|
||||
if (!hasAddedView) {
|
||||
results.push({
|
||||
type: 'separator',
|
||||
label: nls.localize('sidebarContainer', "Side Bar / {0}", containerModel.title)
|
||||
});
|
||||
hasAddedView = true;
|
||||
}
|
||||
|
||||
results.push({
|
||||
id: viewDescriptor.id,
|
||||
label: viewDescriptor.name
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const panels = this.panelService.getPinnedPanels();
|
||||
panels.forEach(panel => {
|
||||
const container = this.viewDescriptorService.getViewContainerById(panel.id)!;
|
||||
const containerModel = this.viewDescriptorService.getViewContainerModel(container);
|
||||
|
||||
let hasAddedView = false;
|
||||
containerModel.visibleViewDescriptors.forEach(viewDescriptor => {
|
||||
if (viewDescriptor.canMoveView) {
|
||||
if (!hasAddedView) {
|
||||
results.push({
|
||||
type: 'separator',
|
||||
label: nls.localize('panelContainer', "Panel / {0}", containerModel.title)
|
||||
});
|
||||
hasAddedView = true;
|
||||
}
|
||||
|
||||
results.push({
|
||||
id: viewDescriptor.id,
|
||||
label: viewDescriptor.name
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private async getView(viewId?: string): Promise<string> {
|
||||
const quickPick = this.quickInputService.createQuickPick();
|
||||
quickPick.placeholder = nls.localize('moveFocusedView.selectView', "Select a View to Move");
|
||||
quickPick.items = this.getViewItems();
|
||||
quickPick.selectedItems = quickPick.items.filter(item => (item as IQuickPickItem).id === viewId) as IQuickPickItem[];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
quickPick.onDidAccept(() => {
|
||||
const viewId = quickPick.selectedItems[0];
|
||||
resolve(viewId.id);
|
||||
quickPick.hide();
|
||||
});
|
||||
|
||||
quickPick.onDidHide(() => reject());
|
||||
|
||||
quickPick.show();
|
||||
});
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
|
||||
let viewId: string;
|
||||
|
||||
if (focusedViewId && this.viewDescriptorService.getViewDescriptorById(focusedViewId)?.canMoveView) {
|
||||
viewId = focusedViewId;
|
||||
}
|
||||
|
||||
viewId = await this.getView(viewId!);
|
||||
|
||||
if (!viewId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.instantiationService.createInstance(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL).run(viewId);
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveViewAction), 'View: Move View', viewCategory);
|
||||
|
||||
// --- Move Focused View with Command
|
||||
export class MoveFocusedViewAction extends Action {
|
||||
static readonly ID = 'workbench.action.moveFocusedView';
|
||||
static readonly LABEL = nls.localize('moveFocusedView', "Move Focused View");
|
||||
@@ -560,8 +671,8 @@ export class MoveFocusedViewAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
|
||||
async run(viewId: string): Promise<void> {
|
||||
const focusedViewId = viewId || FocusedViewContext.getValue(this.contextKeyService);
|
||||
|
||||
if (focusedViewId === undefined || focusedViewId.trim() === '') {
|
||||
this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused."));
|
||||
@@ -576,27 +687,33 @@ export class MoveFocusedViewAction extends Action {
|
||||
|
||||
const quickPick = this.quickInputService.createQuickPick();
|
||||
quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a Destination for the View");
|
||||
quickPick.title = nls.localize('moveFocusedView.title', "View: Move {0}", viewDescriptor.name);
|
||||
quickPick.title = nls.localize({ key: 'moveFocusedView.title', comment: ['{0} indicates the title of the view the user has selected to move.'] }, "View: Move {0}", viewDescriptor.name);
|
||||
|
||||
const items: Array<IQuickPickItem | IQuickPickSeparator> = [];
|
||||
const currentContainer = this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!;
|
||||
const currentLocation = this.viewDescriptorService.getViewLocationById(focusedViewId)!;
|
||||
const isViewSolo = this.viewDescriptorService.getViewContainerModel(currentContainer).allViewDescriptors.length === 1;
|
||||
|
||||
if (!(isViewSolo && currentLocation === ViewContainerLocation.Panel)) {
|
||||
items.push({
|
||||
id: '_.panel.newcontainer',
|
||||
label: nls.localize('moveFocusedView.newContainerInPanel', "New Panel Entry"),
|
||||
});
|
||||
}
|
||||
|
||||
if (!(isViewSolo && currentLocation === ViewContainerLocation.Sidebar)) {
|
||||
items.push({
|
||||
id: '_.sidebar.newcontainer',
|
||||
label: nls.localize('moveFocusedView.newContainerInSidebar', "New Side Bar Entry")
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
type: 'separator',
|
||||
label: nls.localize('sidebar', "Side Bar")
|
||||
});
|
||||
|
||||
const currentContainer = this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!;
|
||||
const currentLocation = this.viewDescriptorService.getViewLocationById(focusedViewId)!;
|
||||
const isViewSolo = this.viewDescriptorService.getViewContainerModel(currentContainer).allViewDescriptors.length === 1;
|
||||
|
||||
if (!(isViewSolo && currentLocation === ViewContainerLocation.Sidebar)) {
|
||||
items.push({
|
||||
id: '_.sidebar.newcontainer',
|
||||
label: nls.localize('moveFocusedView.newContainerInSidebar', "New Container in Side Bar")
|
||||
});
|
||||
}
|
||||
|
||||
const pinnedViewlets = this.activityBarService.getPinnedViewContainerIds();
|
||||
const pinnedViewlets = this.activityBarService.getVisibleViewContainerIds();
|
||||
items.push(...pinnedViewlets
|
||||
.filter(viewletId => {
|
||||
if (viewletId === this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!.id) {
|
||||
@@ -617,13 +734,6 @@ export class MoveFocusedViewAction extends Action {
|
||||
label: nls.localize('panel', "Panel")
|
||||
});
|
||||
|
||||
if (!(isViewSolo && currentLocation === ViewContainerLocation.Panel)) {
|
||||
items.push({
|
||||
id: '_.panel.newcontainer',
|
||||
label: nls.localize('moveFocusedView.newContainerInPanel', "New Container in Panel"),
|
||||
});
|
||||
}
|
||||
|
||||
const pinnedPanels = this.panelService.getPinnedPanels();
|
||||
items.push(...pinnedPanels
|
||||
.filter(panel => {
|
||||
|
||||
@@ -25,15 +25,12 @@
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0 ,0.5);
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
bottom: 20%;
|
||||
left: 0;
|
||||
z-index: 100000;
|
||||
pointer-events: none;
|
||||
color: #eee;
|
||||
line-height: 100px;
|
||||
line-height: 1.75em;
|
||||
text-align: center;
|
||||
font-size: 56px;
|
||||
transition: opacity 0.3s ease-out;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -151,7 +151,7 @@ CommandsRegistry.registerCommand({
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('workbench.action.quickOpenPreviousEditor', async function (accessor: ServicesAccessor, prefix: string | null = null) {
|
||||
CommandsRegistry.registerCommand('workbench.action.quickOpenPreviousEditor', async accessor => {
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
|
||||
quickInputService.quickAccess.show('', { itemActivation: ItemActivation.SECOND });
|
||||
|
||||
@@ -47,11 +47,7 @@ export class DraggedEditorIdentifier {
|
||||
|
||||
export class DraggedEditorGroupIdentifier {
|
||||
|
||||
constructor(private _identifier: GroupIdentifier) { }
|
||||
|
||||
get identifier(): GroupIdentifier {
|
||||
return this._identifier;
|
||||
}
|
||||
constructor(public readonly identifier: GroupIdentifier) { }
|
||||
}
|
||||
|
||||
export interface IDraggedEditor extends IDraggedResource {
|
||||
@@ -675,6 +671,11 @@ export class CompositeDragAndDropObserver extends Disposable {
|
||||
disposableStore.add(addDisposableListener(element, EventType.DRAG_START, e => {
|
||||
const { id, type } = draggedItemProvider();
|
||||
this.writeDragData(id, type);
|
||||
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.setDragImage(element, 0, 0);
|
||||
}
|
||||
|
||||
this._onDragStart.fire({ eventData: e, dragAndDropData: this.readDragData(type)! });
|
||||
}));
|
||||
disposableStore.add(new DragAndDropObserver(element, {
|
||||
|
||||
@@ -505,6 +505,7 @@ class ResourceLabelWidget extends IconLabel {
|
||||
italic: this.options?.italic,
|
||||
strikethrough: this.options?.strikethrough,
|
||||
matches: this.options?.matches,
|
||||
descriptionMatches: this.options?.descriptionMatches,
|
||||
extraClasses: [],
|
||||
separator: this.options?.separator,
|
||||
domId: this.options?.domId
|
||||
|
||||
@@ -44,6 +44,7 @@ import { LineNumbersType } from 'vs/editor/common/config/editorOptions';
|
||||
import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
|
||||
export enum Settings {
|
||||
ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible',
|
||||
@@ -178,6 +179,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
private notificationService!: INotificationService;
|
||||
private themeService!: IThemeService;
|
||||
private activityBarService!: IActivityBarService;
|
||||
private statusBarService!: IStatusbarService;
|
||||
|
||||
protected readonly state = {
|
||||
fullscreen: false,
|
||||
@@ -262,7 +264,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
this.titleService = accessor.get(ITitleService);
|
||||
this.notificationService = accessor.get(INotificationService);
|
||||
this.activityBarService = accessor.get(IActivityBarService);
|
||||
accessor.get(IStatusbarService); // not used, but called to ensure instantiated
|
||||
this.statusBarService = accessor.get(IStatusbarService);
|
||||
|
||||
// Listeners
|
||||
this.registerLayoutListeners();
|
||||
@@ -397,6 +399,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
const newMenubarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService);
|
||||
this.setMenubarVisibility(newMenubarVisibility, !!skipLayout);
|
||||
|
||||
// Centered Layout
|
||||
this.centerEditorLayout(this.state.editor.centered, skipLayout);
|
||||
}
|
||||
|
||||
private setSideBarPosition(position: Position): void {
|
||||
@@ -850,8 +854,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
case Parts.ACTIVITYBAR_PART:
|
||||
this.activityBarService.focusActivityBar();
|
||||
break;
|
||||
case Parts.STATUSBAR_PART:
|
||||
this.statusBarService.focus();
|
||||
default:
|
||||
// Status Bar, Activity Bar and Title Bar simply pass focus to container
|
||||
// Title Bar simply pass focus to container
|
||||
const container = this.getContainer(part);
|
||||
if (container) {
|
||||
container.focus();
|
||||
@@ -1205,9 +1211,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
let smartActive = active;
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
if (this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize')
|
||||
&& (this.editorGroupService.groups.length > 1 || (activeEditor && activeEditor instanceof SideBySideEditorInput))) {
|
||||
smartActive = false; // Respect the auto resize setting - do not go into centered layout if there is more than 1 group.
|
||||
|
||||
const isSideBySideLayout = activeEditor
|
||||
&& activeEditor instanceof SideBySideEditorInput
|
||||
// DiffEditorInput inherits from SideBySideEditorInput but can still be functionally an inline editor.
|
||||
&& (!(activeEditor instanceof DiffEditorInput) || this.configurationService.getValue('diffEditor.renderSideBySide'));
|
||||
|
||||
const isCenteredLayoutAutoResizing = this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize');
|
||||
if (
|
||||
isCenteredLayoutAutoResizing
|
||||
&& (this.editorGroupService.groups.length > 1 || isSideBySideLayout)
|
||||
) {
|
||||
smartActive = false;
|
||||
}
|
||||
|
||||
// Enter Centered Editor Layout
|
||||
|
||||
@@ -5,23 +5,23 @@
|
||||
|
||||
/* Font Families (with CJK support) */
|
||||
|
||||
.mac { font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
|
||||
.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; }
|
||||
.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; }
|
||||
.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; }
|
||||
.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; }
|
||||
.mac { font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif; }
|
||||
.mac:lang(zh-Hans) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; }
|
||||
.mac:lang(zh-Hant) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; }
|
||||
.mac:lang(ja) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; }
|
||||
.mac:lang(ko) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; }
|
||||
|
||||
.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; }
|
||||
.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; }
|
||||
.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; }
|
||||
.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
|
||||
.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
|
||||
.windows { font-family: system-ui, "Segoe WPC", "Segoe UI", sans-serif; }
|
||||
.windows:lang(zh-Hans) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; }
|
||||
.windows:lang(zh-Hant) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; }
|
||||
.windows:lang(ja) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
|
||||
.windows:lang(ko) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
|
||||
|
||||
.linux { font-family: "Ubuntu", "Droid Sans", sans-serif; }
|
||||
.linux:lang(zh-Hans) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(zh-Hant) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(ko) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
|
||||
.linux { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; }
|
||||
.linux:lang(zh-Hans) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(zh-Hant) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(ja) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(ko) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
|
||||
|
||||
.mac { --monaco-monospace-font: "SF Mono", Monaco, Menlo, Courier, monospace; }
|
||||
.windows { --monaco-monospace-font: Consolas, "Courier New", monospace; }
|
||||
|
||||
@@ -16,29 +16,28 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
|
||||
import { ViewPaneContainer } from './parts/views/viewPaneContainer';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { IAction, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
|
||||
export class PaneComposite extends Composite implements IPaneComposite {
|
||||
|
||||
private menuActions: ViewContainerMenuActions;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
protected readonly viewPaneContainer: ViewPaneContainer,
|
||||
@ITelemetryService
|
||||
telemetryService: ITelemetryService,
|
||||
@IStorageService
|
||||
protected storageService: IStorageService,
|
||||
@IInstantiationService
|
||||
protected instantiationService: IInstantiationService,
|
||||
@IThemeService
|
||||
themeService: IThemeService,
|
||||
@IContextMenuService
|
||||
protected contextMenuService: IContextMenuService,
|
||||
@IExtensionService
|
||||
protected extensionService: IExtensionService,
|
||||
@IWorkspaceContextService
|
||||
protected contextService: IWorkspaceContextService
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService protected storageService: IStorageService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IExtensionService protected extensionService: IExtensionService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService
|
||||
) {
|
||||
super(id, telemetryService, themeService, storageService);
|
||||
|
||||
this.menuActions = this._register(this.instantiationService.createInstance(ViewContainerMenuActions, this.getId(), MenuId.ViewContainerTitleContext));
|
||||
this._register(this.viewPaneContainer.onTitleAreaUpdate(() => this.updateTitleArea()));
|
||||
}
|
||||
|
||||
@@ -68,7 +67,15 @@ export class PaneComposite extends Composite implements IPaneComposite {
|
||||
}
|
||||
|
||||
getContextMenuActions(): ReadonlyArray<IAction> {
|
||||
return this.viewPaneContainer.getContextMenuActions();
|
||||
const result = [];
|
||||
result.push(...this.menuActions.getContextMenuActions());
|
||||
|
||||
if (result.length) {
|
||||
result.push(new Separator());
|
||||
}
|
||||
|
||||
result.push(...this.viewPaneContainer.getContextMenuActions());
|
||||
return result;
|
||||
}
|
||||
|
||||
getActions(): ReadonlyArray<IAction> {
|
||||
|
||||
@@ -5,21 +5,19 @@
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IPanel } from 'vs/workbench/common/panel';
|
||||
import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { PaneComposite } from 'vs/workbench/browser/panecomposite';
|
||||
|
||||
export abstract class Panel extends Composite implements IPanel { }
|
||||
|
||||
export abstract class PaneCompositePanel extends PaneComposite implements IPanel { }
|
||||
export abstract class Panel extends PaneComposite implements IPanel { }
|
||||
|
||||
/**
|
||||
* A panel descriptor is a leightweight descriptor of a panel in the workbench.
|
||||
*/
|
||||
export class PanelDescriptor extends CompositeDescriptor<Panel> {
|
||||
|
||||
public static create<Services extends BrandedService[]>(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, _commandId?: string): PanelDescriptor {
|
||||
static create<Services extends BrandedService[]>(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, _commandId?: string): PanelDescriptor {
|
||||
return new PanelDescriptor(ctor as IConstructorSignature0<Panel>, id, name, cssClass, order, _commandId);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
export class ViewContainerActivityAction extends ActivityAction {
|
||||
|
||||
@@ -287,6 +289,42 @@ export class NextSideBarViewAction extends SwitchSideBarViewAction {
|
||||
|
||||
export class HomeAction extends Action {
|
||||
|
||||
constructor(
|
||||
private readonly href: string,
|
||||
name: string,
|
||||
icon: Codicon
|
||||
) {
|
||||
super('workbench.action.home', name, icon.classNames);
|
||||
}
|
||||
|
||||
async run(event: MouseEvent): Promise<void> {
|
||||
let openInNewWindow = false;
|
||||
if (isMacintosh) {
|
||||
openInNewWindow = event.metaKey;
|
||||
} else {
|
||||
openInNewWindow = event.ctrlKey;
|
||||
}
|
||||
|
||||
if (openInNewWindow) {
|
||||
DOM.windowOpenNoOpener(this.href);
|
||||
} else {
|
||||
window.location.href = this.href;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeActionViewItem extends ActionViewItem {
|
||||
|
||||
constructor(action: IAction) {
|
||||
super(undefined, action, { icon: true, label: false, useEventAsContext: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated TODO@ben remove me eventually
|
||||
*/
|
||||
export class DeprecatedHomeAction extends Action {
|
||||
|
||||
constructor(
|
||||
private readonly command: string,
|
||||
name: string,
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
import 'vs/css!./media/activitybarpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { GLOBAL_ACTIVITY_ID, IActivity } from 'vs/workbench/common/activity';
|
||||
import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIIVTY_ID } from 'vs/workbench/common/activity';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActionViewItem, HomeAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActionViewItem, HomeAction, HomeActionViewItem, DeprecatedHomeAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction } from 'vs/workbench/browser/actions/layoutActions';
|
||||
import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar';
|
||||
import { Dimension, addClass, removeNode, createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
|
||||
@@ -74,6 +74,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
private static readonly ACTION_HEIGHT = 48;
|
||||
static readonly PINNED_VIEW_CONTAINERS = 'workbench.activity.pinnedViewlets2';
|
||||
private static readonly PLACEHOLDER_VIEW_CONTAINERS = 'workbench.activity.placeholderViewlets';
|
||||
private static readonly HOME_BAR_VISIBILITY_PREFERENCE = 'workbench.activity.showHomeIndicator';
|
||||
|
||||
//#region IView
|
||||
|
||||
@@ -98,7 +99,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
private globalActivityActionBar: ActionBar | undefined;
|
||||
private readonly globalActivity: ICompositeActivity[] = [];
|
||||
|
||||
private readonly cachedViewContainers: ICachedViewContainer[] = [];
|
||||
private accountsActivityAction: ActivityAction | undefined;
|
||||
|
||||
private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction }>();
|
||||
private readonly viewContainerDisposables = new Map<string, IDisposable>();
|
||||
|
||||
@@ -121,9 +123,9 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
|
||||
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.PINNED_VIEW_CONTAINERS, version: 1 });
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, version: 1 });
|
||||
this.migrateFromOldCachedViewContainersValue();
|
||||
|
||||
this.cachedViewContainers = this.getCachedViewContainers();
|
||||
for (const cachedViewContainer of this.cachedViewContainers) {
|
||||
if (environmentService.configuration.remoteAuthority // In remote window, hide activity bar entries until registered.
|
||||
|| this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer)
|
||||
@@ -144,6 +146,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
getContextMenuActions: () => {
|
||||
const menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService);
|
||||
const actions = [];
|
||||
if (this.homeBarContainer) {
|
||||
actions.push(new Action('toggleHomeBarAction',
|
||||
this.homeBarVisibilityPreference ? nls.localize('hideHomeBar', "Hide Home Button") : nls.localize('showHomeBar', "Show Home Button"),
|
||||
undefined,
|
||||
true,
|
||||
async () => { this.homeBarVisibilityPreference = !this.homeBarVisibilityPreference; }));
|
||||
}
|
||||
|
||||
if (menuBarVisibility === 'compact' || (menuBarVisibility === 'hidden' && isWeb)) {
|
||||
actions.push(this.instantiationService.createInstance(ToggleMenuBarAction, ToggleMenuBarAction.ID, menuBarVisibility === 'compact' ? nls.localize('hideMenu', "Hide Menu") : nls.localize('showMenu', "Show Menu")));
|
||||
@@ -157,7 +166,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
hidePart: () => this.layoutService.setSideBarHidden(true),
|
||||
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Sidebar,
|
||||
(id: string, focus?: boolean) => this.viewsService.openViewContainer(id, focus),
|
||||
(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.verticallyBefore)
|
||||
(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.verticallyBefore),
|
||||
() => this.compositeBar.getCompositeBarItems(),
|
||||
),
|
||||
compositeSize: 52,
|
||||
colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme),
|
||||
@@ -226,6 +236,12 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeHomeBarVisibility(): void {
|
||||
if (this.homeBarContainer) {
|
||||
this.homeBarContainer.style.display = this.homeBarVisibilityPreference ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
private onDidRegisterExtensions(): void {
|
||||
this.removeNotExistingComposites();
|
||||
this.saveCachedViewContainers();
|
||||
@@ -256,6 +272,14 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
return this.showGlobalActivity(badge, clazz, priority);
|
||||
}
|
||||
|
||||
if (viewContainerOrActionId === ACCOUNTS_ACTIIVTY_ID) {
|
||||
if (this.accountsActivityAction) {
|
||||
this.accountsActivityAction.setBadge(badge, clazz);
|
||||
|
||||
return toDisposable(() => this.accountsActivityAction?.setBadge(undefined));
|
||||
}
|
||||
}
|
||||
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
@@ -354,7 +378,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
console.warn(`Unknown home indicator icon ${homeIndicator.icon}`);
|
||||
codicon = Codicon.code;
|
||||
}
|
||||
this.createHomeBar(homeIndicator.command, homeIndicator.title, codicon);
|
||||
this.createHomeBar(homeIndicator.href, homeIndicator.command, homeIndicator.title, codicon);
|
||||
this.onDidChangeHomeBarVisibility();
|
||||
}
|
||||
|
||||
// Install menubar if compact
|
||||
@@ -375,7 +400,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
private createHomeBar(command: string, title: string, icon: Codicon): void {
|
||||
private createHomeBar(href: string, command: string | undefined, title: string, icon: Codicon): void {
|
||||
this.homeBarContainer = document.createElement('div');
|
||||
this.homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home"));
|
||||
this.homeBarContainer.setAttribute('role', 'toolbar');
|
||||
@@ -383,14 +408,21 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
this.homeBar = this._register(new ActionBar(this.homeBarContainer, {
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
animated: false
|
||||
animated: false,
|
||||
ariaLabel: nls.localize('home', "Home"),
|
||||
actionViewItemProvider: command ? undefined : action => new HomeActionViewItem(action),
|
||||
allowContextMenu: true
|
||||
}));
|
||||
|
||||
const homeBarIconBadge = document.createElement('div');
|
||||
addClass(homeBarIconBadge, 'home-bar-icon-badge');
|
||||
this.homeBarContainer.appendChild(homeBarIconBadge);
|
||||
|
||||
this.homeBar.push(this._register(this.instantiationService.createInstance(HomeAction, command, title, icon)), { icon: true, label: false });
|
||||
if (command) {
|
||||
this.homeBar.push(this._register(this.instantiationService.createInstance(DeprecatedHomeAction, command, title, icon)), { icon: true, label: false });
|
||||
} else {
|
||||
this.homeBar.push(this._register(this.instantiationService.createInstance(HomeAction, href, title, icon)));
|
||||
}
|
||||
|
||||
const content = assertIsDefined(this.content);
|
||||
content.prepend(this.homeBarContainer);
|
||||
@@ -422,7 +454,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
activeBackground: theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND),
|
||||
badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),
|
||||
badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),
|
||||
dragAndDropBackground: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND),
|
||||
dragAndDropBorder: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BORDER),
|
||||
activeBackgroundColor: undefined, inactiveBackgroundColor: undefined, activeBorderBottomColor: undefined,
|
||||
};
|
||||
}
|
||||
@@ -452,13 +484,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
});
|
||||
|
||||
if (getUserDataSyncStore(this.productService, this.configurationService)) {
|
||||
const profileAction = new ActivityAction({
|
||||
this.accountsActivityAction = new ActivityAction({
|
||||
id: 'workbench.actions.accounts',
|
||||
name: nls.localize('accounts', "Accounts"),
|
||||
cssClass: Codicon.account.classNames
|
||||
});
|
||||
|
||||
this.globalActivityActionBar.push(profileAction);
|
||||
this.globalActivityActionBar.push(this.accountsActivityAction);
|
||||
}
|
||||
|
||||
this.globalActivityActionBar.push(this.globalActivityAction);
|
||||
@@ -528,7 +560,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}
|
||||
|
||||
this.viewContainerDisposables.delete(viewContainer.id);
|
||||
this.hideComposite(viewContainer.id);
|
||||
this.removeComposite(viewContainer.id);
|
||||
}
|
||||
|
||||
private updateActivity(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
|
||||
@@ -605,6 +637,17 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}
|
||||
}
|
||||
|
||||
private removeComposite(compositeId: string): void {
|
||||
this.compositeBar.removeComposite(compositeId);
|
||||
|
||||
const compositeActions = this.compositeActions.get(compositeId);
|
||||
if (compositeActions) {
|
||||
compositeActions.activityAction.dispose();
|
||||
compositeActions.pinnedAction.dispose();
|
||||
this.compositeActions.delete(compositeId);
|
||||
}
|
||||
}
|
||||
|
||||
getPinnedViewContainerIds(): string[] {
|
||||
const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(v => v.id);
|
||||
return this.getViewContainers()
|
||||
@@ -654,22 +697,19 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
if (e.key === ActivitybarPart.PINNED_VIEW_CONTAINERS && e.scope === StorageScope.GLOBAL
|
||||
&& this.pinnedViewContainersValue !== this.getStoredPinnedViewContainersValue() /* This checks if current window changed the value or not */) {
|
||||
this._pinnedViewContainersValue = undefined;
|
||||
this._cachedViewContainers = undefined;
|
||||
|
||||
const newCompositeItems: ICompositeBarItem[] = [];
|
||||
const compositeItems = this.compositeBar.getCompositeBarItems();
|
||||
const cachedViewContainers = this.getCachedViewContainers();
|
||||
|
||||
for (const cachedViewContainer of cachedViewContainers) {
|
||||
// Add and update existing items
|
||||
const existingItem = compositeItems.filter(({ id }) => id === cachedViewContainer.id)[0];
|
||||
if (existingItem) {
|
||||
newCompositeItems.push({
|
||||
id: existingItem.id,
|
||||
name: existingItem.name,
|
||||
order: existingItem.order,
|
||||
pinned: cachedViewContainer.pinned,
|
||||
visible: existingItem.visible
|
||||
});
|
||||
}
|
||||
for (const cachedViewContainer of this.cachedViewContainers) {
|
||||
newCompositeItems.push({
|
||||
id: cachedViewContainer.id,
|
||||
name: cachedViewContainer.name,
|
||||
order: cachedViewContainer.order,
|
||||
pinned: cachedViewContainer.pinned,
|
||||
visible: !!compositeItems.find(({ id }) => id === cachedViewContainer.id)
|
||||
});
|
||||
}
|
||||
|
||||
for (let index = 0; index < compositeItems.length; index++) {
|
||||
@@ -681,6 +721,10 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
this.compositeBar.setCompositeBarItems(newCompositeItems);
|
||||
}
|
||||
|
||||
if (e.key === ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE && e.scope === StorageScope.GLOBAL) {
|
||||
this.onDidChangeHomeBarVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private saveCachedViewContainers(): void {
|
||||
@@ -713,19 +757,21 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
this.storeCachedViewContainersState(state);
|
||||
}
|
||||
|
||||
private getCachedViewContainers(): ICachedViewContainer[] {
|
||||
const cachedViewContainers: ICachedViewContainer[] = this.getPinnedViewContainers();
|
||||
for (const placeholderViewContainer of this.getPlaceholderViewContainers()) {
|
||||
const cachedViewContainer = cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0];
|
||||
if (cachedViewContainer) {
|
||||
cachedViewContainer.name = placeholderViewContainer.name;
|
||||
cachedViewContainer.icon = placeholderViewContainer.iconCSS ? placeholderViewContainer.iconCSS :
|
||||
placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;
|
||||
cachedViewContainer.views = placeholderViewContainer.views;
|
||||
private _cachedViewContainers: ICachedViewContainer[] | undefined = undefined;
|
||||
private get cachedViewContainers(): ICachedViewContainer[] {
|
||||
if (this._cachedViewContainers === undefined) {
|
||||
this._cachedViewContainers = this.getPinnedViewContainers();
|
||||
for (const placeholderViewContainer of this.getPlaceholderViewContainers()) {
|
||||
const cachedViewContainer = this._cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0];
|
||||
if (cachedViewContainer) {
|
||||
cachedViewContainer.name = placeholderViewContainer.name;
|
||||
cachedViewContainer.icon = placeholderViewContainer.iconCSS ? placeholderViewContainer.iconCSS :
|
||||
placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;
|
||||
cachedViewContainer.views = placeholderViewContainer.views;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cachedViewContainers;
|
||||
return this._cachedViewContainers;
|
||||
}
|
||||
|
||||
private storeCachedViewContainersState(cachedViewContainers: ICachedViewContainer[]): void {
|
||||
@@ -808,6 +854,14 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEW_CONTAINERS, value, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private get homeBarVisibilityPreference(): boolean {
|
||||
return this.storageService.getBoolean(ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, StorageScope.GLOBAL, true);
|
||||
}
|
||||
|
||||
private set homeBarVisibilityPreference(value: boolean) {
|
||||
this.storageService.store(ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, value, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private migrateFromOldCachedViewContainersValue(): void {
|
||||
const value = this.storageService.get('workbench.activity.pinnedViewlets', StorageScope.GLOBAL);
|
||||
if (value !== undefined) {
|
||||
|
||||
@@ -20,9 +20,8 @@
|
||||
width: 48px;
|
||||
height: 2px;
|
||||
display: block;
|
||||
background-color: var(--insert-border-color);
|
||||
opacity: 0;
|
||||
transition-property: opacity;
|
||||
background-color: transparent;
|
||||
transition-property: background-color;
|
||||
transition-duration: 0ms;
|
||||
transition-delay: 100ms;
|
||||
}
|
||||
@@ -53,7 +52,7 @@
|
||||
.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item.top::before,
|
||||
.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item.bottom::after,
|
||||
.monaco-workbench .activitybar > .content.dragged-over > .composite-bar > .monaco-action-bar .action-item:last-of-type::after {
|
||||
opacity: 1;
|
||||
background-color: var(--insert-border-color);
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label {
|
||||
@@ -101,7 +100,7 @@
|
||||
}
|
||||
|
||||
/* Hides active elements in high contrast mode */
|
||||
.hc-black .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator {
|
||||
.monaco-workbench.hc-black .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -120,8 +119,8 @@
|
||||
}
|
||||
|
||||
/* Hides outline on HC as focus is handled by border */
|
||||
.hc-black .monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before,
|
||||
.hc-black .monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
|
||||
.monaco-workbench.hc-black .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before,
|
||||
.monaco-workbench.hc-black .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
private targetContainerLocation: ViewContainerLocation,
|
||||
private openComposite: (id: string, focus?: boolean) => Promise<IPaneComposite | null>,
|
||||
private moveComposite: (from: string, to: string, before?: Before2D) => void,
|
||||
private getItems: () => ICompositeBarItem[],
|
||||
) { }
|
||||
|
||||
drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent, before?: Before2D): void {
|
||||
@@ -61,11 +62,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
return;
|
||||
}
|
||||
|
||||
this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation);
|
||||
|
||||
if (targetCompositeId) {
|
||||
this.moveComposite(currentContainer.id, targetCompositeId, before);
|
||||
}
|
||||
this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation, this.getTargetIndex(targetCompositeId, before));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +95,16 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
return this.canDrop(data, targetCompositeId);
|
||||
}
|
||||
|
||||
private getTargetIndex(targetId: string | undefined, before2d: Before2D | undefined): number | undefined {
|
||||
if (!targetId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const items = this.getItems();
|
||||
const before = this.targetContainerLocation === ViewContainerLocation.Panel ? before2d?.horizontallyBefore : before2d?.verticallyBefore;
|
||||
return items.findIndex(o => o.id === targetId) + (before ? 0 : 1);
|
||||
}
|
||||
|
||||
private canDrop(data: CompositeDragAndDropData, targetCompositeId: string | undefined): boolean {
|
||||
const dragData = data.getData();
|
||||
|
||||
@@ -226,8 +233,8 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
// Register a drop target on the whole bar to prevent forbidden feedback
|
||||
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, {
|
||||
onDragOver: (e: IDraggedCompositeData) => {
|
||||
// don't add feedback if this is over the composite bar actions
|
||||
if (e.eventData.target && isAncestor(e.eventData.target as HTMLElement, actionBarDiv)) {
|
||||
// don't add feedback if this is over the composite bar actions or there are no actions
|
||||
if (!(this.compositeSwitcherBar?.length()) || (e.eventData.target && isAncestor(e.eventData.target as HTMLElement, actionBarDiv))) {
|
||||
toggleClass(parent, 'dragged-over', false);
|
||||
return;
|
||||
}
|
||||
@@ -245,7 +252,9 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
},
|
||||
onDrop: (e: IDraggedCompositeData) => {
|
||||
const pinnedItems = this.getPinnedComposites();
|
||||
this.options.dndHandler.drop(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData, { horizontallyBefore: false, verticallyBefore: false });
|
||||
if (pinnedItems.length) {
|
||||
this.options.dndHandler.drop(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData, { horizontallyBefore: false, verticallyBefore: false });
|
||||
}
|
||||
toggleClass(parent, 'dragged-over', false);
|
||||
}
|
||||
}));
|
||||
@@ -664,9 +673,18 @@ class CompositeBarModel {
|
||||
}
|
||||
this._items = result;
|
||||
}
|
||||
|
||||
this.updateItemsOrder();
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
|
||||
private updateItemsOrder(): void {
|
||||
if (this._items) {
|
||||
this.items.forEach((item, index) => { if (item.order !== undefined) { item.order = index; } });
|
||||
}
|
||||
}
|
||||
|
||||
get visibleItems(): ICompositeBarModelItem[] {
|
||||
return this.items.filter(item => item.visible);
|
||||
}
|
||||
@@ -702,6 +720,8 @@ class CompositeBarModel {
|
||||
item.visible = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
this.updateItemsOrder();
|
||||
return changed;
|
||||
} else {
|
||||
const item = this.createCompositeBarItem(id, name, order, true, true);
|
||||
@@ -714,6 +734,8 @@ class CompositeBarModel {
|
||||
}
|
||||
this.items.splice(index, 0, item);
|
||||
}
|
||||
|
||||
this.updateItemsOrder();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -722,6 +744,7 @@ class CompositeBarModel {
|
||||
for (let index = 0; index < this.items.length; index++) {
|
||||
if (this.items[index].id === id) {
|
||||
this.items.splice(index, 1);
|
||||
this.updateItemsOrder();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -757,6 +780,8 @@ class CompositeBarModel {
|
||||
// Make sure a moved composite gets pinned
|
||||
sourceItem.pinned = true;
|
||||
|
||||
this.updateItemsOrder();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ export interface ICompositeBarColors {
|
||||
inactiveForegroundColor?: Color;
|
||||
badgeBackground?: Color;
|
||||
badgeForeground?: Color;
|
||||
dragAndDropBackground?: Color;
|
||||
dragAndDropBorder?: Color;
|
||||
}
|
||||
|
||||
export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptions {
|
||||
@@ -169,16 +169,14 @@ export class ActivityActionViewItem extends BaseActionViewItem {
|
||||
this.label.style.color = foreground ? foreground.toString() : '';
|
||||
this.label.style.backgroundColor = '';
|
||||
}
|
||||
|
||||
const dragColor = colors.activeBackgroundColor || colors.activeForegroundColor;
|
||||
this.container.style.setProperty('--insert-border-color', dragColor ? dragColor.toString() : '');
|
||||
} else {
|
||||
const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor;
|
||||
const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null;
|
||||
this.label.style.color = foreground ? foreground.toString() : '';
|
||||
this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : '';
|
||||
this.container.style.setProperty('--insert-border-color', colors.activeForegroundColor ? colors.activeForegroundColor.toString() : '');
|
||||
}
|
||||
|
||||
this.container.style.setProperty('--insert-border-color', colors.dragAndDropBorder ? colors.dragAndDropBorder.toString() : '');
|
||||
}
|
||||
|
||||
// Badge
|
||||
@@ -203,7 +201,7 @@ export class ActivityActionViewItem extends BaseActionViewItem {
|
||||
|
||||
// Make the container tab-able for keyboard navigation
|
||||
this.container.tabIndex = 0;
|
||||
this.container.setAttribute('role', this.options.icon ? 'button' : 'tab');
|
||||
this.container.setAttribute('role', 'tab');
|
||||
|
||||
// Try hard to prevent keyboard only focus feedback when using mouse
|
||||
this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_DOWN, () => {
|
||||
@@ -649,9 +647,11 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
if (this.getAction().checked) {
|
||||
dom.addClass(this.container, 'checked');
|
||||
this.container.setAttribute('aria-label', nls.localize('compositeActive', "{0} active", this.container.title));
|
||||
this.container.setAttribute('aria-expanded', 'true');
|
||||
} else {
|
||||
dom.removeClass(this.container, 'checked');
|
||||
this.container.setAttribute('aria-label', this.container.title);
|
||||
this.container.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
@@ -316,11 +316,11 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
toolBar.setAriaLabel(nls.localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle));
|
||||
}
|
||||
|
||||
private collectCompositeActions(composite: Composite): () => void {
|
||||
private collectCompositeActions(composite?: Composite): () => void {
|
||||
|
||||
// From Composite
|
||||
const primaryActions: IAction[] = composite.getActions().slice(0);
|
||||
const secondaryActions: IAction[] = composite.getSecondaryActions().slice(0);
|
||||
const primaryActions: IAction[] = composite?.getActions().slice(0) || [];
|
||||
const secondaryActions: IAction[] = composite?.getSecondaryActions().slice(0) || [];
|
||||
|
||||
// From Part
|
||||
primaryActions.push(...this.getActions());
|
||||
@@ -368,7 +368,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
|
||||
// Empty Actions
|
||||
if (this.toolBar) {
|
||||
this.toolBar.setActions([])();
|
||||
this.collectCompositeActions()();
|
||||
}
|
||||
this.onDidCompositeClose.fire(composite);
|
||||
|
||||
@@ -395,6 +395,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment()
|
||||
}));
|
||||
|
||||
this.collectCompositeActions()();
|
||||
|
||||
return titleArea;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { LRUCache, Touch } from 'vs/base/common/map';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { isEmptyObject } from 'vs/base/common/types';
|
||||
@@ -106,10 +106,6 @@ export abstract class BaseEditor extends Composite implements IEditorPane {
|
||||
this.createEditor(parent);
|
||||
}
|
||||
|
||||
onHide() { }
|
||||
|
||||
onWillHide() { }
|
||||
|
||||
/**
|
||||
* Called to create the editor in the parent HTMLElement.
|
||||
*/
|
||||
@@ -133,6 +129,16 @@ export abstract class BaseEditor extends Composite implements IEditorPane {
|
||||
this._group = group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before the editor is being removed from the DOM.
|
||||
*/
|
||||
onWillHide() { }
|
||||
|
||||
/**
|
||||
* Called after the editor has been removed from the DOM.
|
||||
*/
|
||||
onDidHide() { }
|
||||
|
||||
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
|
||||
const mementoKey = `${this.getId()}${key}`;
|
||||
|
||||
@@ -256,7 +262,9 @@ export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
moveEditorState(source: URI, target: URI): void {
|
||||
const cache = this.doLoad();
|
||||
|
||||
const cacheKeys = cache.keys();
|
||||
// We need a copy of the keys to not iterate over
|
||||
// newly inserted elements.
|
||||
const cacheKeys = [...cache.keys()];
|
||||
for (const cacheKey of cacheKeys) {
|
||||
const resource = URI.parse(cacheKey);
|
||||
|
||||
@@ -273,7 +281,8 @@ export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved
|
||||
}
|
||||
|
||||
const value = cache.get(cacheKey);
|
||||
// Don't modify LRU state.
|
||||
const value = cache.get(cacheKey, Touch.None);
|
||||
if (value) {
|
||||
cache.delete(cacheKey);
|
||||
cache.set(targetResource.toString(), value);
|
||||
@@ -318,18 +327,19 @@ export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
private cleanUp(): void {
|
||||
const cache = this.doLoad();
|
||||
|
||||
// Remove groups from states that no longer exist
|
||||
cache.forEach((mapGroupToMemento, resource) => {
|
||||
// Remove groups from states that no longer exist. Since we modify the
|
||||
// cache and its is a LRU cache make a copy to ensure iteration succeeds
|
||||
const entries = [...cache.entries()];
|
||||
for (const [resource, mapGroupToMemento] of entries) {
|
||||
Object.keys(mapGroupToMemento).forEach(group => {
|
||||
const groupId: GroupIdentifier = Number(group);
|
||||
if (!this.editorGroupService.getGroup(groupId)) {
|
||||
delete mapGroupToMemento[groupId];
|
||||
|
||||
if (isEmptyObject(mapGroupToMemento)) {
|
||||
cache.delete(resource);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +160,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IResourceDescriptor {
|
||||
readonly resource: URI;
|
||||
readonly name: string;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { tail } from 'vs/base/common/arrays';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { extUri } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!./media/breadcrumbscontrol';
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -71,7 +71,7 @@ class Item extends BreadcrumbsItem {
|
||||
return false;
|
||||
}
|
||||
if (this.element instanceof FileElement && other.element instanceof FileElement) {
|
||||
return (isEqual(this.element.uri, other.element.uri, false) &&
|
||||
return (extUri.isEqual(this.element.uri, other.element.uri) &&
|
||||
this.options.showFileIcons === other.options.showFileIcons &&
|
||||
this.options.showSymbolIcons === other.options.showSymbolIcons);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditorPane, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorInput } from 'vs/workbench/common/editor';
|
||||
import { EditorGroup } from 'vs/workbench/common/editor/editorGroup';
|
||||
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
@@ -14,6 +14,7 @@ import { ISerializableView } from 'vs/base/browser/ui/grid/grid';
|
||||
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export const EDITOR_TITLE_HEIGHT = 35;
|
||||
|
||||
@@ -41,6 +42,26 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = {
|
||||
splitSizing: 'distribute'
|
||||
};
|
||||
|
||||
export function computeEditorAriaLabel(input: IEditorInput, index: number | undefined, group: IEditorGroup | undefined, groupCount: number): string {
|
||||
let ariaLabel = input.getAriaLabel();
|
||||
if (group && !group.isPinned(input)) {
|
||||
ariaLabel = localize('preview', "{0}, preview", ariaLabel);
|
||||
}
|
||||
|
||||
if (group && group.isSticky(index ?? input)) {
|
||||
ariaLabel = localize('pinned', "{0}, pinned", ariaLabel);
|
||||
}
|
||||
|
||||
// Apply group information to help identify in
|
||||
// which group we are (only if more than one group
|
||||
// is actually opened)
|
||||
if (group && groupCount > 1) {
|
||||
ariaLabel = `${ariaLabel}, ${group.ariaLabel}`;
|
||||
}
|
||||
|
||||
return ariaLabel;
|
||||
}
|
||||
|
||||
export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean {
|
||||
return event.affectsConfiguration('workbench.editor') || event.affectsConfiguration('workbench.iconTheme');
|
||||
}
|
||||
@@ -68,6 +89,11 @@ export interface IEditorOpeningEvent extends IEditorIdentifier {
|
||||
*/
|
||||
options?: IEditorOptions;
|
||||
|
||||
/**
|
||||
* Context indicates how the editor open event is initialized.
|
||||
*/
|
||||
context?: OpenEditorContext;
|
||||
|
||||
/**
|
||||
* Allows to prevent the opening of an editor by providing a callback
|
||||
* that will be executed instead. By returning another editor promise
|
||||
@@ -144,11 +170,6 @@ export function getActiveTextEditorOptions(group: IEditorGroup, expectedActiveEd
|
||||
*/
|
||||
export interface EditorServiceImpl extends IEditorService {
|
||||
|
||||
/**
|
||||
* Emitted when an editor is closed.
|
||||
*/
|
||||
readonly onDidCloseEditor: Event<IEditorCloseEvent>;
|
||||
|
||||
/**
|
||||
* Emitted when an editor failed to open.
|
||||
*/
|
||||
|
||||
@@ -1339,7 +1339,8 @@ export class QuickAccessPreviousEditorFromHistoryAction extends Action {
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
@@ -1347,7 +1348,14 @@ export class QuickAccessPreviousEditorFromHistoryAction extends Action {
|
||||
async run(): Promise<void> {
|
||||
const keybindings = this.keybindingService.lookupKeybindings(this.id);
|
||||
|
||||
this.quickInputService.quickAccess.show('', { quickNavigateConfiguration: { keybindings } });
|
||||
// Enforce to activate the first item in quick access if
|
||||
// the currently active editor group has n editor opened
|
||||
let itemActivation: ItemActivation | undefined = undefined;
|
||||
if (this.editorGroupService.activeGroup.count === 0) {
|
||||
itemActivation = ItemActivation.FIRST;
|
||||
}
|
||||
|
||||
this.quickInputService.quickAccess.show('', { quickNavigateConfiguration: { keybindings }, itemActivation });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ export class EditorControl extends Disposable {
|
||||
readonly onDidSizeConstraintsChange = this._onDidSizeConstraintsChange.event;
|
||||
|
||||
private _activeEditorPane: BaseEditor | null = null;
|
||||
get activeEditorPane(): IVisibleEditorPane | null { return this._activeEditorPane as IVisibleEditorPane | null; }
|
||||
|
||||
private readonly editorPanes: BaseEditor[] = [];
|
||||
|
||||
private readonly activeEditorPaneDisposables = this._register(new DisposableStore());
|
||||
@@ -53,10 +55,6 @@ export class EditorControl extends Disposable {
|
||||
this.editorOperation = this._register(new LongRunningOperation(editorProgressService));
|
||||
}
|
||||
|
||||
get activeEditorPane(): IVisibleEditorPane | null {
|
||||
return this._activeEditorPane as IVisibleEditorPane | null;
|
||||
}
|
||||
|
||||
async openEditor(editor: EditorInput, options?: EditorOptions): Promise<IOpenEditorResult> {
|
||||
|
||||
// Editor pane
|
||||
@@ -208,7 +206,7 @@ export class EditorControl extends Disposable {
|
||||
this._activeEditorPane.onWillHide();
|
||||
this.parent.removeChild(editorPaneContainer);
|
||||
hide(editorPaneContainer);
|
||||
this._activeEditorPane.onHide();
|
||||
this._activeEditorPane.onDidHide();
|
||||
}
|
||||
|
||||
// Indicate to editor pane
|
||||
|
||||
@@ -22,6 +22,9 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
interface IDropOperation {
|
||||
splitDirection?: GroupDirection;
|
||||
@@ -31,8 +34,10 @@ class DropOverlay extends Themable {
|
||||
|
||||
private static readonly OVERLAY_ID = 'monaco-workbench-editor-drop-overlay';
|
||||
|
||||
private container!: HTMLElement;
|
||||
private overlay!: HTMLElement;
|
||||
private static readonly MAX_FILE_UPLOAD_SIZE = 100 * 1024 * 1024; // 100mb
|
||||
|
||||
private container: HTMLElement | undefined;
|
||||
private overlay: HTMLElement | undefined;
|
||||
|
||||
private currentDropOperation: IDropOperation | undefined;
|
||||
private _disposed: boolean | undefined;
|
||||
@@ -48,7 +53,8 @@ class DropOverlay extends Themable {
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@IEditorService private readonly editorService: IEditorService
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@INotificationService private readonly notificationService: INotificationService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
@@ -65,45 +71,46 @@ class DropOverlay extends Themable {
|
||||
const overlayOffsetHeight = this.getOverlayOffsetHeight();
|
||||
|
||||
// Container
|
||||
this.container = document.createElement('div');
|
||||
this.container.id = DropOverlay.OVERLAY_ID;
|
||||
this.container.style.top = `${overlayOffsetHeight}px`;
|
||||
const container = this.container = document.createElement('div');
|
||||
container.id = DropOverlay.OVERLAY_ID;
|
||||
container.style.top = `${overlayOffsetHeight}px`;
|
||||
|
||||
// Parent
|
||||
this.groupView.element.appendChild(this.container);
|
||||
this.groupView.element.appendChild(container);
|
||||
addClass(this.groupView.element, 'dragged-over');
|
||||
this._register(toDisposable(() => {
|
||||
this.groupView.element.removeChild(this.container);
|
||||
this.groupView.element.removeChild(container);
|
||||
removeClass(this.groupView.element, 'dragged-over');
|
||||
}));
|
||||
|
||||
// Overlay
|
||||
this.overlay = document.createElement('div');
|
||||
addClass(this.overlay, 'editor-group-overlay-indicator');
|
||||
this.container.appendChild(this.overlay);
|
||||
container.appendChild(this.overlay);
|
||||
|
||||
// Overlay Event Handling
|
||||
this.registerListeners();
|
||||
this.registerListeners(container);
|
||||
|
||||
// Styles
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
const overlay = assertIsDefined(this.overlay);
|
||||
|
||||
// Overlay drop background
|
||||
this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || '';
|
||||
overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || '';
|
||||
|
||||
// Overlay contrast border (if any)
|
||||
const activeContrastBorderColor = this.getColor(activeContrastBorder);
|
||||
this.overlay.style.outlineColor = activeContrastBorderColor || '';
|
||||
this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : '';
|
||||
this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : '';
|
||||
this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : '';
|
||||
overlay.style.outlineColor = activeContrastBorderColor || '';
|
||||
overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : '';
|
||||
overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : '';
|
||||
overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : '';
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(new DragAndDropObserver(this.container, {
|
||||
private registerListeners(container: HTMLElement): void {
|
||||
this._register(new DragAndDropObserver(container, {
|
||||
onDragEnter: e => undefined,
|
||||
onDragOver: e => {
|
||||
const isDraggingGroup = this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype);
|
||||
@@ -161,7 +168,7 @@ class DropOverlay extends Themable {
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this.container, EventType.MOUSE_OVER, () => {
|
||||
this._register(addDisposableListener(container, EventType.MOUSE_OVER, () => {
|
||||
// Under some circumstances we have seen reports where the drop overlay is not being
|
||||
// cleaned up and as such the editor area remains under the overlay so that you cannot
|
||||
// type into the editor anymore. This seems related to using VMs and DND via host and
|
||||
@@ -295,6 +302,14 @@ class DropOverlay extends Themable {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files.item(i);
|
||||
if (file) {
|
||||
|
||||
// Skip for very large files because this operation is unbuffered
|
||||
if (file.size > DropOverlay.MAX_FILE_UPLOAD_SIZE) {
|
||||
this.notificationService.warn(localize('fileTooLarge', "File is too large to open as untitled editor. Please upload it first into the file explorer and then try again."));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read file fully and open as untitled editor
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onload = async event => {
|
||||
@@ -456,30 +471,32 @@ class DropOverlay extends Themable {
|
||||
}
|
||||
|
||||
// Make sure the overlay is visible now
|
||||
this.overlay.style.opacity = '1';
|
||||
const overlay = assertIsDefined(this.overlay);
|
||||
overlay.style.opacity = '1';
|
||||
|
||||
// Enable transition after a timeout to prevent initial animation
|
||||
setTimeout(() => addClass(this.overlay, 'overlay-move-transition'), 0);
|
||||
setTimeout(() => addClass(overlay, 'overlay-move-transition'), 0);
|
||||
|
||||
// Remember as current split direction
|
||||
this.currentDropOperation = { splitDirection };
|
||||
}
|
||||
|
||||
private doPositionOverlay(options: { top: string, left: string, width: string, height: string }): void {
|
||||
const [container, overlay] = assertAllDefined(this.container, this.overlay);
|
||||
|
||||
// Container
|
||||
const offsetHeight = this.getOverlayOffsetHeight();
|
||||
if (offsetHeight) {
|
||||
this.container.style.height = `calc(100% - ${offsetHeight}px)`;
|
||||
container.style.height = `calc(100% - ${offsetHeight}px)`;
|
||||
} else {
|
||||
this.container.style.height = '100%';
|
||||
container.style.height = '100%';
|
||||
}
|
||||
|
||||
// Overlay
|
||||
this.overlay.style.top = options.top;
|
||||
this.overlay.style.left = options.left;
|
||||
this.overlay.style.width = options.width;
|
||||
this.overlay.style.height = options.height;
|
||||
overlay.style.top = options.top;
|
||||
overlay.style.left = options.left;
|
||||
overlay.style.width = options.width;
|
||||
overlay.style.height = options.height;
|
||||
}
|
||||
|
||||
private getOverlayOffsetHeight(): number {
|
||||
@@ -491,11 +508,12 @@ class DropOverlay extends Themable {
|
||||
}
|
||||
|
||||
private hideOverlay(): void {
|
||||
const overlay = assertIsDefined(this.overlay);
|
||||
|
||||
// Reset overlay
|
||||
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '100%' });
|
||||
this.overlay.style.opacity = '0';
|
||||
removeClass(this.overlay, 'overlay-move-transition');
|
||||
overlay.style.opacity = '0';
|
||||
removeClass(overlay, 'overlay-move-transition');
|
||||
|
||||
// Reset current operation
|
||||
this.currentDropOperation = undefined;
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/editorgroupview';
|
||||
|
||||
import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
|
||||
import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorStickyContext, EditorPinnedContext } from 'vs/workbench/common/editor';
|
||||
import { Event, Emitter, Relay } from 'vs/base/common/event';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom';
|
||||
@@ -17,7 +16,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER, EDITOR_GROUP_HEADER_BORDER } from 'vs/workbench/common/theme';
|
||||
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
|
||||
import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
@@ -220,8 +219,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
private handleGroupContextKeys(contextKeyService: IContextKeyService): void {
|
||||
const groupActiveEditorDirtyContextKey = EditorGroupActiveEditorDirtyContext.bindTo(contextKeyService);
|
||||
const groupEditorsCountContext = EditorGroupEditorsCountContext.bindTo(contextKeyService);
|
||||
const groupActiveEditorPinnedContext = EditorPinnedContext.bindTo(contextKeyService);
|
||||
const groupActiveEditorStickyContext = EditorStickyContext.bindTo(contextKeyService);
|
||||
|
||||
let activeEditorListener = new MutableDisposable();
|
||||
const activeEditorListener = new MutableDisposable();
|
||||
|
||||
const observeActiveEditor = () => {
|
||||
activeEditorListener.clear();
|
||||
@@ -237,11 +238,22 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
// Update group contexts based on group changes
|
||||
this._register(this.onDidGroupChange(e => {
|
||||
|
||||
// Track the active editor and update context key that reflects
|
||||
// the dirty state of this editor
|
||||
if (e.kind === GroupChangeKind.EDITOR_ACTIVE) {
|
||||
observeActiveEditor();
|
||||
switch (e.kind) {
|
||||
case GroupChangeKind.EDITOR_ACTIVE:
|
||||
// Track the active editor and update context key that reflects
|
||||
// the dirty state of this editor
|
||||
observeActiveEditor();
|
||||
break;
|
||||
case GroupChangeKind.EDITOR_PIN:
|
||||
if (e.editor && e.editor === this._group.activeEditor) {
|
||||
groupActiveEditorPinnedContext.set(this._group.isPinned(this._group.activeEditor));
|
||||
}
|
||||
break;
|
||||
case GroupChangeKind.EDITOR_STICKY:
|
||||
if (e.editor && e.editor === this._group.activeEditor) {
|
||||
groupActiveEditorStickyContext.set(this._group.isSticky(this._group.activeEditor));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Group editors count context
|
||||
@@ -464,6 +476,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
// Model Events
|
||||
this._register(this._group.onDidChangeEditorPinned(editor => this.onDidChangeEditorPinned(editor)));
|
||||
this._register(this._group.onDidChangeEditorSticky(editor => this.onDidChangeEditorSticky(editor)));
|
||||
this._register(this._group.onDidOpenEditor(editor => this.onDidOpenEditor(editor)));
|
||||
this._register(this._group.onDidCloseEditor(editor => this.handleOnDidCloseEditor(editor)));
|
||||
this._register(this._group.onDidDisposeEditor(editor => this.onDidDisposeEditor(editor)));
|
||||
@@ -478,11 +491,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
|
||||
private onDidChangeEditorPinned(editor: EditorInput): void {
|
||||
|
||||
// Event
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_PIN, editor });
|
||||
}
|
||||
|
||||
private onDidChangeEditorSticky(editor: EditorInput): void {
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_STICKY, editor });
|
||||
}
|
||||
|
||||
private onDidOpenEditor(editor: EditorInput): void {
|
||||
|
||||
/* __GDPR__
|
||||
@@ -596,11 +611,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
// Title control Switch between showing tabs <=> not showing tabs
|
||||
if (event.oldPartOptions.showTabs !== event.newPartOptions.showTabs) {
|
||||
|
||||
// Recreate and layout control
|
||||
// Recreate title control
|
||||
this.createTitleAreaControl();
|
||||
if (this.dimension) {
|
||||
this.layoutTitleAreaControl(this.dimension.width);
|
||||
}
|
||||
|
||||
// Re-layout
|
||||
this.relayout();
|
||||
|
||||
// Ensure to show active editor if any
|
||||
if (this._group.activeEditor) {
|
||||
@@ -848,7 +863,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
//#region openEditor()
|
||||
|
||||
async openEditor(editor: EditorInput, options?: EditorOptions): Promise<IEditorPane | null> {
|
||||
async openEditor(editor: EditorInput, options?: EditorOptions, context?: OpenEditorContext): Promise<IEditorPane | null> {
|
||||
|
||||
// Guard against invalid inputs
|
||||
if (!editor) {
|
||||
@@ -856,7 +871,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
|
||||
// Editor opening event allows for prevention
|
||||
const event = new EditorOpeningEvent(this._group.id, editor, options);
|
||||
const event = new EditorOpeningEvent(this._group.id, editor, options, context);
|
||||
this._onWillOpenEditor.fire(event);
|
||||
const prevented = event.isPrevented();
|
||||
if (prevented) {
|
||||
@@ -1110,7 +1125,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
// Move across groups
|
||||
else {
|
||||
this.doMoveOrCopyEditorAcrossGroups(editor, target, options);
|
||||
this.doMoveOrCopyEditorAcrossGroups(editor, target, options, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1156,7 +1171,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}));
|
||||
|
||||
// A move to another group is an open first...
|
||||
target.openEditor(editor, options);
|
||||
target.openEditor(editor, options, keepCopy ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR);
|
||||
|
||||
// ...and a close afterwards (unless we copy)
|
||||
if (!keepCopy) {
|
||||
@@ -1702,7 +1717,8 @@ class EditorOpeningEvent implements IEditorOpeningEvent {
|
||||
constructor(
|
||||
private _group: GroupIdentifier,
|
||||
private _editor: EditorInput,
|
||||
private _options: EditorOptions | undefined
|
||||
private _options: EditorOptions | undefined,
|
||||
private _context: OpenEditorContext | undefined
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -1718,6 +1734,10 @@ class EditorOpeningEvent implements IEditorOpeningEvent {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
get context(): OpenEditorContext | undefined {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
prevent(callback: () => Promise<IEditorPane | undefined>): void {
|
||||
this.override = callback;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import { EditorDropTarget, EditorDropTargetDelegate } from 'vs/workbench/browser
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { Parts, IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { MementoObject } from 'vs/workbench/common/memento';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
@@ -839,12 +839,62 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
||||
}
|
||||
}));
|
||||
|
||||
let panelOpenerTimeout: any;
|
||||
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(overlay, {
|
||||
onDragOver: e => {
|
||||
EventHelper.stop(e.eventData, true);
|
||||
if (e.eventData.dataTransfer) {
|
||||
e.eventData.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
|
||||
if (!this.layoutService.isVisible(Parts.PANEL_PART)) {
|
||||
const boundingRect = overlay.getBoundingClientRect();
|
||||
|
||||
let openPanel = false;
|
||||
const proximity = 100;
|
||||
switch (this.layoutService.getPanelPosition()) {
|
||||
case Position.BOTTOM:
|
||||
if (e.eventData.clientY > boundingRect.bottom - proximity) {
|
||||
openPanel = true;
|
||||
}
|
||||
break;
|
||||
case Position.LEFT:
|
||||
if (e.eventData.clientX < boundingRect.left + proximity) {
|
||||
openPanel = true;
|
||||
}
|
||||
break;
|
||||
case Position.RIGHT:
|
||||
if (e.eventData.clientX > boundingRect.right - proximity) {
|
||||
openPanel = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!panelOpenerTimeout && openPanel) {
|
||||
panelOpenerTimeout = setTimeout(() => this.layoutService.setPanelHidden(false), 200);
|
||||
} else if (panelOpenerTimeout && !openPanel) {
|
||||
clearTimeout(panelOpenerTimeout);
|
||||
panelOpenerTimeout = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
onDragLeave: () => {
|
||||
if (panelOpenerTimeout) {
|
||||
clearTimeout(panelOpenerTimeout);
|
||||
panelOpenerTimeout = undefined;
|
||||
}
|
||||
},
|
||||
onDragEnd: () => {
|
||||
if (panelOpenerTimeout) {
|
||||
clearTimeout(panelOpenerTimeout);
|
||||
panelOpenerTimeout = undefined;
|
||||
}
|
||||
},
|
||||
onDrop: () => {
|
||||
if (panelOpenerTimeout) {
|
||||
clearTimeout(panelOpenerTimeout);
|
||||
panelOpenerTimeout = undefined;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export class EditorsObserver extends Disposable {
|
||||
}
|
||||
|
||||
get editors(): IEditorIdentifier[] {
|
||||
return this.mostRecentEditorsMap.values();
|
||||
return [...this.mostRecentEditorsMap.values()];
|
||||
}
|
||||
|
||||
hasEditor(resource: URI): boolean {
|
||||
@@ -283,7 +283,7 @@ export class EditorsObserver extends Disposable {
|
||||
|
||||
// Across all editor groups
|
||||
else {
|
||||
await this.doEnsureOpenedEditorsLimit(limit, this.mostRecentEditorsMap.values(), exclude);
|
||||
await this.doEnsureOpenedEditorsLimit(limit, [...this.mostRecentEditorsMap.values()], exclude);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ export class EditorsObserver extends Disposable {
|
||||
private serialize(): ISerializedEditorsList {
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
|
||||
|
||||
const entries = this.mostRecentEditorsMap.values();
|
||||
const entries = [...this.mostRecentEditorsMap.values()];
|
||||
const mapGroupToSerializableEditorsOfGroup = new Map<IEditorGroup, IEditorInput[]>();
|
||||
|
||||
return {
|
||||
|
||||
@@ -211,7 +211,7 @@
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-container {
|
||||
.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-container {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@@ -290,13 +290,12 @@
|
||||
padding-right: 5px; /* we need less room when sizing is shrink (unless tab is sticky) */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close {
|
||||
display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without close button */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top) {
|
||||
padding-right: 0; /* remove extra padding when we are running without close button */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top):not(.sticky) {
|
||||
padding-right: 0; /* remove extra padding when we are running without close button (unless tab is sticky) */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
/* Title Actions */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(span) {
|
||||
display: flex;
|
||||
height: 35px;
|
||||
min-width: 28px;
|
||||
@@ -40,8 +40,8 @@
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label,
|
||||
.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(.codicon) {
|
||||
.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .title-actions .action-label,
|
||||
.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(.codicon) {
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor
|
||||
import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
|
||||
import { addDisposableListener, EventType, addClass, EventHelper, removeClass, toggleClass } from 'vs/base/browser/dom';
|
||||
import { addDisposableListener, EventType, addClass, EventHelper, removeClass, toggleClass, Dimension } from 'vs/base/browser/dom';
|
||||
import { EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
@@ -232,7 +232,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
private redraw(): void {
|
||||
const editor = withNullAsUndefined(this.group.activeEditor);
|
||||
|
||||
const isEditorPinned = this.group.activeEditor ? this.group.isPinned(this.group.activeEditor) : false;
|
||||
const isEditorPinned = editor ? this.group.isPinned(editor) : false;
|
||||
const isGroupActive = this.accessor.activeGroup === this.group;
|
||||
|
||||
this.activeLabel = { editor, pinned: isEditorPinned };
|
||||
@@ -320,4 +320,10 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
// Group inactive: only show close action
|
||||
return { primaryEditorActions: editorActions.primary.filter(action => action.id === CLOSE_EDITOR_COMMAND_ID), secondaryEditorActions: [] };
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
if (this.breadcrumbsControl) {
|
||||
this.breadcrumbsControl.layout(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
@@ -26,9 +26,11 @@ export class RangeHighlightDecorations extends Disposable {
|
||||
private readonly editorDisposables = this._register(new DisposableStore());
|
||||
|
||||
private readonly _onHighlightRemoved: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onHighlightRemoved: Event<void> = this._onHighlightRemoved.event;
|
||||
readonly onHighlightRemoved = this._onHighlightRemoved.event;
|
||||
|
||||
constructor(@IEditorService private readonly editorService: IEditorService) {
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdenti
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { addClass, addDisposableListener, hasClass, EventType, EventHelper, removeClass, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl, EDITOR_TITLE_HEIGHT, computeEditorAriaLabel } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
|
||||
@@ -107,7 +107,8 @@ export class TabsTitleControl extends TitleControl {
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IEditorService private readonly editorService: EditorServiceImpl,
|
||||
@IPathService private readonly pathService: IPathService
|
||||
@IPathService private readonly pathService: IPathService,
|
||||
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, extensionService, configurationService, fileService);
|
||||
|
||||
@@ -199,15 +200,12 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
private updateBreadcrumbsControl(): void {
|
||||
if (this.breadcrumbsControl && this.breadcrumbsControl.update()) {
|
||||
// relayout when we have a breadcrumbs and when update changed
|
||||
// its hidden-status
|
||||
this.group.relayout();
|
||||
this.group.relayout(); // relayout when we have a breadcrumbs and when update changed its hidden-status
|
||||
}
|
||||
}
|
||||
|
||||
protected handleBreadcrumbsEnablementChange(): void {
|
||||
// relayout when breadcrumbs are enable/disabled
|
||||
this.group.relayout();
|
||||
this.group.relayout(); // relayout when breadcrumbs are enable/disabled
|
||||
}
|
||||
|
||||
private registerTabsContainerListeners(tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): void {
|
||||
@@ -898,12 +896,12 @@ export class TabsTitleControl extends TitleControl {
|
||||
const { verbosity, shortenDuplicates } = this.getLabelConfigFlags(labelFormat);
|
||||
|
||||
// Build labels and descriptions for each editor
|
||||
const labels = this.group.editors.map(editor => ({
|
||||
const labels = this.group.editors.map((editor, index) => ({
|
||||
editor,
|
||||
name: editor.getName(),
|
||||
description: editor.getDescription(verbosity),
|
||||
title: withNullAsUndefined(editor.getTitle(Verbosity.LONG)),
|
||||
ariaLabel: editor.isReadonly() ? localize('readonlyEditor', "{0} readonly", editor.getTitle(Verbosity.SHORT)) : editor.getTitle(Verbosity.SHORT)
|
||||
ariaLabel: computeEditorAriaLabel(editor, index, this.group, this.editorGroupService.count)
|
||||
}));
|
||||
|
||||
// Shorten labels as needed
|
||||
@@ -1211,6 +1209,10 @@ export class TabsTitleControl extends TitleControl {
|
||||
return hasModifiedBorderColor;
|
||||
}
|
||||
|
||||
getPreferredHeight(): number {
|
||||
return EDITOR_TITLE_HEIGHT + (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden() ? BreadcrumbsControl.HEIGHT : 0);
|
||||
}
|
||||
|
||||
layout(dimension: Dimension | undefined): void {
|
||||
this.dimension = dimension;
|
||||
|
||||
|
||||
@@ -223,19 +223,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
|
||||
return options;
|
||||
}
|
||||
|
||||
protected getAriaLabel(): string {
|
||||
let ariaLabel: string;
|
||||
|
||||
const inputName = this.input?.getName();
|
||||
if (this.input?.isReadonly()) {
|
||||
ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0} readonly compare", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly compare");
|
||||
} else {
|
||||
ariaLabel = inputName ? nls.localize('editableEditorWithInputAriaLabel', "{0} compare", inputName) : nls.localize('editableEditorAriaLabel', "Compare");
|
||||
}
|
||||
|
||||
return ariaLabel;
|
||||
}
|
||||
|
||||
private isFileBinaryError(error: Error[]): boolean;
|
||||
private isFileBinaryError(error: Error): boolean;
|
||||
private isFileBinaryError(error: Error | Error[]): boolean {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { isCodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { computeEditorAriaLabel } from 'vs/workbench/browser/parts/editor/editor';
|
||||
|
||||
export interface IEditorConfiguration {
|
||||
editor: object;
|
||||
@@ -102,16 +103,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditorPa
|
||||
}
|
||||
|
||||
private computeAriaLabel(): string {
|
||||
let ariaLabel = this.getAriaLabel();
|
||||
|
||||
// Apply group information to help identify in
|
||||
// which group we are (only if more than one group
|
||||
// is actually opened)
|
||||
if (ariaLabel && this.group && this.editorGroupService.count > 1) {
|
||||
ariaLabel = localize('editorLabelWithGroup', "{0}, {1}", ariaLabel, this.group.ariaLabel);
|
||||
}
|
||||
|
||||
return ariaLabel;
|
||||
return this._input ? computeEditorAriaLabel(this._input, undefined, this.group, this.editorGroupService.count) : localize('editor', "Editor");
|
||||
}
|
||||
|
||||
protected getConfigurationOverrides(): IEditorOptions {
|
||||
@@ -303,8 +295,6 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditorPa
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected abstract getAriaLabel(): string;
|
||||
|
||||
dispose(): void {
|
||||
this.lastAppliedEditorOptions = undefined;
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { basenameOrAuthority } from 'vs/base/common/resources';
|
||||
import { ModelConstants } from 'vs/editor/common/model';
|
||||
|
||||
/**
|
||||
@@ -108,11 +107,6 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
protected getAriaLabel(): string {
|
||||
const inputName = this.input instanceof UntitledTextEditorInput ? basenameOrAuthority(this.input.resource) : this.input?.getName() || nls.localize('writeableEditorAriaLabel', "Editor");
|
||||
return this.input?.isReadonly() ? nls.localize('readonlyEditor', "{0} readonly", inputName) : inputName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reveals the last line of this editor if it has a model set.
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,7 @@ import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceData
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
|
||||
import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext, EditorStickyContext } from 'vs/workbench/common/editor';
|
||||
import { ResourceContextKey } from 'vs/workbench/common/resources';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
@@ -51,7 +51,7 @@ export abstract class TitleControl extends Themable {
|
||||
protected readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
|
||||
protected readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
|
||||
|
||||
protected breadcrumbsControl?: BreadcrumbsControl;
|
||||
protected breadcrumbsControl: BreadcrumbsControl | undefined = undefined;
|
||||
|
||||
private currentPrimaryEditorActionIds: string[] = [];
|
||||
private currentSecondaryEditorActionIds: string[] = [];
|
||||
@@ -118,6 +118,7 @@ export abstract class TitleControl extends Themable {
|
||||
this.handleBreadcrumbsEnablementChange();
|
||||
}
|
||||
}));
|
||||
|
||||
if (config.getValue()) {
|
||||
this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group);
|
||||
}
|
||||
@@ -401,15 +402,9 @@ export abstract class TitleControl extends Themable {
|
||||
|
||||
abstract updateStyles(): void;
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
if (this.breadcrumbsControl) {
|
||||
this.breadcrumbsControl.layout(undefined);
|
||||
}
|
||||
}
|
||||
abstract layout(dimension: Dimension): void;
|
||||
|
||||
getPreferredHeight(): number {
|
||||
return EDITOR_TITLE_HEIGHT + (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden() ? BreadcrumbsControl.HEIGHT : 0);
|
||||
}
|
||||
abstract getPreferredHeight(): number;
|
||||
|
||||
dispose(): void {
|
||||
dispose(this.breadcrumbsControl);
|
||||
|
||||
@@ -305,7 +305,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente
|
||||
this.hide();
|
||||
|
||||
// Close all
|
||||
for (const notification of this.model.notifications) {
|
||||
for (const notification of [...this.model.notifications] /* copy array since we modify it from closing */) {
|
||||
if (!notification.hasProgress) {
|
||||
notification.close();
|
||||
}
|
||||
|
||||
@@ -63,12 +63,9 @@
|
||||
}
|
||||
|
||||
.monaco-workbench .part.panel .empty-panel-message-area {
|
||||
position: absolute;
|
||||
display: none;
|
||||
top: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.panel .empty-panel-message-area.visible {
|
||||
|
||||
@@ -282,7 +282,6 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(TogglePanelActi
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPanelAction), 'View: Focus into Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMaximizedPanelAction), 'View: Toggle Maximized Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ClosePanelAction), 'View: Close Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMaximizedPanelAction), 'View: Toggle Panel Position', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(PreviousPanelViewAction), 'View: Previous Panel View', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NextPanelViewAction), 'View: Next Panel View', nls.localize('view', "View"));
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions';
|
||||
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_INPUT_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, PANEL_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar';
|
||||
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
@@ -35,7 +35,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ViewContainer, IViewDescriptorService, IViewContainerModel, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
|
||||
import { ViewMenuActions, ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { Before2D, CompositeDragAndDropObserver, ICompositeDragAndDrop } from 'vs/workbench/browser/dnd';
|
||||
@@ -50,11 +50,17 @@ interface ICachedPanel {
|
||||
views?: { when?: string }[];
|
||||
}
|
||||
|
||||
interface IPlaceholderViewContainer {
|
||||
id: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
static readonly activePanelSettingsKey = 'workbench.panelpart.activepanelid';
|
||||
|
||||
static readonly PINNED_PANELS = 'workbench.panel.pinnedPanels';
|
||||
static readonly PLACEHOLDER_VIEW_CONTAINERS = 'workbench.panel.placeholderPanels';
|
||||
private static readonly MIN_COMPOSITE_BAR_WIDTH = 50;
|
||||
|
||||
_serviceBrand: undefined;
|
||||
@@ -94,6 +100,8 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
private blockOpeningPanel = false;
|
||||
private contentDimension: Dimension | undefined;
|
||||
|
||||
private extensionsRegistered = false;
|
||||
|
||||
private panelRegistry: PanelRegistry;
|
||||
|
||||
private dndHandler: ICompositeDragAndDrop;
|
||||
@@ -135,8 +143,9 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: PanelPart.PINNED_PANELS, version: 1 });
|
||||
|
||||
this.dndHandler = new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel,
|
||||
(id: string, focus?: boolean) => (<unknown>this.openPanel(id, focus) as Promise<IPaneComposite | undefined>).then(panel => panel || null),
|
||||
(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.horizontallyBefore)
|
||||
(id: string, focus?: boolean) => (this.openPanel(id, focus) as Promise<IPaneComposite | undefined>).then(panel => panel || null),
|
||||
(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.horizontallyBefore),
|
||||
() => this.compositeBar.getCompositeBarItems()
|
||||
);
|
||||
|
||||
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), {
|
||||
@@ -167,7 +176,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
inactiveForegroundColor: theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND),
|
||||
badgeBackground: theme.getColor(badgeBackground),
|
||||
badgeForeground: theme.getColor(badgeForeground),
|
||||
dragAndDropBackground: theme.getColor(PANEL_DRAG_AND_DROP_BACKGROUND)
|
||||
dragAndDropBorder: theme.getColor(PANEL_DRAG_AND_DROP_BORDER)
|
||||
})
|
||||
}));
|
||||
|
||||
@@ -188,6 +197,10 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
result.push(...viewMenuActions.getContextMenuActions());
|
||||
viewMenuActions.dispose();
|
||||
}
|
||||
|
||||
const viewContainerMenuActions = this.instantiationService.createInstance(ViewContainerMenuActions, container.id, MenuId.ViewContainerTitleContext);
|
||||
result.push(...viewContainerMenuActions.getContextMenuActions());
|
||||
viewContainerMenuActions.dispose();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -196,10 +209,21 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
for (const panel of panels) {
|
||||
const cachedPanel = this.getCachedPanels().filter(({ id }) => id === panel.id)[0];
|
||||
const activePanel = this.getActivePanel();
|
||||
const isActive = activePanel?.getId() === panel.id || (!activePanel && this.getLastActivePanelId() === panel.id);
|
||||
const isActive =
|
||||
activePanel?.getId() === panel.id ||
|
||||
(!activePanel && this.getLastActivePanelId() === panel.id) ||
|
||||
(this.extensionsRegistered && this.compositeBar.getVisibleComposites().length === 0);
|
||||
|
||||
if (isActive || !this.shouldBeHidden(panel.id, cachedPanel)) {
|
||||
this.compositeBar.addComposite(panel);
|
||||
|
||||
// Override order
|
||||
const newPanel = {
|
||||
id: panel.id,
|
||||
name: panel.name,
|
||||
order: cachedPanel?.order === undefined ? panel.order : cachedPanel.order
|
||||
};
|
||||
|
||||
this.compositeBar.addComposite(newPanel);
|
||||
|
||||
// Pin it by default if it is new
|
||||
if (!cachedPanel) {
|
||||
@@ -255,9 +279,11 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
private updateActivity(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
|
||||
const cachedTitle = this.getPlaceholderViewContainers().filter(panel => panel.id === viewContainer.id)[0]?.name;
|
||||
|
||||
const activity: IActivity = {
|
||||
id: viewContainer.id,
|
||||
name: viewContainerModel.title,
|
||||
name: this.extensionsRegistered || cachedTitle === undefined ? viewContainerModel.title : cachedTitle,
|
||||
keybindingId: viewContainer.focusCommand?.id
|
||||
};
|
||||
|
||||
@@ -268,7 +294,10 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
pinnedAction.setActivity(activity);
|
||||
}
|
||||
|
||||
this.saveCachedPanels();
|
||||
// only update our cached panel info after extensions are done registering
|
||||
if (this.extensionsRegistered) {
|
||||
this.saveCachedPanels();
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeActiveViews(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
|
||||
@@ -291,6 +320,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Panel registration
|
||||
this._register(this.registry.onDidRegister(panel => this.onDidRegisterPanels([panel])));
|
||||
this._register(this.registry.onDidDeregister(panel => this.onDidDeregisterPanel(panel.id)));
|
||||
@@ -313,6 +343,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
private onDidRegisterExtensions(): void {
|
||||
this.extensionsRegistered = true;
|
||||
this.removeNotExistingComposites();
|
||||
|
||||
this.saveCachedPanels();
|
||||
@@ -388,15 +419,16 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
private createEmptyPanelMessage(): void {
|
||||
const contentArea = this.getContentArea()!;
|
||||
this.emptyPanelMessageElement = document.createElement('div');
|
||||
addClass(this.emptyPanelMessageElement, 'empty-panel-message-area');
|
||||
|
||||
const messageElement = document.createElement('div');
|
||||
addClass(messageElement, 'empty-panel-message');
|
||||
messageElement.innerText = localize('panel.emptyMessage', "No panels to display. Drag a view into the panel.");
|
||||
messageElement.innerText = localize('panel.emptyMessage', "Drag a view into the panel to display.");
|
||||
|
||||
this.emptyPanelMessageElement.appendChild(messageElement);
|
||||
this.element.appendChild(this.emptyPanelMessageElement);
|
||||
contentArea.appendChild(this.emptyPanelMessageElement);
|
||||
|
||||
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(this.emptyPanelMessageElement, {
|
||||
onDragOver: (e) => {
|
||||
@@ -455,7 +487,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
}
|
||||
|
||||
return this.openComposite(id, focus);
|
||||
return this.openComposite(id, focus) as Panel;
|
||||
}
|
||||
|
||||
async openPanel(id?: string, focus?: boolean): Promise<Panel | undefined> {
|
||||
@@ -670,6 +702,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
private saveCachedPanels(): void {
|
||||
const state: ICachedPanel[] = [];
|
||||
const placeholders: IPlaceholderViewContainer[] = [];
|
||||
|
||||
const compositeItems = this.compositeBar.getCompositeBarItems();
|
||||
for (const compositeItem of compositeItems) {
|
||||
@@ -677,10 +710,12 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
if (viewContainer) {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
state.push({ id: compositeItem.id, name: viewContainerModel.title, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible });
|
||||
placeholders.push({ id: compositeItem.id, name: this.getCompositeActions(compositeItem.id).activityAction.label });
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedPanelsValue = JSON.stringify(state);
|
||||
this.setPlaceholderViewContainers(placeholders);
|
||||
}
|
||||
|
||||
private getCachedPanels(): ICachedPanel[] {
|
||||
@@ -694,6 +729,13 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
return serialized;
|
||||
});
|
||||
|
||||
for (const placeholderViewContainer of this.getPlaceholderViewContainers()) {
|
||||
const cachedViewContainer = cachedPanels.filter(cached => cached.id === placeholderViewContainer.id)[0];
|
||||
if (cachedViewContainer) {
|
||||
cachedViewContainer.name = placeholderViewContainer.name;
|
||||
}
|
||||
}
|
||||
|
||||
return cachedPanels;
|
||||
}
|
||||
|
||||
@@ -721,6 +763,38 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
this.storageService.store(PanelPart.PINNED_PANELS, value, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private getPlaceholderViewContainers(): IPlaceholderViewContainer[] {
|
||||
return JSON.parse(this.placeholderViewContainersValue);
|
||||
}
|
||||
|
||||
private setPlaceholderViewContainers(placeholderViewContainers: IPlaceholderViewContainer[]): void {
|
||||
this.placeholderViewContainersValue = JSON.stringify(placeholderViewContainers);
|
||||
}
|
||||
|
||||
private _placeholderViewContainersValue: string | undefined;
|
||||
private get placeholderViewContainersValue(): string {
|
||||
if (!this._placeholderViewContainersValue) {
|
||||
this._placeholderViewContainersValue = this.getStoredPlaceholderViewContainersValue();
|
||||
}
|
||||
|
||||
return this._placeholderViewContainersValue;
|
||||
}
|
||||
|
||||
private set placeholderViewContainersValue(placeholderViewContainesValue: string) {
|
||||
if (this.placeholderViewContainersValue !== placeholderViewContainesValue) {
|
||||
this._placeholderViewContainersValue = placeholderViewContainesValue;
|
||||
this.setStoredPlaceholderViewContainersValue(placeholderViewContainesValue);
|
||||
}
|
||||
}
|
||||
|
||||
private getStoredPlaceholderViewContainersValue(): string {
|
||||
return this.storageService.get(PanelPart.PLACEHOLDER_VIEW_CONTAINERS, StorageScope.WORKSPACE, '[]');
|
||||
}
|
||||
|
||||
private setStoredPlaceholderViewContainersValue(value: string): void {
|
||||
this.storageService.store(PanelPart.PLACEHOLDER_VIEW_CONTAINERS, value, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
private getViewContainer(panelId: string): ViewContainer | undefined {
|
||||
return this.viewDescriptorService.getViewContainerById(panelId) || undefined;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
align-items: center;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
outline-width: 0px; /* do not render focus outline, we already have background */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item > a:hover {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { dispose, IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -69,6 +68,10 @@ class StatusbarViewModel extends Disposable {
|
||||
get entries(): IStatusbarViewModelEntry[] { return this._entries; }
|
||||
|
||||
private hidden!: Set<string>;
|
||||
get lastFocusedEntry(): IStatusbarViewModelEntry | undefined {
|
||||
return this._lastFocusedEntry && !this.isHidden(this._lastFocusedEntry.id) ? this._lastFocusedEntry : undefined;
|
||||
}
|
||||
private _lastFocusedEntry: IStatusbarViewModelEntry | undefined;
|
||||
|
||||
private readonly _onDidChangeEntryVisibility = this._register(new Emitter<{ id: string, visible: boolean }>());
|
||||
readonly onDidChangeEntryVisibility = this._onDidChangeEntryVisibility.event;
|
||||
@@ -219,6 +222,7 @@ class StatusbarViewModel extends Disposable {
|
||||
if (focused) {
|
||||
const entry = getVisibleEntry(this._entries.indexOf(focused) + delta);
|
||||
if (entry) {
|
||||
this._lastFocusedEntry = entry;
|
||||
entry.labelContainer.focus();
|
||||
return;
|
||||
}
|
||||
@@ -226,6 +230,7 @@ class StatusbarViewModel extends Disposable {
|
||||
|
||||
const entry = getVisibleEntry(restartPosition);
|
||||
if (entry) {
|
||||
this._lastFocusedEntry = entry;
|
||||
entry.labelContainer.focus();
|
||||
}
|
||||
}
|
||||
@@ -493,6 +498,15 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
this.viewModel.focusPreviousEntry();
|
||||
}
|
||||
|
||||
focus(preserveEntryFocus = true): void {
|
||||
this.getContainer()?.focus();
|
||||
const lastFocusedEntry = this.viewModel.lastFocusedEntry;
|
||||
if (preserveEntryFocus && lastFocusedEntry) {
|
||||
// Need a timeout, for some reason without it the inner label container will not get focused
|
||||
setTimeout(() => lastFocusedEntry.labelContainer.focus(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
createContentArea(parent: HTMLElement): HTMLElement {
|
||||
this.element = parent;
|
||||
|
||||
@@ -680,10 +694,6 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
return itemContainer;
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.getContainer();
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
super.layout(width, height);
|
||||
super.layoutContents(width, height);
|
||||
@@ -715,7 +725,6 @@ class StatusbarEntryItem extends Disposable {
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IThemeService private readonly themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
@@ -773,7 +782,7 @@ class StatusbarEntryItem extends Disposable {
|
||||
const command = entry.command;
|
||||
if (command) {
|
||||
this.commandMouseListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));
|
||||
this.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_UP, e => {
|
||||
this.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_DOWN, e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {
|
||||
this.executeCommand(command);
|
||||
@@ -818,12 +827,6 @@ class StatusbarEntryItem extends Disposable {
|
||||
const id = typeof command === 'string' ? command : command.id;
|
||||
const args = typeof command === 'string' ? [] : command.arguments ?? [];
|
||||
|
||||
// Maintain old behaviour of always focusing the editor here
|
||||
const activeTextEditorControl = this.editorService.activeTextEditorControl;
|
||||
if (activeTextEditorControl) {
|
||||
activeTextEditorControl.focus();
|
||||
}
|
||||
|
||||
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id, from: 'status bar' });
|
||||
try {
|
||||
await this.commandService.executeCommand(id, ...args);
|
||||
@@ -935,3 +938,38 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
statusBarService.focusNextEntry();
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.statusBar.focusFirst',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.Home,
|
||||
when: CONTEXT_STATUS_BAR_FOCUSED,
|
||||
handler: (accessor: ServicesAccessor) => {
|
||||
const statusBarService = accessor.get(IStatusbarService);
|
||||
statusBarService.focus(false);
|
||||
statusBarService.focusNextEntry();
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.statusBar.focusLast',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.End,
|
||||
when: CONTEXT_STATUS_BAR_FOCUSED,
|
||||
handler: (accessor: ServicesAccessor) => {
|
||||
const statusBarService = accessor.get(IStatusbarService);
|
||||
statusBarService.focus(false);
|
||||
statusBarService.focusPreviousEntry();
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.statusBar.clearFocus',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.Escape,
|
||||
when: CONTEXT_STATUS_BAR_FOCUSED,
|
||||
handler: (accessor: ServicesAccessor) => {
|
||||
const statusBarService = accessor.get(IStatusbarService);
|
||||
statusBarService.focus(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -333,7 +333,6 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl));
|
||||
|
||||
this.menubar = this.element.insertBefore($('div.menubar'), this.title);
|
||||
|
||||
this.menubar.setAttribute('role', 'menubar');
|
||||
|
||||
this.customMenubar.create(this.menubar);
|
||||
|
||||
@@ -18,6 +18,16 @@
|
||||
.monaco-pane-view .pane > .pane-header > .actions.show {
|
||||
display: initial;
|
||||
}
|
||||
.monaco-pane-view .pane > .pane-header .icon {
|
||||
display: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane.pane.horizontal:not(.expanded) > .pane-header .icon {
|
||||
display: inline;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header h3.title {
|
||||
white-space: nowrap;
|
||||
@@ -28,6 +38,11 @@
|
||||
-webkit-margin-after: 0;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header h3.title,
|
||||
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header .description {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane .monaco-progress-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
@@ -68,6 +68,9 @@ export class TreeViewPane extends ViewPane {
|
||||
this._register(toDisposable(() => this.treeView.setVisibility(false)));
|
||||
this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility()));
|
||||
this._register(this.treeView.onDidChangeWelcomeState(() => this._onDidChangeViewWelcomeState.fire()));
|
||||
if (options.title !== this.treeView.title) {
|
||||
this.updateTitle(this.treeView.title);
|
||||
}
|
||||
this.updateTreeVisibility();
|
||||
}
|
||||
|
||||
@@ -161,7 +164,7 @@ export class TreeView extends Disposable implements ITreeView {
|
||||
private readonly _onDidCompleteRefresh: Emitter<void> = this._register(new Emitter<void>());
|
||||
|
||||
constructor(
|
||||
protected readonly id: string,
|
||||
readonly id: string,
|
||||
private _title: string,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@@ -425,8 +428,15 @@ export class TreeView extends Disposable implements ITreeView {
|
||||
identityProvider: new TreeViewIdentityProvider(),
|
||||
accessibilityProvider: {
|
||||
getAriaLabel(element: ITreeItem): string {
|
||||
if (element.accessibilityInformation) {
|
||||
return element.accessibilityInformation.label;
|
||||
}
|
||||
|
||||
return element.tooltip ? element.tooltip : element.label ? element.label.label : '';
|
||||
},
|
||||
getRole(element: ITreeItem): string | undefined {
|
||||
return element.accessibilityInformation?.role;
|
||||
},
|
||||
getWidgetAriaLabel(): string {
|
||||
return widgetAriaLabel;
|
||||
}
|
||||
@@ -783,15 +793,15 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : undefined;
|
||||
const label = treeItemLabel ? treeItemLabel.label : undefined;
|
||||
const matches = (treeItemLabel && treeItemLabel.highlights && label) ? treeItemLabel.highlights.map(([start, end]) => {
|
||||
if ((Math.abs(start) > label.length) || (Math.abs(end) >= label.length)) {
|
||||
return ({ start: 0, end: 0 });
|
||||
}
|
||||
if (start < 0) {
|
||||
start = label.length + start;
|
||||
}
|
||||
if (end < 0) {
|
||||
end = label.length + end;
|
||||
}
|
||||
if ((start >= label.length) || (end > label.length)) {
|
||||
return ({ start: 0, end: 0 });
|
||||
}
|
||||
if (start > end) {
|
||||
const swap = start;
|
||||
start = end;
|
||||
|
||||
@@ -69,3 +69,37 @@ export class ViewMenuActions extends Disposable {
|
||||
return this.contextMenuActions;
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewContainerMenuActions extends Disposable {
|
||||
|
||||
private readonly titleActionsDisposable = this._register(new MutableDisposable());
|
||||
private contextMenuActions: IAction[] = [];
|
||||
|
||||
constructor(
|
||||
containerId: string,
|
||||
contextMenuId: MenuId,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
) {
|
||||
super();
|
||||
|
||||
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
|
||||
scopedContextKeyService.createKey('container', containerId);
|
||||
|
||||
const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService));
|
||||
const updateContextMenuActions = () => {
|
||||
this.contextMenuActions = [];
|
||||
this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, { shouldForwardArgs: true }, { primary: [], secondary: this.contextMenuActions });
|
||||
};
|
||||
this._register(contextMenu.onDidChange(updateContextMenuActions));
|
||||
updateContextMenuActions();
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
this.contextMenuActions = [];
|
||||
}));
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
return this.contextMenuActions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
import 'vs/css!./media/paneviewlet';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ColorIdentifier, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ColorIdentifier, activeContrastBorder, foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND, EDITOR_DRAG_AND_DROP_BACKGROUND, PANEL_BORDER } from 'vs/workbench/common/theme';
|
||||
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND, PANEL_SECTION_HEADER_FOREGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_BORDER } from 'vs/workbench/common/theme';
|
||||
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass, createCSSRule, asCSSUrl, addClasses } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
@@ -20,20 +20,20 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { PaneView, IPaneViewOptions, IPaneOptions, Pane } from 'vs/base/browser/ui/splitview/paneview';
|
||||
import { PaneView, IPaneViewOptions, IPaneOptions, Pane, IPaneStyles } from 'vs/base/browser/ui/splitview/paneview';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel } from 'vs/workbench/common/views';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { assertIsDefined, isString } from 'vs/base/common/types';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { Component } from 'vs/workbench/common/component';
|
||||
import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { MenuId, MenuItemAction, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions';
|
||||
import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
|
||||
import { parseLinkedText } from 'vs/base/common/linkedText';
|
||||
@@ -48,6 +48,9 @@ import { IProgressIndicator } from 'vs/platform/progress/common/progress';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
|
||||
export interface IPaneColors extends IColorMapping {
|
||||
dropBackground?: ColorIdentifier;
|
||||
@@ -185,6 +188,7 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
private readonly showActionsAlways: boolean = false;
|
||||
private headerContainer?: HTMLElement;
|
||||
private titleContainer?: HTMLElement;
|
||||
private iconContainer?: HTMLElement;
|
||||
protected twistiesContainer?: HTMLElement;
|
||||
|
||||
private bodyContainer!: HTMLElement;
|
||||
@@ -290,6 +294,10 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
this._register(this.toolbar);
|
||||
this.setActions();
|
||||
|
||||
this._register(this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!)!.onDidChangeContainerInfo(({ title }) => {
|
||||
this.updateTitle(this.title);
|
||||
}));
|
||||
|
||||
const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));
|
||||
this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));
|
||||
this.updateActionsVisibility();
|
||||
@@ -299,18 +307,86 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
this.twistiesContainer = append(container, $('.twisties.codicon.codicon-chevron-right'));
|
||||
}
|
||||
|
||||
style(styles: IPaneStyles): void {
|
||||
super.style(styles);
|
||||
|
||||
const icon = this.getIcon();
|
||||
if (this.iconContainer) {
|
||||
const fgColor = styles.headerForeground || this.themeService.getColorTheme().getColor(foreground);
|
||||
if (URI.isUri(icon)) {
|
||||
// Apply background color to activity bar item provided with iconUrls
|
||||
this.iconContainer.style.backgroundColor = fgColor ? fgColor.toString() : '';
|
||||
this.iconContainer.style.color = '';
|
||||
} else {
|
||||
// Apply foreground color to activity bar items provided with codicons
|
||||
this.iconContainer.style.color = fgColor ? fgColor.toString() : '';
|
||||
this.iconContainer.style.backgroundColor = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getIcon(): string | URI {
|
||||
return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || 'codicon-window';
|
||||
}
|
||||
|
||||
protected renderHeaderTitle(container: HTMLElement, title: string): void {
|
||||
this.titleContainer = append(container, $('h3.title', undefined, title));
|
||||
this.iconContainer = append(container, $('.icon', undefined));
|
||||
const icon = this.getIcon();
|
||||
|
||||
let cssClass: string | undefined = undefined;
|
||||
if (URI.isUri(icon)) {
|
||||
cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`;
|
||||
const iconClass = `.pane-header .icon.${cssClass}`;
|
||||
|
||||
createCSSRule(iconClass, `
|
||||
mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
|
||||
mask-size: 24px;
|
||||
-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
|
||||
-webkit-mask-size: 16px;
|
||||
`);
|
||||
} else if (isString(icon)) {
|
||||
addClass(this.iconContainer, 'codicon');
|
||||
cssClass = icon;
|
||||
}
|
||||
|
||||
if (cssClass) {
|
||||
addClasses(this.iconContainer, cssClass);
|
||||
}
|
||||
|
||||
const calculatedTitle = this.calculateTitle(title);
|
||||
this.titleContainer = append(container, $('h3.title', undefined, calculatedTitle));
|
||||
this.iconContainer.title = calculatedTitle;
|
||||
this.iconContainer.setAttribute('aria-label', calculatedTitle);
|
||||
}
|
||||
|
||||
protected updateTitle(title: string): void {
|
||||
const calculatedTitle = this.calculateTitle(title);
|
||||
if (this.titleContainer) {
|
||||
this.titleContainer.textContent = title;
|
||||
this.titleContainer.textContent = calculatedTitle;
|
||||
}
|
||||
|
||||
if (this.iconContainer) {
|
||||
this.iconContainer.title = calculatedTitle;
|
||||
this.iconContainer.setAttribute('aria-label', calculatedTitle);
|
||||
}
|
||||
|
||||
this.title = title;
|
||||
this._onDidChangeTitleArea.fire();
|
||||
}
|
||||
|
||||
private calculateTitle(title: string): string {
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;
|
||||
const model = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);
|
||||
const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;
|
||||
|
||||
if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) {
|
||||
return `${viewDescriptor.containerTitle}: ${title}`;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
private scrollableElement!: DomScrollableElement;
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
@@ -510,7 +586,6 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
|
||||
export interface IViewPaneContainerOptions extends IPaneViewOptions {
|
||||
mergeViewWithContainerWhenSingleView: boolean;
|
||||
donotShowContainerTitleWhenMergedWithContainer?: boolean;
|
||||
}
|
||||
|
||||
interface IViewPaneItem {
|
||||
@@ -546,7 +621,8 @@ class ViewPaneDropOverlay extends Themable {
|
||||
constructor(
|
||||
private paneElement: HTMLElement,
|
||||
private orientation: Orientation | undefined,
|
||||
protected themeService: IThemeService
|
||||
protected location: ViewContainerLocation,
|
||||
protected themeService: IThemeService,
|
||||
) {
|
||||
super(themeService);
|
||||
this.cleanupOverlayScheduler = this._register(new RunOnceScheduler(() => this.dispose(), 300));
|
||||
@@ -587,7 +663,7 @@ class ViewPaneDropOverlay extends Themable {
|
||||
protected updateStyles(): void {
|
||||
|
||||
// Overlay drop background
|
||||
this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || '';
|
||||
this.overlay.style.backgroundColor = this.getColor(this.location === ViewContainerLocation.Panel ? PANEL_SECTION_DRAG_AND_DROP_BACKGROUND : SIDE_BAR_DRAG_AND_DROP_BACKGROUND) || '';
|
||||
|
||||
// Overlay contrast border (if any)
|
||||
const activeContrastBorderColor = this.getColor(activeContrastBorder);
|
||||
@@ -830,7 +906,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
return;
|
||||
}
|
||||
|
||||
overlay = new ViewPaneDropOverlay(parent, undefined, this.themeService);
|
||||
overlay = new ViewPaneDropOverlay(parent, undefined, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
|
||||
}
|
||||
|
||||
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) {
|
||||
@@ -838,7 +914,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors;
|
||||
|
||||
if (!viewsToMove.some(v => !v.canMoveView)) {
|
||||
overlay = new ViewPaneDropOverlay(parent, undefined, this.themeService);
|
||||
overlay = new ViewPaneDropOverlay(parent, undefined, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,7 +980,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
const paneItemTitle = this.paneItems[0].pane.title;
|
||||
if (this.options.donotShowContainerTitleWhenMergedWithContainer || containerTitle === paneItemTitle) {
|
||||
if (containerTitle === paneItemTitle) {
|
||||
return this.paneItems[0].pane.title;
|
||||
}
|
||||
return paneItemTitle ? `${containerTitle}: ${paneItemTitle}` : containerTitle;
|
||||
@@ -1227,6 +1303,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
this.updateTitleArea();
|
||||
}
|
||||
});
|
||||
|
||||
const onDidChangeVisibility = pane.onDidChangeBodyVisibility(() => this._onDidChangeViewVisibility.fire(pane));
|
||||
const onDidChange = pane.onDidChange(() => {
|
||||
if (pane === this.lastFocusedPane && !pane.isExpanded()) {
|
||||
@@ -1234,13 +1311,13 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO@sbatten Styling is viewlet specific, must fix
|
||||
const isPanel = this.viewDescriptorService.getViewLocationById(this.viewContainer.id) === ViewContainerLocation.Panel;
|
||||
const paneStyler = attachStyler<IPaneColors>(this.themeService, {
|
||||
headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND,
|
||||
headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND,
|
||||
headerBorder: SIDE_BAR_SECTION_HEADER_BORDER,
|
||||
leftBorder: PANEL_BORDER,
|
||||
dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND
|
||||
headerForeground: isPanel ? PANEL_SECTION_HEADER_FOREGROUND : SIDE_BAR_SECTION_HEADER_FOREGROUND,
|
||||
headerBackground: isPanel ? PANEL_SECTION_HEADER_BACKGROUND : SIDE_BAR_SECTION_HEADER_BACKGROUND,
|
||||
headerBorder: isPanel ? PANEL_SECTION_HEADER_BORDER : SIDE_BAR_SECTION_HEADER_BORDER,
|
||||
dropBackground: isPanel ? PANEL_SECTION_DRAG_AND_DROP_BACKGROUND : SIDE_BAR_DRAG_AND_DROP_BACKGROUND,
|
||||
leftBorder: isPanel ? PANEL_SECTION_BORDER : undefined
|
||||
}, pane);
|
||||
const disposable = combinedDisposable(pane, onDidFocus, onDidChangeTitleArea, paneStyler, onDidChange, onDidChangeVisibility);
|
||||
const paneItem: IViewPaneItem = { pane, disposable };
|
||||
@@ -1265,7 +1342,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
return;
|
||||
}
|
||||
|
||||
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, this.themeService);
|
||||
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
|
||||
}
|
||||
|
||||
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id && !this.viewContainer.rejectAddedViews) {
|
||||
@@ -1273,7 +1350,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors;
|
||||
|
||||
if (!viewsToMove.some(v => !v.canMoveView)) {
|
||||
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, this.themeService);
|
||||
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1466,3 +1543,96 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MoveViewPosition extends Action2 {
|
||||
constructor(desc: Readonly<IAction2Options>, private readonly offset: number) {
|
||||
super(desc);
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const viewDescriptorService = accessor.get(IViewDescriptorService);
|
||||
const contextKeyService = accessor.get(IContextKeyService);
|
||||
|
||||
const viewId = FocusedViewContext.getValue(contextKeyService);
|
||||
if (viewId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewContainer = viewDescriptorService.getViewContainerByViewId(viewId)!;
|
||||
const model = viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
|
||||
const viewDescriptor = model.visibleViewDescriptors.find(vd => vd.id === viewId)!;
|
||||
const currentIndex = model.visibleViewDescriptors.indexOf(viewDescriptor);
|
||||
if (currentIndex + this.offset < 0 || currentIndex + this.offset >= model.visibleViewDescriptors.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newPosition = model.visibleViewDescriptors[currentIndex + this.offset];
|
||||
|
||||
model.move(viewDescriptor.id, newPosition.id);
|
||||
}
|
||||
}
|
||||
|
||||
registerAction2(
|
||||
class MoveViewUp extends MoveViewPosition {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'views.moveViewUp',
|
||||
title: nls.localize('viewMoveUp', "Move View Up"),
|
||||
keybinding: {
|
||||
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KEY_K, KeyCode.UpArrow),
|
||||
weight: KeybindingWeight.WorkbenchContrib + 1,
|
||||
when: FocusedViewContext.notEqualsTo('')
|
||||
}
|
||||
}, -1);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
registerAction2(
|
||||
class MoveViewLeft extends MoveViewPosition {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'views.moveViewLeft',
|
||||
title: nls.localize('viewMoveLeft', "Move View Left"),
|
||||
keybinding: {
|
||||
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KEY_K, KeyCode.LeftArrow),
|
||||
weight: KeybindingWeight.WorkbenchContrib + 1,
|
||||
when: FocusedViewContext.notEqualsTo('')
|
||||
}
|
||||
}, -1);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
registerAction2(
|
||||
class MoveViewDown extends MoveViewPosition {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'views.moveViewDown',
|
||||
title: nls.localize('viewMoveDown', "Move View Down"),
|
||||
keybinding: {
|
||||
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KEY_K, KeyCode.DownArrow),
|
||||
weight: KeybindingWeight.WorkbenchContrib + 1,
|
||||
when: FocusedViewContext.notEqualsTo('')
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
registerAction2(
|
||||
class MoveViewRight extends MoveViewPosition {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'views.moveViewRight',
|
||||
title: nls.localize('viewMoveRight', "Move View Right"),
|
||||
keybinding: {
|
||||
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KEY_K, KeyCode.RightArrow),
|
||||
weight: KeybindingWeight.WorkbenchContrib + 1,
|
||||
when: FocusedViewContext.notEqualsTo('')
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { PaneCompositePanel, PanelRegistry, PanelDescriptor, Extensions as PanelExtensions } from 'vs/workbench/browser/panel';
|
||||
import { PanelRegistry, PanelDescriptor, Extensions as PanelExtensions, Panel } from 'vs/workbench/browser/panel';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -186,8 +186,8 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
super({
|
||||
id: `${viewDescriptor.id}.resetViewLocation`,
|
||||
title: {
|
||||
original: 'Reset View Location',
|
||||
value: localize('resetViewLocation', "Reset View Location")
|
||||
original: 'Reset Location',
|
||||
value: localize('resetViewLocation', "Reset Location")
|
||||
},
|
||||
menu: [{
|
||||
id: MenuId.ViewTitleContext,
|
||||
@@ -202,6 +202,15 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
}
|
||||
run(accessor: ServicesAccessor): void {
|
||||
const viewDescriptorService = accessor.get(IViewDescriptorService);
|
||||
const defaultContainer = viewDescriptorService.getDefaultContainerById(viewDescriptor.id)!;
|
||||
const containerModel = viewDescriptorService.getViewContainerModel(defaultContainer)!;
|
||||
|
||||
// The default container is hidden so we should try to reset its location first
|
||||
if (defaultContainer.hideIfEmpty && containerModel.visibleViewDescriptors.length === 0) {
|
||||
const defaultLocation = viewDescriptorService.getDefaultViewContainerLocation(defaultContainer)!;
|
||||
viewDescriptorService.moveViewContainerToLocation(defaultContainer, defaultLocation);
|
||||
}
|
||||
|
||||
viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getDefaultContainerById(viewDescriptor.id)!);
|
||||
accessor.get(IViewsService).openView(viewDescriptor.id, true);
|
||||
}
|
||||
@@ -414,7 +423,7 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
|
||||
private registerPanel(viewContainer: ViewContainer): void {
|
||||
const that = this;
|
||||
class PaneContainerPanel extends PaneCompositePanel {
|
||||
class PaneContainerPanel extends Panel {
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { domContentLoaded, addDisposableListener, EventType, addClass, EventHelper } from 'vs/base/browser/dom';
|
||||
import { domContentLoaded, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { ILogService, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log';
|
||||
import { ConsoleLogInAutomationService } from 'vs/platform/log/browser/log';
|
||||
@@ -39,7 +39,6 @@ import { BACKUPS } from 'vs/platform/environment/common/environment';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { BrowserStorageService } from 'vs/platform/storage/browser/storageService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService';
|
||||
import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
import { FileLogService } from 'vs/platform/log/common/fileLogService';
|
||||
@@ -74,9 +73,6 @@ class BrowserMain extends Disposable {
|
||||
await domContentLoaded();
|
||||
mark('willStartWorkbench');
|
||||
|
||||
// Base Theme
|
||||
this.restoreBaseTheme();
|
||||
|
||||
// Create Workbench
|
||||
const workbench = new Workbench(
|
||||
this.domElement,
|
||||
@@ -131,7 +127,6 @@ class BrowserMain extends Disposable {
|
||||
}));
|
||||
this._register(workbench.onWillShutdown(() => {
|
||||
storageService.close();
|
||||
this.saveBaseTheme();
|
||||
}));
|
||||
this._register(workbench.onShutdown(() => this.dispose()));
|
||||
|
||||
@@ -147,21 +142,6 @@ class BrowserMain extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private restoreBaseTheme(): void {
|
||||
addClass(this.domElement, window.localStorage.getItem('vscode.baseTheme') || getThemeTypeSelector(LIGHT) /* Fallback to a light theme by default on web */);
|
||||
}
|
||||
|
||||
private saveBaseTheme(): void {
|
||||
const classes = this.domElement.className;
|
||||
const baseThemes = [DARK, LIGHT, HIGH_CONTRAST].map(baseTheme => getThemeTypeSelector(baseTheme));
|
||||
for (const baseTheme of baseThemes) {
|
||||
if (classes.indexOf(baseTheme) >= 0) {
|
||||
window.localStorage.setItem('vscode.baseTheme', baseTheme);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: BrowserStorageService }> {
|
||||
const serviceCollection = new ServiceCollection();
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
|
||||
},
|
||||
'workbench.editor.scrollToSwitchTabs': {
|
||||
'type': 'boolean',
|
||||
'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls wether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration."),
|
||||
'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration."),
|
||||
'default': false
|
||||
},
|
||||
'workbench.editor.highlightModifiedTabs': {
|
||||
|
||||
@@ -14,3 +14,4 @@ export interface IActivity {
|
||||
}
|
||||
|
||||
export const GLOBAL_ACTIVITY_ID = 'workbench.action.globalActivity';
|
||||
export const ACCOUNTS_ACTIIVTY_ID = 'workbench.action.accountsActivity';
|
||||
|
||||
@@ -14,20 +14,14 @@ import { IInstantiationService, IConstructorSignature0, ServicesAccessor, Brande
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ICompositeControl, IComposite } from 'vs/workbench/common/composite';
|
||||
import { ActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IPathData } from 'vs/platform/windows/common/windows';
|
||||
import { coalesce, firstOrDefault } from 'vs/base/common/arrays';
|
||||
import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { isEqual, dirname } from 'vs/base/common/resources';
|
||||
import { IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { createMemoizer } from 'vs/base/common/decorators';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
|
||||
export const DirtyWorkingCopiesContext = new RawContextKey<boolean>('dirtyWorkingCopies', false);
|
||||
export const ActiveEditorContext = new RawContextKey<string | null>('activeEditor', null);
|
||||
@@ -403,6 +397,11 @@ export interface IEditorInput extends IDisposable {
|
||||
*/
|
||||
getTitle(verbosity?: Verbosity): string | undefined;
|
||||
|
||||
/**
|
||||
* Returns the aria label to be read out by a screen reader.
|
||||
*/
|
||||
getAriaLabel(): string;
|
||||
|
||||
/**
|
||||
* Resolves the input.
|
||||
*/
|
||||
@@ -512,6 +511,10 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
|
||||
return this.getName();
|
||||
}
|
||||
|
||||
getAriaLabel(): string {
|
||||
return this.getTitle(Verbosity.SHORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preferred editor for this input. A list of candidate editors is passed in that whee registered
|
||||
* for the input. This allows subclasses to decide late which editor to use for the input on a case by case basis.
|
||||
@@ -595,164 +598,6 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class TextResourceEditorInput extends EditorInput {
|
||||
|
||||
private static readonly MEMOIZER = createMemoizer();
|
||||
|
||||
constructor(
|
||||
public readonly resource: URI,
|
||||
@IEditorService protected readonly editorService: IEditorService,
|
||||
@IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService,
|
||||
@ITextFileService protected readonly textFileService: ITextFileService,
|
||||
@ILabelService protected readonly labelService: ILabelService,
|
||||
@IFileService protected readonly fileService: IFileService,
|
||||
@IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
protected registerListeners(): void {
|
||||
|
||||
// Clear label memoizer on certain events that have impact
|
||||
this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme)));
|
||||
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme)));
|
||||
this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme)));
|
||||
}
|
||||
|
||||
private onLabelEvent(scheme: string): void {
|
||||
if (scheme === this.resource.scheme) {
|
||||
|
||||
// Clear any cached labels from before
|
||||
TextResourceEditorInput.MEMOIZER.clear();
|
||||
|
||||
// Trigger recompute of label
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return this.basename;
|
||||
}
|
||||
|
||||
@TextResourceEditorInput.MEMOIZER
|
||||
private get basename(): string {
|
||||
return this.labelService.getUriBasenameLabel(this.resource);
|
||||
}
|
||||
|
||||
getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined {
|
||||
switch (verbosity) {
|
||||
case Verbosity.SHORT:
|
||||
return this.shortDescription;
|
||||
case Verbosity.LONG:
|
||||
return this.longDescription;
|
||||
case Verbosity.MEDIUM:
|
||||
default:
|
||||
return this.mediumDescription;
|
||||
}
|
||||
}
|
||||
|
||||
@TextResourceEditorInput.MEMOIZER
|
||||
private get shortDescription(): string {
|
||||
return this.labelService.getUriBasenameLabel(dirname(this.resource));
|
||||
}
|
||||
|
||||
@TextResourceEditorInput.MEMOIZER
|
||||
private get mediumDescription(): string {
|
||||
return this.labelService.getUriLabel(dirname(this.resource), { relative: true });
|
||||
}
|
||||
|
||||
@TextResourceEditorInput.MEMOIZER
|
||||
private get longDescription(): string {
|
||||
return this.labelService.getUriLabel(dirname(this.resource));
|
||||
}
|
||||
|
||||
@TextResourceEditorInput.MEMOIZER
|
||||
private get shortTitle(): string {
|
||||
return this.getName();
|
||||
}
|
||||
|
||||
@TextResourceEditorInput.MEMOIZER
|
||||
private get mediumTitle(): string {
|
||||
return this.labelService.getUriLabel(this.resource, { relative: true });
|
||||
}
|
||||
|
||||
@TextResourceEditorInput.MEMOIZER
|
||||
private get longTitle(): string {
|
||||
return this.labelService.getUriLabel(this.resource);
|
||||
}
|
||||
|
||||
getTitle(verbosity: Verbosity): string {
|
||||
switch (verbosity) {
|
||||
case Verbosity.SHORT:
|
||||
return this.shortTitle;
|
||||
case Verbosity.LONG:
|
||||
return this.longTitle;
|
||||
default:
|
||||
case Verbosity.MEDIUM:
|
||||
return this.mediumTitle;
|
||||
}
|
||||
}
|
||||
|
||||
isUntitled(): boolean {
|
||||
return this.resource.scheme === Schemas.untitled;
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
if (this.isUntitled()) {
|
||||
return false; // untitled is never readonly
|
||||
}
|
||||
|
||||
return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly);
|
||||
}
|
||||
|
||||
isSaving(): boolean {
|
||||
if (this.isUntitled()) {
|
||||
return false; // untitled is never saving automatically
|
||||
}
|
||||
|
||||
if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
return true; // a short auto save is configured, treat this as being saved
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IEditorInput | undefined> {
|
||||
return this.doSave(group, options, false);
|
||||
}
|
||||
|
||||
saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IEditorInput | undefined> {
|
||||
return this.doSave(group, options, true);
|
||||
}
|
||||
|
||||
private async doSave(group: GroupIdentifier, options: ISaveOptions | undefined, saveAs: boolean): Promise<IEditorInput | undefined> {
|
||||
|
||||
// Save / Save As
|
||||
let target: URI | undefined;
|
||||
if (saveAs) {
|
||||
target = await this.textFileService.saveAs(this.resource, undefined, options);
|
||||
} else {
|
||||
target = await this.textFileService.save(this.resource, options);
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
return undefined; // save cancelled
|
||||
}
|
||||
|
||||
if (!isEqual(target, this.resource)) {
|
||||
return this.editorService.createEditorInput({ resource: target });
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
|
||||
await this.textFileService.revert(this.resource, options);
|
||||
}
|
||||
}
|
||||
|
||||
export const enum EncodingMode {
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,10 @@ import { IEditorModel } from 'vs/platform/editor/common/editor';
|
||||
export class DiffEditorModel extends EditorModel {
|
||||
|
||||
protected readonly _originalModel: IEditorModel | null;
|
||||
get originalModel(): IEditorModel | null { return this._originalModel; }
|
||||
|
||||
protected readonly _modifiedModel: IEditorModel | null;
|
||||
get modifiedModel(): IEditorModel | null { return this._modifiedModel; }
|
||||
|
||||
constructor(originalModel: IEditorModel | null, modifiedModel: IEditorModel | null) {
|
||||
super();
|
||||
@@ -22,14 +25,6 @@ export class DiffEditorModel extends EditorModel {
|
||||
this._modifiedModel = modifiedModel;
|
||||
}
|
||||
|
||||
get originalModel(): IEditorModel | null {
|
||||
return this._originalModel;
|
||||
}
|
||||
|
||||
get modifiedModel(): IEditorModel | null {
|
||||
return this._modifiedModel;
|
||||
}
|
||||
|
||||
async load(): Promise<EditorModel> {
|
||||
await Promise.all([
|
||||
this._originalModel?.load(),
|
||||
|
||||
@@ -84,6 +84,9 @@ export class EditorGroup extends Disposable {
|
||||
private readonly _onDidChangeEditorPinned = this._register(new Emitter<EditorInput>());
|
||||
readonly onDidChangeEditorPinned = this._onDidChangeEditorPinned.event;
|
||||
|
||||
private readonly _onDidChangeEditorSticky = this._register(new Emitter<EditorInput>());
|
||||
readonly onDidChangeEditorSticky = this._onDidChangeEditorSticky.event;
|
||||
|
||||
//#endregion
|
||||
|
||||
private _id: GroupIdentifier;
|
||||
@@ -123,6 +126,12 @@ export class EditorGroup extends Disposable {
|
||||
private onConfigurationUpdated(): void {
|
||||
this.editorOpenPositioning = this.configurationService.getValue('workbench.editor.openPositioning');
|
||||
this.focusRecentEditorAfterClose = this.configurationService.getValue('workbench.editor.focusRecentEditorAfterClose');
|
||||
|
||||
if (this.configurationService.getValue('workbench.editor.showTabs') === false) {
|
||||
// Disabling tabs disables sticky editors until we support
|
||||
// an indication of sticky editors when tabs are disabled
|
||||
this.sticky = -1;
|
||||
}
|
||||
}
|
||||
|
||||
get count(): number {
|
||||
@@ -555,6 +564,9 @@ export class EditorGroup extends Disposable {
|
||||
|
||||
// Adjust sticky index
|
||||
this.sticky++;
|
||||
|
||||
// Event
|
||||
this._onDidChangeEditorSticky.fire(editor);
|
||||
}
|
||||
|
||||
unstick(candidate: EditorInput): EditorInput | undefined {
|
||||
@@ -580,6 +592,9 @@ export class EditorGroup extends Disposable {
|
||||
|
||||
// Adjust sticky index
|
||||
this.sticky--;
|
||||
|
||||
// Event
|
||||
this._onDidChangeEditorSticky.fire(editor);
|
||||
}
|
||||
|
||||
isSticky(candidateOrIndex: EditorInput | number): boolean {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITextEditorModel, IModeSupport, TextResourceEditorInput } from 'vs/workbench/common/editor';
|
||||
import { ITextEditorModel, IModeSupport } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IReference } from 'vs/base/common/lifecycle';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
@@ -14,12 +14,13 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
|
||||
|
||||
/**
|
||||
* A read-only text editor input whos contents are made of the provided resource that points to an existing
|
||||
* code editor model.
|
||||
*/
|
||||
export class ResourceEditorInput extends TextResourceEditorInput implements IModeSupport {
|
||||
export class ResourceEditorInput extends AbstractTextResourceEditorInput implements IModeSupport {
|
||||
|
||||
static readonly ID: string = 'workbench.editors.resourceEditorInput';
|
||||
|
||||
@@ -64,6 +65,7 @@ export class ResourceEditorInput extends TextResourceEditorInput implements IMod
|
||||
setDescription(description: string): void {
|
||||
if (this.description !== description) {
|
||||
this.description = description;
|
||||
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
}
|
||||
@@ -87,9 +89,8 @@ export class ResourceEditorInput extends TextResourceEditorInput implements IMod
|
||||
|
||||
const ref = await this.modelReference;
|
||||
|
||||
const model = ref.object;
|
||||
|
||||
// Ensure the resolved model is of expected type
|
||||
const model = ref.object;
|
||||
if (!(model instanceof ResourceEditorModel)) {
|
||||
ref.dispose();
|
||||
this.modelReference = undefined;
|
||||
|
||||
@@ -15,9 +15,13 @@ import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel';
|
||||
export class TextDiffEditorModel extends DiffEditorModel {
|
||||
|
||||
protected readonly _originalModel: BaseTextEditorModel | null;
|
||||
get originalModel(): BaseTextEditorModel | null { return this._originalModel; }
|
||||
|
||||
protected readonly _modifiedModel: BaseTextEditorModel | null;
|
||||
get modifiedModel(): BaseTextEditorModel | null { return this._modifiedModel; }
|
||||
|
||||
private _textDiffEditorModel: IDiffEditorModel | null = null;
|
||||
get textDiffEditorModel(): IDiffEditorModel | null { return this._textDiffEditorModel; }
|
||||
|
||||
constructor(originalModel: BaseTextEditorModel, modifiedModel: BaseTextEditorModel) {
|
||||
super(originalModel, modifiedModel);
|
||||
@@ -28,14 +32,6 @@ export class TextDiffEditorModel extends DiffEditorModel {
|
||||
this.updateTextDiffEditorModel();
|
||||
}
|
||||
|
||||
get originalModel(): BaseTextEditorModel | null {
|
||||
return this._originalModel;
|
||||
}
|
||||
|
||||
get modifiedModel(): BaseTextEditorModel | null {
|
||||
return this._modifiedModel;
|
||||
}
|
||||
|
||||
async load(): Promise<EditorModel> {
|
||||
await super.load();
|
||||
|
||||
@@ -63,10 +59,6 @@ export class TextDiffEditorModel extends DiffEditorModel {
|
||||
}
|
||||
}
|
||||
|
||||
get textDiffEditorModel(): IDiffEditorModel | null {
|
||||
return this._textDiffEditorModel;
|
||||
}
|
||||
|
||||
isResolved(): boolean {
|
||||
return !!this._textDiffEditorModel;
|
||||
}
|
||||
|
||||
177
src/vs/workbench/common/editor/textResourceEditorInput.ts
Normal file
177
src/vs/workbench/common/editor/textResourceEditorInput.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorInput, Verbosity, GroupIdentifier, IEditorInput, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextFileService, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { createMemoizer } from 'vs/base/common/decorators';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { dirname, isEqual } from 'vs/base/common/resources';
|
||||
|
||||
/**
|
||||
* The base class for all editor inputs that open in text editors.
|
||||
*/
|
||||
export abstract class AbstractTextResourceEditorInput extends EditorInput {
|
||||
|
||||
private static readonly MEMOIZER = createMemoizer();
|
||||
|
||||
constructor(
|
||||
public readonly resource: URI,
|
||||
@IEditorService protected readonly editorService: IEditorService,
|
||||
@IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService,
|
||||
@ITextFileService protected readonly textFileService: ITextFileService,
|
||||
@ILabelService protected readonly labelService: ILabelService,
|
||||
@IFileService protected readonly fileService: IFileService,
|
||||
@IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
protected registerListeners(): void {
|
||||
|
||||
// Clear label memoizer on certain events that have impact
|
||||
this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme)));
|
||||
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme)));
|
||||
this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme)));
|
||||
}
|
||||
|
||||
private onLabelEvent(scheme: string): void {
|
||||
if (scheme === this.resource.scheme) {
|
||||
|
||||
// Clear any cached labels from before
|
||||
AbstractTextResourceEditorInput.MEMOIZER.clear();
|
||||
|
||||
// Trigger recompute of label
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return this.basename;
|
||||
}
|
||||
|
||||
@AbstractTextResourceEditorInput.MEMOIZER
|
||||
private get basename(): string {
|
||||
return this.labelService.getUriBasenameLabel(this.resource);
|
||||
}
|
||||
|
||||
getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined {
|
||||
switch (verbosity) {
|
||||
case Verbosity.SHORT:
|
||||
return this.shortDescription;
|
||||
case Verbosity.LONG:
|
||||
return this.longDescription;
|
||||
case Verbosity.MEDIUM:
|
||||
default:
|
||||
return this.mediumDescription;
|
||||
}
|
||||
}
|
||||
|
||||
@AbstractTextResourceEditorInput.MEMOIZER
|
||||
private get shortDescription(): string {
|
||||
return this.labelService.getUriBasenameLabel(dirname(this.resource));
|
||||
}
|
||||
|
||||
@AbstractTextResourceEditorInput.MEMOIZER
|
||||
private get mediumDescription(): string {
|
||||
return this.labelService.getUriLabel(dirname(this.resource), { relative: true });
|
||||
}
|
||||
|
||||
@AbstractTextResourceEditorInput.MEMOIZER
|
||||
private get longDescription(): string {
|
||||
return this.labelService.getUriLabel(dirname(this.resource));
|
||||
}
|
||||
|
||||
@AbstractTextResourceEditorInput.MEMOIZER
|
||||
private get shortTitle(): string {
|
||||
return this.getName();
|
||||
}
|
||||
|
||||
@AbstractTextResourceEditorInput.MEMOIZER
|
||||
private get mediumTitle(): string {
|
||||
return this.labelService.getUriLabel(this.resource, { relative: true });
|
||||
}
|
||||
|
||||
@AbstractTextResourceEditorInput.MEMOIZER
|
||||
private get longTitle(): string {
|
||||
return this.labelService.getUriLabel(this.resource);
|
||||
}
|
||||
|
||||
getTitle(verbosity: Verbosity): string {
|
||||
switch (verbosity) {
|
||||
case Verbosity.SHORT:
|
||||
return this.shortTitle;
|
||||
case Verbosity.LONG:
|
||||
return this.longTitle;
|
||||
default:
|
||||
case Verbosity.MEDIUM:
|
||||
return this.mediumTitle;
|
||||
}
|
||||
}
|
||||
|
||||
isUntitled(): boolean {
|
||||
return this.resource.scheme === Schemas.untitled;
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
if (this.isUntitled()) {
|
||||
return false; // untitled is never readonly
|
||||
}
|
||||
|
||||
return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly);
|
||||
}
|
||||
|
||||
isSaving(): boolean {
|
||||
if (this.isUntitled()) {
|
||||
return false; // untitled is never saving automatically
|
||||
}
|
||||
|
||||
if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
return true; // a short auto save is configured, treat this as being saved
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IEditorInput | undefined> {
|
||||
return this.doSave(group, options, false);
|
||||
}
|
||||
|
||||
saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IEditorInput | undefined> {
|
||||
return this.doSave(group, options, true);
|
||||
}
|
||||
|
||||
private async doSave(group: GroupIdentifier, options: ISaveOptions | undefined, saveAs: boolean): Promise<IEditorInput | undefined> {
|
||||
|
||||
// Save / Save As
|
||||
let target: URI | undefined;
|
||||
if (saveAs) {
|
||||
target = await this.textFileService.saveAs(this.resource, undefined, options);
|
||||
} else {
|
||||
target = await this.textFileService.save(this.resource, options);
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
return undefined; // save cancelled
|
||||
}
|
||||
|
||||
if (!isEqual(target, this.resource)) {
|
||||
return this.editorService.createEditorInput({ resource: target });
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
|
||||
await this.textFileService.revert(this.resource, options);
|
||||
}
|
||||
}
|
||||
@@ -283,18 +283,50 @@ export const PANEL_ACTIVE_TITLE_BORDER = registerColor('panelTitle.activeBorder'
|
||||
hc: contrastBorder
|
||||
}, nls.localize('panelActiveTitleBorder', "Border color for the active panel title. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_DRAG_AND_DROP_BACKGROUND = registerColor('panel.dropBackground', {
|
||||
dark: Color.white.transparent(0.12),
|
||||
light: Color.fromHex('#2677CB').transparent(0.18),
|
||||
hc: Color.white.transparent(0.12)
|
||||
}, nls.localize('panelDragAndDropBackground', "Drag and drop feedback color for the panel title items. The color should have transparency so that the panel entries can still shine through. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_INPUT_BORDER = registerColor('panelInput.border', {
|
||||
dark: null,
|
||||
light: Color.fromHex('#ddd'),
|
||||
hc: null
|
||||
}, nls.localize('panelInputBorder', "Input box border for inputs in the panel."));
|
||||
|
||||
export const PANEL_DRAG_AND_DROP_BORDER = registerColor('panel.dropBorder', {
|
||||
dark: PANEL_ACTIVE_TITLE_FOREGROUND,
|
||||
light: PANEL_ACTIVE_TITLE_FOREGROUND,
|
||||
hc: PANEL_ACTIVE_TITLE_FOREGROUND,
|
||||
}, nls.localize('panelDragAndDropBorder', "Drag and drop feedback color for the panel titles. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
|
||||
export const PANEL_SECTION_DRAG_AND_DROP_BACKGROUND = registerColor('panelSection.dropBackground', {
|
||||
dark: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
light: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
hc: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
}, nls.localize('panelSectionDragAndDropBackground', "Drag and drop feedback color for the panel sections. The color should have transparency so that the panel sections can still shine through. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_SECTION_HEADER_BACKGROUND = registerColor('panelSectionHeader.background', {
|
||||
dark: Color.fromHex('#808080').transparent(0.2),
|
||||
light: Color.fromHex('#808080').transparent(0.2),
|
||||
hc: null
|
||||
}, nls.localize('panelSectionHeaderBackground', "Panel section header background color. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_SECTION_HEADER_FOREGROUND = registerColor('panelSectionHeader.foreground', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('panelSectionHeaderForeground', "Panel section header foreground color. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_SECTION_HEADER_BORDER = registerColor('panelSectionHeader.border', {
|
||||
dark: contrastBorder,
|
||||
light: contrastBorder,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('panelSectionHeaderBorder', "Panel section header border color. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_SECTION_BORDER = registerColor('panelSection.border', {
|
||||
dark: PANEL_BORDER,
|
||||
light: PANEL_BORDER,
|
||||
hc: PANEL_BORDER
|
||||
}, nls.localize('panelSectionBorder', "Panel section border color. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
|
||||
// < --- Status --- >
|
||||
|
||||
export const STATUS_BAR_FOREGROUND = registerColor('statusBar.foreground', {
|
||||
@@ -407,11 +439,11 @@ export const ACTIVITY_BAR_ACTIVE_BACKGROUND = registerColor('activityBar.activeB
|
||||
hc: null
|
||||
}, nls.localize('activityBarActiveBackground', "Activity bar background color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND = registerColor('activityBar.dropBackground', {
|
||||
dark: Color.white.transparent(0.12),
|
||||
light: Color.white.transparent(0.12),
|
||||
hc: Color.white.transparent(0.12),
|
||||
}, nls.localize('activityBarDragAndDropBackground', "Drag and drop feedback color for the activity bar items. The color should have transparency so that the activity bar entries can still shine through. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
export const ACTIVITY_BAR_DRAG_AND_DROP_BORDER = registerColor('activityBar.dropBorder', {
|
||||
dark: ACTIVITY_BAR_FOREGROUND,
|
||||
light: ACTIVITY_BAR_FOREGROUND,
|
||||
hc: ACTIVITY_BAR_FOREGROUND,
|
||||
}, nls.localize('activityBarDragAndDropBorder', "Drag and drop feedback color for the activity bar items. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_BADGE_BACKGROUND = registerColor('activityBarBadge.background', {
|
||||
dark: '#007ACC',
|
||||
@@ -480,9 +512,9 @@ export const SIDE_BAR_TITLE_FOREGROUND = registerColor('sideBarTitle.foreground'
|
||||
}, nls.localize('sideBarTitleForeground', "Side bar title foreground color. The side bar is the container for views like explorer and search."));
|
||||
|
||||
export const SIDE_BAR_DRAG_AND_DROP_BACKGROUND = registerColor('sideBar.dropBackground', {
|
||||
dark: Color.white.transparent(0.12),
|
||||
light: Color.black.transparent(0.1),
|
||||
hc: Color.white.transparent(0.3),
|
||||
dark: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
light: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
hc: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
}, nls.localize('sideBarDragAndDropBackground', "Drag and drop feedback color for the side bar sections. The color should have transparency so that the side bar sections can still shine through. The side bar is the container for views like explorer and search."));
|
||||
|
||||
export const SIDE_BAR_SECTION_HEADER_BACKGROUND = registerColor('sideBarSectionHeader.background', {
|
||||
@@ -510,31 +542,31 @@ export const TITLE_BAR_ACTIVE_FOREGROUND = registerColor('titleBar.activeForegro
|
||||
dark: '#CCCCCC',
|
||||
light: '#333333',
|
||||
hc: '#FFFFFF'
|
||||
}, nls.localize('titleBarActiveForeground', "Title bar foreground when the window is active. Note that this color is currently only supported on macOS."));
|
||||
}, nls.localize('titleBarActiveForeground', "Title bar foreground when the window is active."));
|
||||
|
||||
export const TITLE_BAR_INACTIVE_FOREGROUND = registerColor('titleBar.inactiveForeground', {
|
||||
dark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, 0.6),
|
||||
light: transparent(TITLE_BAR_ACTIVE_FOREGROUND, 0.6),
|
||||
hc: null
|
||||
}, nls.localize('titleBarInactiveForeground', "Title bar foreground when the window is inactive. Note that this color is currently only supported on macOS."));
|
||||
}, nls.localize('titleBarInactiveForeground', "Title bar foreground when the window is inactive."));
|
||||
|
||||
export const TITLE_BAR_ACTIVE_BACKGROUND = registerColor('titleBar.activeBackground', {
|
||||
dark: '#3C3C3C',
|
||||
light: '#DDDDDD',
|
||||
hc: '#000000'
|
||||
}, nls.localize('titleBarActiveBackground', "Title bar background when the window is active. Note that this color is currently only supported on macOS."));
|
||||
}, nls.localize('titleBarActiveBackground', "Title bar background when the window is active."));
|
||||
|
||||
export const TITLE_BAR_INACTIVE_BACKGROUND = registerColor('titleBar.inactiveBackground', {
|
||||
dark: transparent(TITLE_BAR_ACTIVE_BACKGROUND, 0.6),
|
||||
light: transparent(TITLE_BAR_ACTIVE_BACKGROUND, 0.6),
|
||||
hc: null
|
||||
}, nls.localize('titleBarInactiveBackground', "Title bar background when the window is inactive. Note that this color is currently only supported on macOS."));
|
||||
}, nls.localize('titleBarInactiveBackground', "Title bar background when the window is inactive."));
|
||||
|
||||
export const TITLE_BAR_BORDER = registerColor('titleBar.border', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('titleBarBorder', "Title bar border color. Note that this color is currently only supported on macOS."));
|
||||
}, nls.localize('titleBarBorder', "Title bar border color."));
|
||||
|
||||
// < --- Menubar --- >
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { SetMap } from 'vs/base/common/collections';
|
||||
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
||||
|
||||
export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test';
|
||||
|
||||
@@ -49,8 +50,6 @@ export interface IViewContainerDescriptor {
|
||||
|
||||
readonly alwaysUseContainerInfo?: boolean;
|
||||
|
||||
readonly order?: number;
|
||||
|
||||
readonly focusCommand?: { id: string, keybindings?: IKeybindings };
|
||||
|
||||
readonly viewOrderDelegate?: ViewOrderDelegate;
|
||||
@@ -60,6 +59,8 @@ export interface IViewContainerDescriptor {
|
||||
readonly extensionId?: ExtensionIdentifier;
|
||||
|
||||
readonly rejectAddedViews?: boolean;
|
||||
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export interface IViewContainersRegistry {
|
||||
@@ -211,6 +212,8 @@ export interface IViewDescriptor {
|
||||
|
||||
readonly containerIcon?: string | URI;
|
||||
|
||||
readonly containerTitle?: string;
|
||||
|
||||
// Applies only to newly created views
|
||||
readonly hideByDefault?: boolean;
|
||||
|
||||
@@ -234,6 +237,11 @@ export interface IAddedViewDescriptorRef extends IViewDescriptorRef {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface IAddedViewDescriptorState {
|
||||
viewDescriptor: IViewDescriptor,
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
export interface IViewContainerModel {
|
||||
|
||||
readonly title: string;
|
||||
@@ -507,7 +515,7 @@ export interface IViewDescriptorService {
|
||||
getViewContainerModel(viewContainer: ViewContainer): IViewContainerModel;
|
||||
|
||||
readonly onDidChangeContainerLocation: Event<{ viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation }>;
|
||||
moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation): void;
|
||||
moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation, order?: number): void;
|
||||
|
||||
// Views
|
||||
getViewDescriptorById(id: string): IViewDescriptor | null;
|
||||
@@ -632,6 +640,8 @@ export interface ITreeItem {
|
||||
command?: Command;
|
||||
|
||||
children?: ITreeItem[];
|
||||
|
||||
accessibilityInformation?: IAccessibilityInformation;
|
||||
}
|
||||
|
||||
export interface ITreeViewDataProvider {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-browser/backupTracker';
|
||||
import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker';
|
||||
|
||||
// Register Backup Tracker
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeBackupTracker, LifecyclePhase.Starting);
|
||||
@@ -14,7 +14,7 @@ import Severity from 'vs/base/common/severity';
|
||||
import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { HotExitConfiguration } from 'vs/platform/files/common/files';
|
||||
import { IElectronService } from 'vs/platform/electron/node/electron';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -13,7 +13,7 @@ import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { DefaultEndOfLine } from 'vs/editor/common/model';
|
||||
import { hashPath } from 'vs/workbench/services/backup/node/backupFileService';
|
||||
import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-browser/backupTracker';
|
||||
import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker';
|
||||
import { workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
|
||||
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as pfs from 'vs/base/node/pfs';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { hashPath } from 'vs/workbench/services/backup/node/backupFileService';
|
||||
import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-browser/backupTracker';
|
||||
import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-sandbox/backupTracker';
|
||||
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
|
||||
@@ -34,7 +34,7 @@ import { HotExitConfiguration } from 'vs/platform/files/common/files';
|
||||
import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IFileDialogService, ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace';
|
||||
import { IElectronService } from 'vs/platform/electron/node/electron';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker';
|
||||
import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
@@ -70,10 +70,10 @@
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .bulk-edit-panel .monaco-tl-contents.category .uri-icon,
|
||||
.hc-black .monaco-workbench .bulk-edit-panel .monaco-tl-contents.category .uri-icon,
|
||||
.vs-dark .monaco-workbench .bulk-edit-panel .monaco-tl-contents.textedit .uri-icon,
|
||||
.hc-black .monaco-workbench .bulk-edit-panel .monaco-tl-contents.textedit .uri-icon
|
||||
.monaco-workbench.vs-dark .bulk-edit-panel .monaco-tl-contents.category .uri-icon,
|
||||
.monaco-workbench.hc-black .bulk-edit-panel .monaco-tl-contents.category .uri-icon,
|
||||
.monaco-workbench.vs-dark .bulk-edit-panel .monaco-tl-contents.textedit .uri-icon,
|
||||
.monaco-workbench.hc-black .bulk-edit-panel .monaco-tl-contents.textedit .uri-icon
|
||||
{
|
||||
background-image: var(--background-dark);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { ConflictDetector } from 'vs/workbench/services/bulkEdit/browser/conflicts';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { localize } from 'vs/nls';
|
||||
import { extUri } from 'vs/base/common/resources';
|
||||
|
||||
export class CheckedStates<T extends object> {
|
||||
|
||||
@@ -209,13 +210,13 @@ export class BulkFileOperations {
|
||||
}
|
||||
|
||||
const insert = (uri: URI, map: Map<string, BulkFileOperation>) => {
|
||||
let key = uri.toString();
|
||||
let key = extUri.getComparisonKey(uri, true);
|
||||
let operation = map.get(key);
|
||||
|
||||
// rename
|
||||
if (!operation && newToOldUri.has(uri)) {
|
||||
uri = newToOldUri.get(uri)!;
|
||||
key = uri.toString();
|
||||
key = extUri.getComparisonKey(uri, true);
|
||||
operation = map.get(key);
|
||||
}
|
||||
|
||||
|
||||
@@ -350,6 +350,10 @@ export abstract class SimpleFindReplaceWidget extends Widget {
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._findInput.focus();
|
||||
}
|
||||
|
||||
public show(initialInput?: string): void {
|
||||
if (initialInput && !this._isVisible) {
|
||||
this._findInput.setValue(initialInput);
|
||||
@@ -361,6 +365,8 @@ export abstract class SimpleFindReplaceWidget extends Widget {
|
||||
dom.addClass(this._domNode, 'visible');
|
||||
dom.addClass(this._domNode, 'visible-transition');
|
||||
this._domNode.setAttribute('aria-hidden', 'false');
|
||||
|
||||
this.focus();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,17 +14,18 @@ import { AbstractGotoSymbolQuickAccessProvider, IGotoSymbolQuickPickItem } from
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkbenchEditorConfiguration, IEditorPane } from 'vs/workbench/common/editor';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { prepareQuery } from 'vs/base/common/fuzzyScorer';
|
||||
import { SymbolKind } from 'vs/editor/common/modes';
|
||||
import { fuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
|
||||
export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider {
|
||||
|
||||
@@ -41,6 +42,13 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
|
||||
|
||||
//#region DocumentSymbols (text editor required)
|
||||
|
||||
protected provideWithTextEditor(editor: IEditor, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
|
||||
if (this.canPickFromTableOfContents()) {
|
||||
return this.doGetTableOfContentsPicks(picker);
|
||||
}
|
||||
return super.provideWithTextEditor(editor, picker, token);
|
||||
}
|
||||
|
||||
private get configuration() {
|
||||
const editorConfig = this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench.editor;
|
||||
|
||||
@@ -106,12 +114,21 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
|
||||
//#endregion
|
||||
|
||||
protected provideWithoutTextEditor(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
|
||||
const pane = this.editorService.activeEditorPane;
|
||||
if (!pane || !TableOfContentsProviderRegistry.has(pane.getId())) {
|
||||
//
|
||||
return super.provideWithoutTextEditor(picker);
|
||||
if (this.canPickFromTableOfContents()) {
|
||||
return this.doGetTableOfContentsPicks(picker);
|
||||
}
|
||||
return super.provideWithoutTextEditor(picker);
|
||||
}
|
||||
|
||||
private canPickFromTableOfContents(): boolean {
|
||||
return this.editorService.activeEditorPane ? TableOfContentsProviderRegistry.has(this.editorService.activeEditorPane.getId()) : false;
|
||||
}
|
||||
|
||||
private doGetTableOfContentsPicks(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
|
||||
const pane = this.editorService.activeEditorPane;
|
||||
if (!pane) {
|
||||
return Disposable.None;
|
||||
}
|
||||
const provider = TableOfContentsProviderRegistry.get(pane.getId())!;
|
||||
const cts = new CancellationTokenSource();
|
||||
|
||||
@@ -133,7 +150,8 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
|
||||
kind: SymbolKind.File,
|
||||
index: idx,
|
||||
score: 0,
|
||||
label: entry.label,
|
||||
label: entry.icon ? `$(${entry.icon.id}) ${entry.label}` : entry.label,
|
||||
ariaLabel: entry.detail ? `${entry.label}, ${entry.detail}` : entry.label,
|
||||
detail: entry.detail,
|
||||
description: entry.description,
|
||||
};
|
||||
@@ -142,7 +160,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
|
||||
disposables.add(picker.onDidAccept(() => {
|
||||
picker.hide();
|
||||
const [entry] = picker.selectedItems;
|
||||
entries[entry.index]?.reveal();
|
||||
entries[entry.index]?.pick();
|
||||
}));
|
||||
|
||||
const updatePickerItems = () => {
|
||||
@@ -175,14 +193,11 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
|
||||
let ignoreFirstActiveEvent = true;
|
||||
disposables.add(picker.onDidChangeActive(() => {
|
||||
const [entry] = picker.activeItems;
|
||||
|
||||
if (entry && entries[entry.index]) {
|
||||
if (ignoreFirstActiveEvent) {
|
||||
ignoreFirstActiveEvent = false;
|
||||
return;
|
||||
if (!ignoreFirstActiveEvent) {
|
||||
entries[entry.index]?.preview();
|
||||
}
|
||||
|
||||
entries[entry.index]?.reveal();
|
||||
ignoreFirstActiveEvent = false;
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -206,36 +221,38 @@ Registry.as<IQuickAccessRegistry>(QuickaccessExtensions.Quickaccess).registerQui
|
||||
]
|
||||
});
|
||||
|
||||
export class GotoSymbolAction extends Action {
|
||||
registerAction2(class GotoSymbolAction extends Action2 {
|
||||
|
||||
static readonly ID = 'workbench.action.gotoSymbol';
|
||||
static readonly LABEL = localize('gotoSymbol', "Go to Symbol in Editor...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService
|
||||
) {
|
||||
super(id, label);
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.gotoSymbol',
|
||||
title: {
|
||||
value: localize('gotoSymbol', "Go to Symbol in Editor..."),
|
||||
original: 'Go to Symbol in Editor...'
|
||||
},
|
||||
f1: true,
|
||||
keybinding: {
|
||||
when: undefined,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
this.quickInputService.quickAccess.show(GotoSymbolQuickAccessProvider.PREFIX);
|
||||
run(accessor: ServicesAccessor) {
|
||||
accessor.get(IQuickInputService).quickAccess.show(GotoSymbolQuickAccessProvider.PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction(SyncActionDescriptor.from(GotoSymbolAction, {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O
|
||||
}), 'Go to Symbol in Editor...');
|
||||
|
||||
});
|
||||
|
||||
//#region toc definition and logic
|
||||
|
||||
export interface ITableOfContentsEntry {
|
||||
icon?: ThemeIcon;
|
||||
label: string;
|
||||
detail?: string;
|
||||
description?: string;
|
||||
reveal(): any;
|
||||
pick(): any;
|
||||
preview(): any;
|
||||
}
|
||||
|
||||
export interface ITableOfContentsProvider<T extends IEditorPane = IEditorPane> {
|
||||
|
||||
@@ -311,7 +311,7 @@ function getSuggestEnabledInputOptions(ariaLabel?: string): IEditorOptions {
|
||||
roundedSelection: false,
|
||||
renderIndentGuides: false,
|
||||
cursorWidth: 1,
|
||||
fontFamily: ' -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif',
|
||||
fontFamily: ' system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif',
|
||||
ariaLabel: ariaLabel || '',
|
||||
|
||||
snippetSuggestions: 'none',
|
||||
|
||||
@@ -47,31 +47,31 @@ export class ToggleColumnSelectionAction extends Action {
|
||||
if (!codeEditor || codeEditor !== this._getCodeEditor() || oldValue === newValue || !codeEditor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const cursors = codeEditor._getCursors();
|
||||
const viewModel = codeEditor._getViewModel();
|
||||
if (codeEditor.getOption(EditorOption.columnSelection)) {
|
||||
const selection = codeEditor.getSelection();
|
||||
const modelSelectionStart = new Position(selection.selectionStartLineNumber, selection.selectionStartColumn);
|
||||
const viewSelectionStart = cursors.context.convertModelPositionToViewPosition(modelSelectionStart);
|
||||
const viewSelectionStart = viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelSelectionStart);
|
||||
const modelPosition = new Position(selection.positionLineNumber, selection.positionColumn);
|
||||
const viewPosition = cursors.context.convertModelPositionToViewPosition(modelPosition);
|
||||
const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
|
||||
|
||||
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursors, {
|
||||
CoreNavigationCommands.MoveTo.runCoreEditorCommand(viewModel, {
|
||||
position: modelSelectionStart,
|
||||
viewPosition: viewSelectionStart
|
||||
});
|
||||
const visibleColumn = CursorColumns.visibleColumnFromColumn2(cursors.context.config, cursors.context.viewModel, viewPosition);
|
||||
CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(cursors, {
|
||||
const visibleColumn = CursorColumns.visibleColumnFromColumn2(viewModel.cursorConfig, viewModel, viewPosition);
|
||||
CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(viewModel, {
|
||||
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);
|
||||
const columnSelectData = viewModel.getCursorColumnSelectData();
|
||||
const fromViewColumn = CursorColumns.columnFromVisibleColumn2(viewModel.cursorConfig, viewModel, columnSelectData.fromViewLineNumber, columnSelectData.fromViewVisualColumn);
|
||||
const fromPosition = viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(columnSelectData.fromViewLineNumber, fromViewColumn));
|
||||
const toViewColumn = CursorColumns.columnFromVisibleColumn2(viewModel.cursorConfig, viewModel, columnSelectData.toViewLineNumber, columnSelectData.toViewVisualColumn);
|
||||
const toPosition = viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(columnSelectData.toViewLineNumber, toViewColumn));
|
||||
|
||||
codeEditor.setSelection(new Selection(fromPosition.lineNumber, fromPosition.column, toPosition.lineNumber, toPosition.column));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,4 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import './inputClipboardActions';
|
||||
import './sleepResumeRepaintMinimap';
|
||||
import './selectionClipboard';
|
||||
import './startDebugTextMate';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user