mirror of
https://github.com/coder/code-server.git
synced 2026-05-07 12:57:26 +02:00
chore(vscode): update to 1.53.2
These conflicts will be resolved in the following commits. We do it this way so that PR review is possible.
This commit is contained in:
@@ -17,6 +17,7 @@ import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEdito
|
||||
// --- mainThread participants
|
||||
import './mainThreadBulkEdits';
|
||||
import './mainThreadCodeInsets';
|
||||
import './mainThreadCLICommands';
|
||||
import './mainThreadClipboard';
|
||||
import './mainThreadCommands';
|
||||
import './mainThreadConfiguration';
|
||||
@@ -30,6 +31,7 @@ import './mainThreadDocuments';
|
||||
import './mainThreadDocumentsAndEditors';
|
||||
import './mainThreadEditor';
|
||||
import './mainThreadEditors';
|
||||
import './mainThreadEditorTabs';
|
||||
import './mainThreadErrors';
|
||||
import './mainThreadExtensionService';
|
||||
import './mainThreadFileSystem';
|
||||
@@ -54,6 +56,7 @@ import './mainThreadTheming';
|
||||
import './mainThreadTreeViews';
|
||||
import './mainThreadDownloadService';
|
||||
import './mainThreadUrls';
|
||||
import './mainThreadUriOpeners';
|
||||
import './mainThreadWindow';
|
||||
import './mainThreadWebviewManager';
|
||||
import './mainThreadWorkspace';
|
||||
@@ -65,6 +68,7 @@ import './mainThreadTunnelService';
|
||||
import './mainThreadAuthentication';
|
||||
import './mainThreadTimeline';
|
||||
import './mainThreadTesting';
|
||||
import './mainThreadSecretState';
|
||||
import 'vs/workbench/api/common/apiCommands';
|
||||
|
||||
export class ExtensionPoints implements IWorkbenchContribution {
|
||||
|
||||
@@ -18,9 +18,6 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { IEncryptionService } from 'vs/workbench/services/encryption/common/encryptionService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
|
||||
|
||||
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'github.codespaces'];
|
||||
|
||||
@@ -225,10 +222,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@ICredentialsService private readonly credentialsService: ICredentialsService,
|
||||
@IEncryptionService private readonly encryptionService: IEncryptionService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
@IExtensionService private readonly extensionService: IExtensionService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication);
|
||||
@@ -250,10 +244,6 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
this._register(this.authenticationService.onDidChangeDeclaredProviders(e => {
|
||||
this._proxy.$setProviders(e);
|
||||
}));
|
||||
|
||||
this._register(this.credentialsService.onDidChangePassword(_ => {
|
||||
this._proxy.$onDidChangePassword();
|
||||
}));
|
||||
}
|
||||
|
||||
$getProviderIds(): Promise<string[]> {
|
||||
@@ -416,7 +406,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
|
||||
const remoteConnection = this.remoteAgentService.getConnection();
|
||||
const isVSO = remoteConnection !== null
|
||||
? remoteConnection.remoteAuthority.startsWith('vsonline')
|
||||
? remoteConnection.remoteAuthority.startsWith('vsonline') || remoteConnection.remoteAuthority.startsWith('codespaces')
|
||||
: isWeb;
|
||||
|
||||
if (isVSO && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) {
|
||||
@@ -466,46 +456,4 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
||||
this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
|
||||
}
|
||||
|
||||
private getFullKey(extensionId: string): string {
|
||||
return `${this.productService.urlProtocol}${extensionId}`;
|
||||
}
|
||||
|
||||
async $getPassword(extensionId: string, key: string): Promise<string | undefined> {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
const password = await this.credentialsService.getPassword(fullKey, key);
|
||||
const decrypted = password && await this.encryptionService.decrypt(password);
|
||||
|
||||
if (decrypted) {
|
||||
try {
|
||||
const value = JSON.parse(decrypted);
|
||||
if (value.extensionId === extensionId) {
|
||||
return value.content;
|
||||
}
|
||||
} catch (_) {
|
||||
throw new Error('Cannot get password');
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async $setPassword(extensionId: string, key: string, value: string): Promise<void> {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
const toEncrypt = JSON.stringify({
|
||||
extensionId,
|
||||
content: value
|
||||
});
|
||||
const encrypted = await this.encryptionService.encrypt(toEncrypt);
|
||||
return this.credentialsService.setPassword(fullKey, key, encrypted);
|
||||
}
|
||||
|
||||
async $deletePassword(extensionId: string, key: string): Promise<void> {
|
||||
try {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
await this.credentialsService.deletePassword(fullKey, key);
|
||||
} catch (_) {
|
||||
throw new Error('Cannot delete password');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,29 +3,9 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { IExtHostContext, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
|
||||
function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
|
||||
if (!data?.edits) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: ResourceEdit[] = [];
|
||||
for (let edit of revive<IWorkspaceEditDto>(data).edits) {
|
||||
if (edit._type === WorkspaceEditType.File) {
|
||||
result.push(new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata));
|
||||
} else if (edit._type === WorkspaceEditType.Text) {
|
||||
result.push(new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata));
|
||||
} else if (edit._type === WorkspaceEditType.Cell) {
|
||||
result.push(new ResourceNotebookCellEdit(edit.resource, edit.edit, edit.notebookVersionId, edit.metadata));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { IExtHostContext, IWorkspaceEditDto, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadBulkEdits)
|
||||
export class MainThreadBulkEdits implements MainThreadBulkEditsShape {
|
||||
@@ -39,12 +19,6 @@ export class MainThreadBulkEdits implements MainThreadBulkEditsShape {
|
||||
|
||||
$tryApplyWorkspaceEdit(dto: IWorkspaceEditDto, undoRedoGroupId?: number): Promise<boolean> {
|
||||
const edits = reviveWorkspaceEditDto2(dto);
|
||||
return this._bulkEditService.apply(edits, {
|
||||
// having a undoRedoGroupId means that this is a nested workspace edit,
|
||||
// e.g one from a onWill-handler and for now we need to forcefully suppress
|
||||
// refactor previewing, see: https://github.com/microsoft/vscode/issues/111873#issuecomment-738739852
|
||||
undoRedoGroupId,
|
||||
suppressPreview: typeof undoRedoGroupId === 'number' ? true : undefined
|
||||
}).then(() => true, _err => false);
|
||||
return this._bulkEditService.apply(edits, { undoRedoGroupId }).then(() => true, _err => false);
|
||||
}
|
||||
}
|
||||
|
||||
108
lib/vscode/src/vs/workbench/api/browser/mainThreadCLICommands.ts
Normal file
108
lib/vscode/src/vs/workbench/api/browser/mainThreadCLICommands.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CLIOutput, IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService';
|
||||
import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { canExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { IExtensionManifest } from 'vs/workbench/workbench.web.api';
|
||||
|
||||
|
||||
// this class contains the commands that the CLI server is reying on
|
||||
|
||||
CommandsRegistry.registerCommand('_remoteCLI.openExternal', function (accessor: ServicesAccessor, uri: UriComponents, options: { allowTunneling?: boolean }) {
|
||||
// TODO: discuss martin, ben where to put this
|
||||
const openerService = accessor.get(IOpenerService);
|
||||
openerService.open(URI.revive(uri), { openExternal: true, allowTunneling: options?.allowTunneling === true });
|
||||
});
|
||||
|
||||
interface ManageExtensionsArgs {
|
||||
list?: { showVersions?: boolean, category?: string; };
|
||||
install?: (string | URI)[];
|
||||
uninstall?: string[];
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_remoteCLI.manageExtensions', async function (accessor: ServicesAccessor, args: ManageExtensionsArgs) {
|
||||
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const extensionManagementServerService = accessor.get(IExtensionManagementServerService);
|
||||
const remoteExtensionManagementService = extensionManagementServerService.remoteExtensionManagementServer?.extensionManagementService;
|
||||
if (!remoteExtensionManagementService) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cliService = instantiationService.createChild(new ServiceCollection([IExtensionManagementService, remoteExtensionManagementService])).createInstance(RemoteExtensionCLIManagementService);
|
||||
|
||||
const lines: string[] = [];
|
||||
const output = { log: lines.push.bind(lines), error: lines.push.bind(lines) };
|
||||
|
||||
if (args.list) {
|
||||
await cliService.listExtensions(!!args.list.showVersions, args.list.category, output);
|
||||
} else {
|
||||
const revive = (inputs: (string | UriComponents)[]) => inputs.map(input => isString(input) ? input : URI.revive(input));
|
||||
if (Array.isArray(args.install) && args.install.length) {
|
||||
try {
|
||||
await cliService.installExtensions(revive(args.install), [], true, !!args.force, output);
|
||||
} catch (e) {
|
||||
lines.push(e.message);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(args.uninstall) && args.uninstall.length) {
|
||||
try {
|
||||
await cliService.uninstallExtensions(revive(args.uninstall), !!args.force, output);
|
||||
} catch (e) {
|
||||
lines.push(e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
});
|
||||
|
||||
class RemoteExtensionCLIManagementService extends ExtensionManagementCLIService {
|
||||
|
||||
private _location: string | undefined;
|
||||
|
||||
constructor(
|
||||
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
|
||||
@ILocalizationsService localizationsService: ILocalizationsService,
|
||||
@ILabelService labelService: ILabelService,
|
||||
@IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService
|
||||
) {
|
||||
super(extensionManagementService, extensionGalleryService, localizationsService);
|
||||
|
||||
const remoteAuthority = envService.remoteAuthority;
|
||||
this._location = remoteAuthority ? labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) : undefined;
|
||||
}
|
||||
|
||||
protected get location(): string | undefined {
|
||||
return this._location;
|
||||
}
|
||||
|
||||
protected validateExtensionKind(manifest: IExtensionManifest, output: CLIOutput): boolean {
|
||||
if (!canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) {
|
||||
output.log(localize('cannot be installed', "Cannot install '{0}' because this extension has defined that it cannot run on the remote server.", getExtensionId(manifest.publisher, manifest.name)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -10,22 +10,18 @@ import { IRemoteConsoleLog, log } from 'vs/base/common/console';
|
||||
import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil';
|
||||
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadConsole)
|
||||
export class MainThreadConsole implements MainThreadConsoleShape {
|
||||
|
||||
private readonly _isExtensionDevHost: boolean;
|
||||
private readonly _isExtensionDevTestFromCli: boolean;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
_extHostContext: IExtHostContext,
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,
|
||||
) {
|
||||
const devOpts = parseExtensionDevOptions(this._environmentService);
|
||||
this._isExtensionDevHost = devOpts.isExtensionDevHost;
|
||||
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
|
||||
}
|
||||
|
||||
@@ -43,10 +39,5 @@ export class MainThreadConsole implements MainThreadConsoleShape {
|
||||
if (this._isExtensionDevTestFromCli) {
|
||||
logRemoteEntry(this._logService, entry);
|
||||
}
|
||||
|
||||
// Broadcast to other windows if we are in development mode
|
||||
else if (this._environmentService.debugExtensionHost.debugId && (!this._environmentService.isBuilt || this._isExtensionDevHost)) {
|
||||
this._extensionHostDebugService.logToSession(this._environmentService.debugExtensionHost.debugId, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { multibyteAwareBtoa } from 'vs/base/browser/dom';
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, IDisposable, IReference } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { isEqual, isEqualOrParent, toLocalResource } from 'vs/base/common/resources';
|
||||
import { multibyteAwareBtoa } from 'vs/base/browser/dom';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -95,10 +95,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
for (const disposable of this._editorProviders.values()) {
|
||||
disposable.dispose();
|
||||
}
|
||||
|
||||
dispose(this._editorProviders.values());
|
||||
this._editorProviders.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
return {
|
||||
id: sessionID,
|
||||
type: session.configuration.type,
|
||||
name: session.configuration.name,
|
||||
name: session.name,
|
||||
folderUri: session.root ? session.root.uri : undefined,
|
||||
configuration: session.configuration
|
||||
};
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtHostContext, IExtHostEditorTabsShape, IExtHostContext, MainContext, IEditorTabDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { Verbosity } from 'vs/workbench/common/editor';
|
||||
import { GroupChangeKind, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
|
||||
export interface ITabInfo {
|
||||
name: string;
|
||||
resource: URI;
|
||||
}
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadEditorTabs)
|
||||
export class MainThreadEditorTabs {
|
||||
|
||||
private static _GroupEventFilter = new Set([GroupChangeKind.EDITOR_CLOSE, GroupChangeKind.EDITOR_OPEN]);
|
||||
|
||||
private readonly _dispoables = new DisposableStore();
|
||||
private readonly _groups = new Map<IEditorGroup, IDisposable>();
|
||||
private readonly _proxy: IExtHostEditorTabsShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
|
||||
) {
|
||||
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditorTabs);
|
||||
|
||||
this._editorGroupsService.groups.forEach(this._subscribeToGroup, this);
|
||||
this._dispoables.add(_editorGroupsService.onDidAddGroup(this._subscribeToGroup, this));
|
||||
this._dispoables.add(_editorGroupsService.onDidRemoveGroup(e => {
|
||||
const subscription = this._groups.get(e);
|
||||
if (subscription) {
|
||||
subscription.dispose();
|
||||
this._groups.delete(e);
|
||||
this._pushEditorTabs();
|
||||
}
|
||||
}));
|
||||
this._pushEditorTabs();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._groups.values());
|
||||
this._dispoables.dispose();
|
||||
}
|
||||
|
||||
private _subscribeToGroup(group: IEditorGroup) {
|
||||
this._groups.get(group)?.dispose();
|
||||
const listener = group.onDidGroupChange(e => {
|
||||
if (MainThreadEditorTabs._GroupEventFilter.has(e.kind)) {
|
||||
this._pushEditorTabs();
|
||||
}
|
||||
});
|
||||
this._groups.set(group, listener);
|
||||
}
|
||||
|
||||
private _pushEditorTabs(): void {
|
||||
const tabs: IEditorTabDto[] = [];
|
||||
for (const group of this._editorGroupsService.groups) {
|
||||
for (const editor of group.editors) {
|
||||
if (editor.isDisposed() || !editor.resource) {
|
||||
continue;
|
||||
}
|
||||
tabs.push({
|
||||
group: group.id,
|
||||
name: editor.getTitle(Verbosity.SHORT) ?? '',
|
||||
resource: editor.resource
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._proxy.$acceptEditorTabs(tabs);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
|
||||
|
||||
function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
|
||||
export function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
|
||||
if (!data?.edits) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { SerializedError } from 'vs/base/common/errors';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { IExtHostContext, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtensionService, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionService, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -19,29 +19,23 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadExtensionService)
|
||||
export class MainThreadExtensionService implements MainThreadExtensionServiceShape {
|
||||
|
||||
private readonly _extensionService: IExtensionService;
|
||||
private readonly _notificationService: INotificationService;
|
||||
private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService;
|
||||
private readonly _hostService: IHostService;
|
||||
private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService;
|
||||
private readonly _extensionHostKind: ExtensionHostKind;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IHostService hostService: IHostService,
|
||||
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IHostService private readonly _hostService: IHostService,
|
||||
@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@ITimerService private readonly _timerService: ITimerService,
|
||||
) {
|
||||
this._extensionService = extensionService;
|
||||
this._notificationService = notificationService;
|
||||
this._extensionsWorkbenchService = extensionsWorkbenchService;
|
||||
this._hostService = hostService;
|
||||
this._extensionEnablementService = extensionEnablementService;
|
||||
this._extensionHostKind = extHostContext.extensionHostKind;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -131,4 +125,14 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
|
||||
async $onExtensionHostExit(code: number): Promise<void> {
|
||||
this._extensionService._onExtensionHostExit(code);
|
||||
}
|
||||
|
||||
async $setPerformanceMarks(marks: PerformanceMark[]): Promise<void> {
|
||||
if (this._extensionHostKind === ExtensionHostKind.LocalProcess) {
|
||||
this._timerService.setPerformanceMarks('localExtHost', marks);
|
||||
} else if (this._extensionHostKind === ExtensionHostKind.LocalWebWorker) {
|
||||
this._timerService.setPerformanceMarks('workerExtHost', marks);
|
||||
} else {
|
||||
this._timerService.setPerformanceMarks('remoteExtHost', marks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,43 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { FileChangeType, IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileChangeType, FileOperation, IFileService } from 'vs/platform/files/common/files';
|
||||
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IWorkingCopyFileOperationParticipant, IWorkingCopyFileService, SourceTargetPair, IFileOperationUndoRedoInfo } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
@extHostCustomer
|
||||
export class MainThreadFileSystemEventService {
|
||||
|
||||
static readonly MementoKeyAdditionalEdits = `file.particpants.additionalEdits`;
|
||||
|
||||
private readonly _listener = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IFileService fileService: IFileService,
|
||||
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
|
||||
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
|
||||
@IBulkEditService bulkEditService: IBulkEditService,
|
||||
@IProgressService progressService: IProgressService,
|
||||
@IDialogService dialogService: IDialogService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ILogService logService: ILogService,
|
||||
@IEnvironmentService envService: IEnvironmentService
|
||||
) {
|
||||
|
||||
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService);
|
||||
@@ -53,14 +73,124 @@ export class MainThreadFileSystemEventService {
|
||||
}));
|
||||
|
||||
|
||||
// BEFORE file operation
|
||||
this._listener.add(workingCopyFileService.addFileOperationParticipant({
|
||||
participate: async (files, operation, undoRedoGroupId, isUndoing, _progress, timeout, token) => {
|
||||
if (!isUndoing) {
|
||||
return proxy.$onWillRunFileOperation(operation, files, undoRedoGroupId, timeout, token);
|
||||
const fileOperationParticipant = new class implements IWorkingCopyFileOperationParticipant {
|
||||
async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, timeout: number, token: CancellationToken) {
|
||||
if (undoInfo?.isUndoing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cts = new CancellationTokenSource(token);
|
||||
const timer = setTimeout(() => cts.cancel(), timeout);
|
||||
|
||||
const data = await progressService.withProgress({
|
||||
location: ProgressLocation.Notification,
|
||||
title: this._progressLabel(operation),
|
||||
cancellable: true,
|
||||
delay: Math.min(timeout / 2, 3000)
|
||||
}, () => {
|
||||
// race extension host event delivery against timeout AND user-cancel
|
||||
const onWillEvent = proxy.$onWillRunFileOperation(operation, files, timeout, token);
|
||||
return raceCancellation(onWillEvent, cts.token);
|
||||
}, () => {
|
||||
// user-cancel
|
||||
cts.cancel();
|
||||
|
||||
}).finally(() => {
|
||||
cts.dispose();
|
||||
clearTimeout(timer);
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
// cancelled or no reply
|
||||
return;
|
||||
}
|
||||
|
||||
const needsConfirmation = data.edit.edits.some(edit => edit.metadata?.needsConfirmation);
|
||||
let showPreview = storageService.getBoolean(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, StorageScope.GLOBAL);
|
||||
|
||||
if (envService.extensionTestsLocationURI) {
|
||||
// don't show dialog in tests
|
||||
showPreview = false;
|
||||
}
|
||||
|
||||
if (showPreview === undefined) {
|
||||
// show a user facing message
|
||||
|
||||
let message: string;
|
||||
if (data.extensionNames.length === 1) {
|
||||
if (operation === FileOperation.CREATE) {
|
||||
message = localize('ask.1.create', "Extension '{0}' wants to make refactoring changes with this file creation", data.extensionNames[0]);
|
||||
} else if (operation === FileOperation.COPY) {
|
||||
message = localize('ask.1.copy', "Extension '{0}' wants to make refactoring changes with this file copy", data.extensionNames[0]);
|
||||
} else if (operation === FileOperation.MOVE) {
|
||||
message = localize('ask.1.move', "Extension '{0}' wants to make refactoring changes with this file move", data.extensionNames[0]);
|
||||
} else /* if (operation === FileOperation.DELETE) */ {
|
||||
message = localize('ask.1.delete', "Extension '{0}' wants to make refactoring changes with this file deletion", data.extensionNames[0]);
|
||||
}
|
||||
} else {
|
||||
if (operation === FileOperation.CREATE) {
|
||||
message = localize('ask.N.create', "{0} extensions want to make refactoring changes with this file creation", data.extensionNames.length);
|
||||
} else if (operation === FileOperation.COPY) {
|
||||
message = localize('ask.N.copy', "{0} extensions want to make refactoring changes with this file copy", data.extensionNames.length);
|
||||
} else if (operation === FileOperation.MOVE) {
|
||||
message = localize('ask.N.move', "{0} extensions want to make refactoring changes with this file move", data.extensionNames.length);
|
||||
} else /* if (operation === FileOperation.DELETE) */ {
|
||||
message = localize('ask.N.delete', "{0} extensions want to make refactoring changes with this file deletion", data.extensionNames.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (needsConfirmation) {
|
||||
// edit which needs confirmation -> always show dialog
|
||||
const answer = await dialogService.show(Severity.Info, message, [localize('preview', "Show Preview"), localize('cancel', "Skip Changes")], { cancelId: 1 });
|
||||
showPreview = true;
|
||||
if (answer.choice === 1) {
|
||||
// no changes wanted
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// choice
|
||||
const answer = await dialogService.show(Severity.Info, message,
|
||||
[localize('ok', "OK"), localize('preview', "Show Preview"), localize('cancel', "Skip Changes")],
|
||||
{
|
||||
cancelId: 2,
|
||||
checkbox: { label: localize('again', "Don't ask again") }
|
||||
}
|
||||
);
|
||||
if (answer.choice === 2) {
|
||||
// no changes wanted, don't persist cancel option
|
||||
return;
|
||||
}
|
||||
showPreview = answer.choice === 1;
|
||||
if (answer.checkboxChecked /* && answer.choice !== 2 */) {
|
||||
storageService.store(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, showPreview, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logService.info('[onWill-handler] applying additional workspace edit from extensions', data.extensionNames);
|
||||
|
||||
await bulkEditService.apply(
|
||||
reviveWorkspaceEditDto2(data.edit),
|
||||
{ undoRedoGroupId: undoInfo?.undoRedoGroupId, showPreview }
|
||||
);
|
||||
}
|
||||
|
||||
private _progressLabel(operation: FileOperation): string {
|
||||
switch (operation) {
|
||||
case FileOperation.CREATE:
|
||||
return localize('msg-create', "Running 'File Create' participants...");
|
||||
case FileOperation.MOVE:
|
||||
return localize('msg-rename', "Running 'File Rename' participants...");
|
||||
case FileOperation.COPY:
|
||||
return localize('msg-copy', "Running 'File Copy' participants...");
|
||||
case FileOperation.DELETE:
|
||||
return localize('msg-delete', "Running 'File Delete' participants...");
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
// BEFORE file operation
|
||||
this._listener.add(workingCopyFileService.addFileOperationParticipant(fileOperationParticipant));
|
||||
|
||||
// AFTER file operation
|
||||
this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => proxy.$onDidRunFileOperation(e.operation, e.files)));
|
||||
@@ -71,6 +201,19 @@ export class MainThreadFileSystemEventService {
|
||||
}
|
||||
}
|
||||
|
||||
registerAction2(class ResetMemento extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'files.participants.resetChoice',
|
||||
title: localize('label', "Reset choice for 'File operation needs preview'"),
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor) {
|
||||
accessor.get(IStorageService).remove(MainThreadFileSystemEventService.MementoKeyAdditionalEdits, StorageScope.GLOBAL);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
|
||||
id: 'files',
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto';
|
||||
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
|
||||
export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape {
|
||||
@@ -497,6 +497,32 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
}));
|
||||
}
|
||||
|
||||
// --- inline hints
|
||||
|
||||
$registerInlineHintsProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void {
|
||||
const provider = <modes.InlineHintsProvider>{
|
||||
provideInlineHints: async (model: ITextModel, range: EditorRange, token: CancellationToken): Promise<modes.InlineHint[] | undefined> => {
|
||||
const result = await this._proxy.$provideInlineHints(handle, model.uri, range, token);
|
||||
return result?.hints;
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof eventHandle === 'number') {
|
||||
const emitter = new Emitter<void>();
|
||||
this._registrations.set(eventHandle, emitter);
|
||||
provider.onDidChangeInlineHints = emitter.event;
|
||||
}
|
||||
|
||||
this._registrations.set(handle, modes.InlineHintsProviderRegistry.register(selector, provider));
|
||||
}
|
||||
|
||||
$emitInlineHintsEvent(eventHandle: number, event?: any): void {
|
||||
const obj = this._registrations.get(eventHandle);
|
||||
if (obj instanceof Emitter) {
|
||||
obj.fire(event);
|
||||
}
|
||||
}
|
||||
|
||||
// --- links
|
||||
|
||||
$registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void {
|
||||
@@ -662,7 +688,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
return {
|
||||
beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText),
|
||||
afterText: onEnterRule.afterText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText) : undefined,
|
||||
oneLineAboveText: onEnterRule.oneLineAboveText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.oneLineAboveText) : undefined,
|
||||
previousLineText: onEnterRule.previousLineText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.previousLineText) : undefined,
|
||||
action: onEnterRule.action
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
||||
|
||||
$showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise<number | undefined> {
|
||||
if (options.modal) {
|
||||
return this._showModalMessage(severity, message, commands);
|
||||
return this._showModalMessage(severity, message, commands, options.useCustom);
|
||||
} else {
|
||||
return this._showMessage(severity, message, commands, options.extension);
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
||||
});
|
||||
}
|
||||
|
||||
private async _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise<number | undefined> {
|
||||
private async _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], useCustom?: boolean): Promise<number | undefined> {
|
||||
let cancelId: number | undefined = undefined;
|
||||
|
||||
const buttons = commands.map((command, index) => {
|
||||
@@ -118,7 +118,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
||||
cancelId = buttons.length - 1;
|
||||
}
|
||||
|
||||
const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId });
|
||||
const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId, useCustom });
|
||||
return choice === commands.length ? undefined : commands[choice].handle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { viewColumnToEditorGroup } from 'vs/workbench/common/editor';
|
||||
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
@@ -129,12 +129,12 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
|
||||
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
|
||||
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService,
|
||||
@IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService,
|
||||
@INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService,
|
||||
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
|
||||
@@ -646,14 +646,13 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
|
||||
switch (revealType) {
|
||||
case NotebookEditorRevealType.Default:
|
||||
notebookEditor.revealInView(cell);
|
||||
break;
|
||||
return notebookEditor.revealCellRangeInView(range);
|
||||
case NotebookEditorRevealType.InCenter:
|
||||
notebookEditor.revealInCenter(cell);
|
||||
break;
|
||||
return notebookEditor.revealInCenter(cell);
|
||||
case NotebookEditorRevealType.InCenterIfOutsideViewport:
|
||||
notebookEditor.revealInCenterIfOutsideViewport(cell);
|
||||
break;
|
||||
return notebookEditor.revealInCenterIfOutsideViewport(cell);
|
||||
case NotebookEditorRevealType.AtTop:
|
||||
return notebookEditor.revealInViewAtTop(cell);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -733,7 +732,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
|
||||
const input = this.editorService.createEditorInput({ resource: URI.revive(resource), options: editorOptions });
|
||||
|
||||
// TODO: handle options.selection
|
||||
const editorPane = await openEditorWith(input, viewType, options, group, this.editorService, this.configurationService, this.quickInputService);
|
||||
const editorPane = await this._instantiationService.invokeFunction(openEditorWith, input, viewType, options, group);
|
||||
const notebookEditor = (editorPane as unknown as { isNotebookEditor?: boolean })?.isNotebookEditor ? (editorPane!.getControl() as INotebookEditor) : undefined;
|
||||
|
||||
if (notebookEditor) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
|
||||
import { IEncryptionService } from 'vs/workbench/services/encryption/common/encryptionService';
|
||||
import { ExtHostContext, ExtHostSecretStateShape, IExtHostContext, MainContext, MainThreadSecretStateShape } from '../common/extHost.protocol';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadSecretState)
|
||||
export class MainThreadSecretState extends Disposable implements MainThreadSecretStateShape {
|
||||
private readonly _proxy: ExtHostSecretStateShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ICredentialsService private readonly credentialsService: ICredentialsService,
|
||||
@IEncryptionService private readonly encryptionService: IEncryptionService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSecretState);
|
||||
|
||||
this._register(this.credentialsService.onDidChangePassword(e => {
|
||||
const extensionId = e.service.substring(this.productService.urlProtocol.length);
|
||||
this._proxy.$onDidChangePassword({ extensionId, key: e.account });
|
||||
}));
|
||||
}
|
||||
|
||||
private getFullKey(extensionId: string): string {
|
||||
return `${this.productService.urlProtocol}${extensionId}`;
|
||||
}
|
||||
|
||||
async $getPassword(extensionId: string, key: string): Promise<string | undefined> {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
const password = await this.credentialsService.getPassword(fullKey, key);
|
||||
const decrypted = password && await this.encryptionService.decrypt(password);
|
||||
|
||||
if (decrypted) {
|
||||
try {
|
||||
const value = JSON.parse(decrypted);
|
||||
if (value.extensionId === extensionId) {
|
||||
return value.content;
|
||||
}
|
||||
} catch (_) {
|
||||
throw new Error('Cannot get password');
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async $setPassword(extensionId: string, key: string, value: string): Promise<void> {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
const toEncrypt = JSON.stringify({
|
||||
extensionId,
|
||||
content: value
|
||||
});
|
||||
const encrypted = await this.encryptionService.encrypt(toEncrypt);
|
||||
return this.credentialsService.setPassword(fullKey, key, encrypted);
|
||||
}
|
||||
|
||||
async $deletePassword(extensionId: string, key: string): Promise<void> {
|
||||
try {
|
||||
const fullKey = this.getFullKey(extensionId);
|
||||
await this.credentialsService.deletePassword(fullKey, key);
|
||||
} catch (_) {
|
||||
throw new Error('Cannot delete password');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,13 +567,19 @@ export class MainThreadTask implements MainThreadTaskShape {
|
||||
if (!task) {
|
||||
reject(new Error('Task not found'));
|
||||
} else {
|
||||
this._taskService.run(task).then(undefined, reason => {
|
||||
// eat the error, it has already been surfaced to the user and we don't care about it here
|
||||
});
|
||||
const result: TaskExecutionDTO = {
|
||||
id: value.id,
|
||||
task: TaskDTO.from(task)
|
||||
};
|
||||
this._taskService.run(task).then(summary => {
|
||||
// Ensure that the task execution gets cleaned up if the exit code is undefined
|
||||
// This can happen when the task has dependent tasks and one of them failed
|
||||
if ((summary?.exitCode === undefined) || (summary.exitCode !== 0)) {
|
||||
this._proxy.$OnDidEndTask(result);
|
||||
}
|
||||
}, reason => {
|
||||
// eat the error, it has already been surfaced to the user and we don't care about it here
|
||||
});
|
||||
resolve(result);
|
||||
}
|
||||
}, (_error) => {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
@@ -16,11 +16,18 @@ import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/termi
|
||||
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
|
||||
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
|
||||
|
||||
private _proxy: ExtHostTerminalServiceShape;
|
||||
/**
|
||||
* Stores a map from a temporary terminal id (a UUID generated on the extension host side)
|
||||
* to a numeric terminal id (an id generated on the renderer side)
|
||||
* This comes in play only when dealing with terminals created on the extension host side
|
||||
*/
|
||||
private _extHostTerminalIds = new Map<string, number>();
|
||||
private _remoteAuthority: string | null;
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
|
||||
@@ -40,6 +47,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
|
||||
@@ -47,13 +55,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
|
||||
// ITerminalService listeners
|
||||
this._toDispose.add(_terminalService.onInstanceCreated((instance) => {
|
||||
// Delay this message so the TerminalInstance constructor has a chance to finish and
|
||||
// return the ID normally to the extension host. The ID that is passed here will be
|
||||
// used to register non-extension API terminals in the extension host.
|
||||
setTimeout(() => {
|
||||
this._onTerminalOpened(instance);
|
||||
this._onInstanceDimensionsChanged(instance);
|
||||
}, EXT_HOST_CREATION_DELAY);
|
||||
this._onTerminalOpened(instance);
|
||||
this._onInstanceDimensionsChanged(instance);
|
||||
}));
|
||||
|
||||
this._toDispose.add(_terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
|
||||
@@ -100,7 +103,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
// when the extension host process goes down ?
|
||||
}
|
||||
|
||||
public $createTerminal(launchConfig: TerminalLaunchConfig): Promise<{ id: number, name: string }> {
|
||||
private _getTerminalId(id: TerminalIdentifier): number | undefined {
|
||||
if (typeof id === 'number') {
|
||||
return id;
|
||||
}
|
||||
return this._extHostTerminalIds.get(id);
|
||||
}
|
||||
|
||||
private _getTerminalInstance(id: TerminalIdentifier): ITerminalInstance | undefined {
|
||||
const rendererId = this._getTerminalId(id);
|
||||
if (typeof rendererId === 'number') {
|
||||
return this._terminalService.getInstanceFromId(rendererId);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async $createTerminal(extHostTerminalId: string, launchConfig: TerminalLaunchConfig): Promise<void> {
|
||||
const shellLaunchConfig: IShellLaunchConfig = {
|
||||
name: launchConfig.name,
|
||||
executable: launchConfig.shellPath,
|
||||
@@ -112,39 +130,38 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
strictEnv: launchConfig.strictEnv,
|
||||
hideFromUser: launchConfig.hideFromUser,
|
||||
isExtensionTerminal: launchConfig.isExtensionTerminal,
|
||||
extHostTerminalId: extHostTerminalId,
|
||||
isFeatureTerminal: launchConfig.isFeatureTerminal
|
||||
};
|
||||
const terminal = this._terminalService.createTerminal(shellLaunchConfig);
|
||||
return Promise.resolve({
|
||||
id: terminal.id,
|
||||
name: terminal.title
|
||||
});
|
||||
this._extHostTerminalIds.set(extHostTerminalId, terminal.id);
|
||||
}
|
||||
|
||||
public $show(terminalId: number, preserveFocus: boolean): void {
|
||||
const terminalInstance = this._terminalService.getInstanceFromId(terminalId);
|
||||
public $show(id: TerminalIdentifier, preserveFocus: boolean): void {
|
||||
const terminalInstance = this._getTerminalInstance(id);
|
||||
if (terminalInstance) {
|
||||
this._terminalService.setActiveInstance(terminalInstance);
|
||||
this._terminalService.showPanel(!preserveFocus);
|
||||
}
|
||||
}
|
||||
|
||||
public $hide(terminalId: number): void {
|
||||
public $hide(id: TerminalIdentifier): void {
|
||||
const rendererId = this._getTerminalId(id);
|
||||
const instance = this._terminalService.getActiveInstance();
|
||||
if (instance && instance.id === terminalId) {
|
||||
if (instance && instance.id === rendererId) {
|
||||
this._terminalService.hidePanel();
|
||||
}
|
||||
}
|
||||
|
||||
public $dispose(terminalId: number): void {
|
||||
const terminalInstance = this._terminalService.getInstanceFromId(terminalId);
|
||||
public $dispose(id: TerminalIdentifier): void {
|
||||
const terminalInstance = this._getTerminalInstance(id);
|
||||
if (terminalInstance) {
|
||||
terminalInstance.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
|
||||
const terminalInstance = this._terminalService.getInstanceFromId(terminalId);
|
||||
public $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void {
|
||||
const terminalInstance = this._getTerminalInstance(id);
|
||||
if (terminalInstance) {
|
||||
terminalInstance.sendText(text, addNewLine);
|
||||
}
|
||||
@@ -204,6 +221,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
}
|
||||
|
||||
private _onTerminalOpened(terminalInstance: ITerminalInstance): void {
|
||||
const extHostTerminalId = terminalInstance.shellLaunchConfig.extHostTerminalId;
|
||||
const shellLaunchConfigDto: IShellLaunchConfigDto = {
|
||||
name: terminalInstance.shellLaunchConfig.name,
|
||||
executable: terminalInstance.shellLaunchConfig.executable,
|
||||
@@ -212,13 +230,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
env: terminalInstance.shellLaunchConfig.env,
|
||||
hideFromUser: terminalInstance.shellLaunchConfig.hideFromUser
|
||||
};
|
||||
if (terminalInstance.title) {
|
||||
this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title, shellLaunchConfigDto);
|
||||
} else {
|
||||
terminalInstance.waitForTitle().then(title => {
|
||||
this._proxy.$acceptTerminalOpened(terminalInstance.id, title, shellLaunchConfigDto);
|
||||
});
|
||||
}
|
||||
this._proxy.$acceptTerminalOpened(terminalInstance.id, extHostTerminalId, terminalInstance.title, shellLaunchConfigDto);
|
||||
}
|
||||
|
||||
private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void {
|
||||
@@ -249,7 +261,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
executable: request.shellLaunchConfig.executable,
|
||||
args: request.shellLaunchConfig.args,
|
||||
cwd: request.shellLaunchConfig.cwd,
|
||||
env: request.shellLaunchConfig.env
|
||||
env: request.shellLaunchConfig.env,
|
||||
flowControl: this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION).flowControl
|
||||
};
|
||||
|
||||
this._logService.trace('Spawning ext host process', { terminalId: proxy.terminalId, shellLaunchConfigDto, request });
|
||||
@@ -260,8 +273,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
request.cols,
|
||||
request.rows,
|
||||
request.isWorkspaceShellAllowed
|
||||
).then(request.callback);
|
||||
).then(request.callback, request.callback);
|
||||
|
||||
proxy.onAcknowledgeDataEvent(charCount => this._proxy.$acceptProcessAckDataEvent(proxy.terminalId, charCount));
|
||||
proxy.onInput(data => this._proxy.$acceptProcessInput(proxy.terminalId, data));
|
||||
proxy.onResize(dimensions => this._proxy.$acceptProcessResize(proxy.terminalId, dimensions.cols, dimensions.rows));
|
||||
proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(proxy.terminalId, immediate));
|
||||
@@ -294,38 +308,59 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
}
|
||||
|
||||
public $sendProcessTitle(terminalId: number, title: string): void {
|
||||
this._getTerminalProcess(terminalId).emitTitle(title);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitTitle(title);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendProcessData(terminalId: number, data: string): void {
|
||||
this._getTerminalProcess(terminalId).emitData(data);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitData(data);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendProcessReady(terminalId: number, pid: number, cwd: string): void {
|
||||
this._getTerminalProcess(terminalId).emitReady(pid, cwd);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitReady(pid, cwd);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendProcessExit(terminalId: number, exitCode: number | undefined): void {
|
||||
this._getTerminalProcess(terminalId).emitExit(exitCode);
|
||||
this._terminalProcessProxies.delete(terminalId);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitExit(exitCode);
|
||||
this._terminalProcessProxies.delete(terminalId);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void {
|
||||
this._getTerminalProcess(terminalId).emitOverrideDimensions(dimensions);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitOverrideDimensions(dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void {
|
||||
this._getTerminalProcess(terminalId).emitInitialCwd(initialCwd);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitInitialCwd(initialCwd);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendProcessCwd(terminalId: number, cwd: string): void {
|
||||
this._getTerminalProcess(terminalId).emitCwd(cwd);
|
||||
const terminalProcess = this._terminalProcessProxies.get(terminalId);
|
||||
if (terminalProcess) {
|
||||
terminalProcess.emitCwd(cwd);
|
||||
}
|
||||
}
|
||||
|
||||
public $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void {
|
||||
const instance = this._terminalService.getInstanceFromId(terminalId);
|
||||
if (instance) {
|
||||
this._getTerminalProcess(terminalId).emitResolvedShellLaunchConfig(shellLaunchConfig);
|
||||
this._getTerminalProcess(terminalId)?.emitResolvedShellLaunchConfig(shellLaunchConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +373,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
sw.stop();
|
||||
sum += sw.elapsed();
|
||||
}
|
||||
this._getTerminalProcess(terminalId).emitLatency(sum / COUNT);
|
||||
this._getTerminalProcess(terminalId)?.emitLatency(sum / COUNT);
|
||||
}
|
||||
|
||||
private _isPrimaryExtHost(): boolean {
|
||||
@@ -363,10 +398,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
}
|
||||
}
|
||||
|
||||
private _getTerminalProcess(terminalId: number): ITerminalProcessExtHostProxy {
|
||||
private _getTerminalProcess(terminalId: number): ITerminalProcessExtHostProxy | undefined {
|
||||
const terminal = this._terminalProcessProxies.get(terminalId);
|
||||
if (!terminal) {
|
||||
throw new Error(`Unknown terminal: ${terminalId}`);
|
||||
this._logService.error(`Unknown terminal: ${terminalId}`);
|
||||
return undefined;
|
||||
}
|
||||
return terminal;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,31 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
|
||||
|
||||
const reviveDiff = (diff: TestsDiff) => {
|
||||
for (const entry of diff) {
|
||||
if (entry[0] === TestDiffOpType.Add || entry[0] === TestDiffOpType.Update) {
|
||||
const item = entry[1];
|
||||
if (item.item.location) {
|
||||
item.item.location.uri = URI.revive(item.item.location.uri);
|
||||
}
|
||||
|
||||
for (const message of item.item.state.messages) {
|
||||
if (message.location) {
|
||||
message.location.uri = URI.revive(message.location.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTesting)
|
||||
export class MainThreadTesting extends Disposable implements MainThreadTestingShape {
|
||||
@@ -18,11 +37,28 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ITestService private readonly testService: ITestService,
|
||||
@ITestResultService resultService: ITestResultService,
|
||||
) {
|
||||
super();
|
||||
this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostTesting);
|
||||
this._register(this.testService.onShouldSubscribe(args => this.proxy.$subscribeToTests(args.resource, args.uri)));
|
||||
this._register(this.testService.onShouldUnsubscribe(args => this.proxy.$unsubscribeFromTests(args.resource, args.uri)));
|
||||
|
||||
const testCompleteListener = this._register(new MutableDisposable());
|
||||
this._register(resultService.onNewTestResult(results => {
|
||||
testCompleteListener.value = results.onComplete(() => this.proxy.$publishTestResults({ tests: results.tests }));
|
||||
}));
|
||||
|
||||
testService.updateRootProviderCount(1);
|
||||
|
||||
const lastCompleted = resultService.results.find(r => !r.isComplete);
|
||||
if (lastCompleted) {
|
||||
this.proxy.$publishTestResults({ tests: lastCompleted.tests });
|
||||
}
|
||||
|
||||
for (const { resource, uri } of this.testService.subscriptions) {
|
||||
this.proxy.$subscribeToTests(resource, uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,7 +66,8 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
*/
|
||||
public $registerTestProvider(id: string) {
|
||||
this.testService.registerTestController(id, {
|
||||
runTests: req => this.proxy.$runTestsForProvider(req),
|
||||
runTests: (req, token) => this.proxy.$runTestsForProvider(req, token),
|
||||
lookupTest: test => this.proxy.$lookupTest(test),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,14 +101,19 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void {
|
||||
reviveDiff(diff);
|
||||
this.testService.publishDiff(resource, URI.revive(uri), diff);
|
||||
}
|
||||
|
||||
public $runTests(req: RunTestsRequest): Promise<RunTestsResult> {
|
||||
return this.testService.runTests(req);
|
||||
public $runTests(req: RunTestsRequest, token: CancellationToken): Promise<RunTestsResult> {
|
||||
return this.testService.runTests(req, token);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
// no-op
|
||||
this.testService.updateRootProviderCount(-1);
|
||||
for (const subscription of this.testSubscriptions.values()) {
|
||||
subscription.dispose();
|
||||
}
|
||||
this.testSubscriptions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,8 +222,8 @@ class TreeViewDataProvider implements ITreeViewDataProvider {
|
||||
const hasResolve = await this.hasResolve;
|
||||
if (elements) {
|
||||
for (const element of elements) {
|
||||
const resolvable = new ResolvableTreeItem(element, hasResolve ? () => {
|
||||
return this._proxy.$resolve(this.treeViewId, element.handle);
|
||||
const resolvable = new ResolvableTreeItem(element, hasResolve ? (token) => {
|
||||
return this._proxy.$resolve(this.treeViewId, element.handle, token);
|
||||
} : undefined);
|
||||
this.itemsMap.set(element.handle, resolvable);
|
||||
result.push(resolvable);
|
||||
|
||||
@@ -3,22 +3,31 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { CandidatePort, IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged } from 'vs/platform/remote/common/tunnel';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { PORT_AUTO_FORWARD_SETTING } from 'vs/workbench/contrib/remote/browser/tunnelView';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
|
||||
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape {
|
||||
private readonly _proxy: ExtHostTunnelServiceShape;
|
||||
private elevateionRetry: boolean = false;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
|
||||
@ITunnelService private readonly tunnelService: ITunnelService
|
||||
@ITunnelService private readonly tunnelService: ITunnelService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService);
|
||||
@@ -26,14 +35,50 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
this._register(tunnelService.onTunnelClosed(() => this._proxy.$onDidTunnelsChange()));
|
||||
}
|
||||
|
||||
async $setCandidateFinder(): Promise<void> {
|
||||
if (this.remoteExplorerService.portsFeaturesEnabled) {
|
||||
this._proxy.$registerCandidateFinder(this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING));
|
||||
} else {
|
||||
this._register(this.remoteExplorerService.onEnabledPortsFeatures(() => this._proxy.$registerCandidateFinder(this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING))));
|
||||
}
|
||||
this._register(this.configurationService.onDidChangeConfiguration(async (e) => {
|
||||
if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) {
|
||||
return this._proxy.$registerCandidateFinder((this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async $openTunnel(tunnelOptions: TunnelOptions, source: string): Promise<TunnelDto | undefined> {
|
||||
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source);
|
||||
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, false);
|
||||
if (tunnel) {
|
||||
if (!this.elevateionRetry
|
||||
&& (tunnelOptions.localAddressPort !== undefined)
|
||||
&& (tunnel.tunnelLocalPort !== undefined)
|
||||
&& isPortPrivileged(tunnelOptions.localAddressPort)
|
||||
&& (tunnel.tunnelLocalPort !== tunnelOptions.localAddressPort)
|
||||
&& this.tunnelService.canElevate) {
|
||||
|
||||
this.elevationPrompt(tunnelOptions, tunnel, source);
|
||||
}
|
||||
return TunnelDto.fromServiceTunnel(tunnel);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async elevationPrompt(tunnelOptions: TunnelOptions, tunnel: RemoteTunnel, source: string) {
|
||||
return this.notificationService.prompt(Severity.Info,
|
||||
nls.localize('remote.tunnel.openTunnel', "The extension {0} has forwarded port {1}. You'll need to run as superuser to use port {2} locally.", source, tunnelOptions.remoteAddress.port, tunnelOptions.localAddressPort),
|
||||
[{
|
||||
label: nls.localize('remote.tunnelsView.elevationButton', "Use Port {0} as Sudo...", tunnel.tunnelRemotePort),
|
||||
run: async () => {
|
||||
this.elevateionRetry = true;
|
||||
await this.remoteExplorerService.close({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort });
|
||||
await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, true);
|
||||
this.elevateionRetry = false;
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
async $closeTunnel(remote: { host: string, port: number }): Promise<void> {
|
||||
return this.remoteExplorerService.close(remote);
|
||||
}
|
||||
@@ -47,27 +92,29 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
});
|
||||
}
|
||||
|
||||
async $onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<void> {
|
||||
async $onFoundNewCandidates(candidates: CandidatePort[]): Promise<void> {
|
||||
this.remoteExplorerService.onFoundNewCandidates(candidates);
|
||||
}
|
||||
|
||||
async $tunnelServiceReady(): Promise<void> {
|
||||
return this.remoteExplorerService.restore();
|
||||
}
|
||||
|
||||
async $setTunnelProvider(): Promise<void> {
|
||||
async $setTunnelProvider(features: TunnelProviderFeatures): Promise<void> {
|
||||
const tunnelProvider: ITunnelProvider = {
|
||||
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => {
|
||||
const forward = this._proxy.$forwardPort(tunnelOptions, tunnelCreationOptions);
|
||||
if (forward) {
|
||||
return forward.then(tunnel => {
|
||||
this.logService.trace(`MainThreadTunnelService: New tunnel established by tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`);
|
||||
if (!tunnel) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
tunnelRemotePort: tunnel.remoteAddress.port,
|
||||
tunnelRemoteHost: tunnel.remoteAddress.host,
|
||||
localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port),
|
||||
tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined,
|
||||
dispose: (silent?: boolean) => {
|
||||
this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent);
|
||||
public: tunnel.public,
|
||||
dispose: async (silent?: boolean) => {
|
||||
this.logService.trace(`MainThreadTunnelService: Closing tunnel from tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`);
|
||||
return this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -75,9 +122,16 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
this.tunnelService.setTunnelProvider(tunnelProvider);
|
||||
this.tunnelService.setTunnelProvider(tunnelProvider, features);
|
||||
}
|
||||
|
||||
async $setCandidateFilter(): Promise<void> {
|
||||
this.remoteExplorerService.setCandidateFilter((candidates: CandidatePort[]): Promise<CandidatePort[]> => {
|
||||
return this._proxy.$applyCandidateFilter(candidates);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
dispose(): void {
|
||||
|
||||
}
|
||||
|
||||
132
lib/vscode/src/vs/workbench/api/browser/mainThreadUriOpeners.ts
Normal file
132
lib/vscode/src/vs/workbench/api/browser/mainThreadUriOpeners.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ExtHostContext, ExtHostUriOpenersShape, IExtHostContext, MainContext, MainThreadUriOpenersShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { defaultExternalUriOpenerId } from 'vs/workbench/contrib/externalUriOpener/common/configuration';
|
||||
import { ContributedExternalUriOpenersStore } from 'vs/workbench/contrib/externalUriOpener/common/contributedOpeners';
|
||||
import { IExternalOpenerProvider, IExternalUriOpener, IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { extHostNamedCustomer } from '../common/extHostCustomers';
|
||||
|
||||
interface RegisteredOpenerMetadata {
|
||||
readonly schemes: ReadonlySet<string>;
|
||||
readonly extensionId: ExtensionIdentifier;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadUriOpeners)
|
||||
export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpenersShape, IExternalOpenerProvider {
|
||||
|
||||
private readonly proxy: ExtHostUriOpenersShape;
|
||||
private readonly _registeredOpeners = new Map<string, RegisteredOpenerMetadata>();
|
||||
private readonly _contributedExternalUriOpenersStore: ContributedExternalUriOpenersStore;
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IExternalUriOpenerService externalUriOpenerService: IExternalUriOpenerService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
) {
|
||||
super();
|
||||
this.proxy = context.getProxy(ExtHostContext.ExtHostUriOpeners);
|
||||
|
||||
this._register(externalUriOpenerService.registerExternalOpenerProvider(this));
|
||||
|
||||
this._contributedExternalUriOpenersStore = this._register(new ContributedExternalUriOpenersStore(storageService, extensionService));
|
||||
}
|
||||
|
||||
public async *getOpeners(targetUri: URI): AsyncIterable<IExternalUriOpener> {
|
||||
|
||||
// Currently we only allow openers for http and https urls
|
||||
if (targetUri.scheme !== Schemas.http && targetUri.scheme !== Schemas.https) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.extensionService.activateByEvent(`onUriOpen:${targetUri.scheme}`);
|
||||
|
||||
for (const [id, openerMetadata] of this._registeredOpeners) {
|
||||
if (openerMetadata.schemes.has(targetUri.scheme)) {
|
||||
yield this.createOpener(id, openerMetadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createOpener(id: string, metadata: RegisteredOpenerMetadata): IExternalUriOpener {
|
||||
return {
|
||||
id: id,
|
||||
label: metadata.label,
|
||||
canOpen: (uri, token) => {
|
||||
return this.proxy.$canOpenUri(id, uri, token);
|
||||
},
|
||||
openExternalUri: async (uri, ctx, token) => {
|
||||
try {
|
||||
await this.proxy.$openUri(id, { resolvedUri: uri, sourceUri: ctx.sourceUri }, token);
|
||||
} catch (e) {
|
||||
if (!isPromiseCanceledError(e)) {
|
||||
const openDefaultAction = new Action('default', localize('openerFailedUseDefault', "Open using default opener"), undefined, undefined, () => {
|
||||
return this.openerService.open(uri, {
|
||||
allowTunneling: false,
|
||||
allowContributedOpeners: defaultExternalUriOpenerId,
|
||||
});
|
||||
});
|
||||
openDefaultAction.tooltip = uri.toString();
|
||||
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('openerFailedMessage', 'Could not open uri with \'{0}\': {1}', id, e.toString()),
|
||||
actions: {
|
||||
primary: [
|
||||
openDefaultAction
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async $registerUriOpener(
|
||||
id: string,
|
||||
schemes: readonly string[],
|
||||
extensionId: ExtensionIdentifier,
|
||||
label: string,
|
||||
): Promise<void> {
|
||||
if (this._registeredOpeners.has(id)) {
|
||||
throw new Error(`Opener with id '${id}' already registered`);
|
||||
}
|
||||
|
||||
this._registeredOpeners.set(id, {
|
||||
schemes: new Set(schemes),
|
||||
label,
|
||||
extensionId,
|
||||
});
|
||||
|
||||
this._contributedExternalUriOpenersStore.add(id, extensionId.value);
|
||||
}
|
||||
|
||||
async $unregisterUriOpener(id: string): Promise<void> {
|
||||
this._registeredOpeners.delete(id);
|
||||
this._contributedExternalUriOpenersStore.delete(id);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this._registeredOpeners.clear();
|
||||
}
|
||||
}
|
||||
@@ -129,6 +129,9 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
|
||||
dispose(this._editorProviders.values());
|
||||
this._editorProviders.clear();
|
||||
|
||||
dispose(this._revivers.values());
|
||||
this._revivers.clear();
|
||||
}
|
||||
|
||||
public get webviewInputs(): Iterable<WebviewInput> { return this._webviewInputs; }
|
||||
@@ -181,7 +184,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
webview.setName(value);
|
||||
}
|
||||
|
||||
|
||||
public $setIconPath(handle: extHostProtocol.WebviewHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void {
|
||||
const webview = this.getWebviewInput(handle);
|
||||
webview.iconPath = reviveWebviewIcon(value);
|
||||
@@ -199,8 +201,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
}
|
||||
}
|
||||
|
||||
public $registerSerializer(viewType: string)
|
||||
: void {
|
||||
public $registerSerializer(viewType: string): void {
|
||||
if (this._revivers.has(viewType)) {
|
||||
throw new Error(`Reviver for ${viewType} already registered`);
|
||||
}
|
||||
@@ -216,7 +217,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const handle = webviewInput.id;
|
||||
|
||||
this.addWebviewInput(handle, webviewInput);
|
||||
|
||||
@@ -78,7 +78,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
private onDidClickLink(handle: extHostProtocol.WebviewHandle, link: string): void {
|
||||
const webview = this.getWebview(handle);
|
||||
if (this.isSupportedLink(webview, URI.parse(link))) {
|
||||
this._openerService.open(link, { fromUserGesture: true });
|
||||
this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,11 @@ export class MainThreadWindow implements MainThreadWindowShape {
|
||||
// called with URI or transformed -> use uri
|
||||
target = uri;
|
||||
}
|
||||
return this.openerService.open(target, { openExternal: true, allowTunneling: options.allowTunneling });
|
||||
return this.openerService.open(target, {
|
||||
openExternal: true,
|
||||
allowTunneling: options.allowTunneling,
|
||||
allowContributedOpeners: options.allowContributedOpeners,
|
||||
});
|
||||
}
|
||||
|
||||
async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise<UriComponents> {
|
||||
|
||||
@@ -6,25 +6,25 @@
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isNative } from 'vs/base/common/platform';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isNative } from 'vs/base/common/platform';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
|
||||
import { IWorkspaceContextService, WorkbenchState, IWorkspace, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { checkGlobFileExists } from 'vs/workbench/api/common/shared/workspaceContains';
|
||||
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
|
||||
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { checkGlobFileExists } from 'vs/workbench/api/common/shared/workspaceContains';
|
||||
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, ITextSearchComplete, IWorkspaceData, MainContext, MainThreadWorkspaceShape } from '../common/extHost.protocol';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
|
||||
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
@@ -139,7 +139,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
}
|
||||
|
||||
const query = this._queryBuilder.file(
|
||||
includeFolder ? [toWorkspaceFolder(includeFolder)] : workspace.folders,
|
||||
includeFolder ? [includeFolder] : workspace.folders,
|
||||
{
|
||||
maxResults: withNullAsUndefined(maxResults),
|
||||
disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined,
|
||||
|
||||
@@ -3,35 +3,32 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { forEach } from 'vs/base/common/collections';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, testViewIcon } from 'vs/workbench/common/views';
|
||||
import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { coalesce, } from 'vs/base/common/arrays';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files';
|
||||
import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { localize } from 'vs/nls';
|
||||
import { registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { Extensions as ViewletExtensions, ShowViewletAction2, ViewletRegistry } from 'vs/workbench/browser/viewlet';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { Extensions as ViewContainerExtensions, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files';
|
||||
import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer';
|
||||
import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
||||
export interface IUserFriendlyViewsContainerDescriptor {
|
||||
id: string;
|
||||
@@ -110,7 +107,7 @@ const viewDescriptor: IJSONSchema = {
|
||||
defaultSnippets: [{ body: { id: '${1:id}', name: '${2:name}' } }],
|
||||
properties: {
|
||||
type: {
|
||||
markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."),
|
||||
markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."),
|
||||
type: 'string',
|
||||
enum: [
|
||||
'tree',
|
||||
@@ -255,7 +252,8 @@ const viewsExtensionPoint: IExtensionPoint<ViewExtensionPointType> = ExtensionsR
|
||||
jsonSchema: viewsContribution
|
||||
});
|
||||
|
||||
const TEST_VIEW_CONTAINER_ORDER = 6;
|
||||
const CUSTOM_VIEWS_START_ORDER = 7;
|
||||
|
||||
class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
|
||||
private viewContainersRegistry: IViewContainersRegistry;
|
||||
@@ -271,7 +269,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
private handleAndRegisterCustomViewContainers() {
|
||||
this.registerTestViewContainer();
|
||||
viewsContainersExtensionPoint.setHandler((extensions, { added, removed }) => {
|
||||
if (removed.length) {
|
||||
this.removeCustomViewContainers(removed);
|
||||
@@ -284,7 +281,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
|
||||
private addCustomViewContainers(extensionPoints: readonly IExtensionPointUser<ViewContainerExtensionPointType>[], existingViewContainers: ViewContainer[]): void {
|
||||
const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
let activityBarOrder = TEST_VIEW_CONTAINER_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length + 1;
|
||||
let activityBarOrder = CUSTOM_VIEWS_START_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length;
|
||||
let panelOrder = 5 + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Panel).length + 1;
|
||||
for (let { value, collector, description } of extensionPoints) {
|
||||
forEach(value, entry => {
|
||||
@@ -318,13 +315,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
private registerTestViewContainer(): void {
|
||||
const title = localize('test', "Test");
|
||||
const icon = testViewIcon;
|
||||
|
||||
this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined, ViewContainerLocation.Sidebar);
|
||||
}
|
||||
|
||||
private isValidViewsContainer(viewsContainersDescriptors: IUserFriendlyViewsContainerDescriptor[], collector: ExtensionMessageCollector): boolean {
|
||||
if (!Array.isArray(viewsContainersDescriptors)) {
|
||||
collector.error(localize('viewcontainer requirearray', "views containers must be an array"));
|
||||
@@ -395,22 +385,15 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}, location);
|
||||
|
||||
// Register Action to Open Viewlet
|
||||
class OpenCustomViewletAction extends ShowViewletAction {
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label, id, viewletService, editorGroupService, layoutService);
|
||||
registerAction2(class OpenCustomViewletAction extends ShowViewletAction2 {
|
||||
constructor() {
|
||||
super({ id, f1: true, title: localize('showViewlet', "Show {0}", title), category: CATEGORIES.View.value });
|
||||
}
|
||||
}
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(
|
||||
SyncActionDescriptor.create(OpenCustomViewletAction, id, localize('showViewlet', "Show {0}", title)),
|
||||
`View: Show ${title}`,
|
||||
CATEGORIES.View.value
|
||||
);
|
||||
|
||||
protected viewletId() {
|
||||
return id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return viewContainer;
|
||||
@@ -501,7 +484,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
originalContainerId: entry.key,
|
||||
group: item.group,
|
||||
remoteAuthority: item.remoteName || (<any>item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated
|
||||
hideByDefault: initialVisibility === InitialVisibility.Hidden
|
||||
hideByDefault: initialVisibility === InitialVisibility.Hidden,
|
||||
workspace: viewContainer?.id === REMOTE ? true : undefined
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -12,7 +12,6 @@ import { IWorkspacesService, IRecent } from 'vs/platform/workspaces/common/works
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IViewDescriptorService, IViewsService, ViewVisibilityState } from 'vs/workbench/common/views';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// The following commands are registered on both sides separately.
|
||||
@@ -128,12 +127,6 @@ CommandsRegistry.registerCommand('_extensionTests.setLogLevel', function (access
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.openExternal', function (accessor: ServicesAccessor, uri: UriComponents, options: { allowTunneling?: boolean }) {
|
||||
// TODO: discuss martin, ben where to put this
|
||||
const openerService = accessor.get(IOpenerService);
|
||||
openerService.open(URI.revive(uri), options);
|
||||
});
|
||||
|
||||
|
||||
CommandsRegistry.registerCommand('_extensionTests.getLogLevel', function (accessor: ServicesAccessor) {
|
||||
const logService = accessor.get(ILogService);
|
||||
|
||||
38
lib/vscode/src/vs/workbench/api/common/exHostSecretState.ts
Normal file
38
lib/vscode/src/vs/workbench/api/common/exHostSecretState.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtHostSecretStateShape, MainContext, MainThreadSecretStateShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class ExtHostSecretState implements ExtHostSecretStateShape {
|
||||
private _proxy: MainThreadSecretStateShape;
|
||||
private _onDidChangePassword = new Emitter<{ extensionId: string, key: string }>();
|
||||
readonly onDidChangePassword = this._onDidChangePassword.event;
|
||||
|
||||
constructor(mainContext: IExtHostRpcService) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadSecretState);
|
||||
}
|
||||
|
||||
async $onDidChangePassword(e: { extensionId: string, key: string }): Promise<void> {
|
||||
this._onDidChangePassword.fire(e);
|
||||
}
|
||||
|
||||
get(extensionId: string, key: string): Promise<string | undefined> {
|
||||
return this._proxy.$getPassword(extensionId, key);
|
||||
}
|
||||
|
||||
store(extensionId: string, key: string, value: string): Promise<void> {
|
||||
return this._proxy.$setPassword(extensionId, key, value);
|
||||
}
|
||||
|
||||
delete(extensionId: string, key: string): Promise<void> {
|
||||
return this._proxy.$deletePassword(extensionId, key);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExtHostSecretState extends ExtHostSecretState { }
|
||||
export const IExtHostSecretState = createDecorator<IExtHostSecretState>('IExtHostSecretState');
|
||||
@@ -82,6 +82,9 @@ import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPane
|
||||
import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits';
|
||||
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
|
||||
import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
|
||||
import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener';
|
||||
import { IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
|
||||
import { ExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
|
||||
|
||||
export interface IExtensionApiFactory {
|
||||
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
|
||||
@@ -107,6 +110,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostTunnelService = accessor.get(IExtHostTunnelService);
|
||||
const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService);
|
||||
const extHostWindow = accessor.get(IExtHostWindow);
|
||||
const extHostSecretState = accessor.get(IExtHostSecretState);
|
||||
|
||||
// register addressable instances
|
||||
rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo);
|
||||
@@ -117,6 +121,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostTunnelService, extHostTunnelService);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostWindow, extHostWindow);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostSecretState, extHostSecretState);
|
||||
|
||||
// automatically create and register addressable instances
|
||||
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
|
||||
@@ -129,6 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService));
|
||||
|
||||
// manually create and register addressable instances
|
||||
const extHostEditorTabs = rpcProtocol.set(ExtHostContext.ExtHostEditorTabs, new ExtHostEditorTabs());
|
||||
const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol));
|
||||
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService));
|
||||
@@ -154,6 +160,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostCustomEditors = rpcProtocol.set(ExtHostContext.ExtHostCustomEditors, new ExtHostCustomEditors(rpcProtocol, extHostDocuments, extensionStoragePaths, extHostWebviews, extHostWebviewPanels));
|
||||
const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews));
|
||||
const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, new ExtHostTesting(rpcProtocol, extHostDocumentsAndEditors, extHostWorkspace));
|
||||
const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol));
|
||||
|
||||
// Check that no named customers are missing
|
||||
const expected: ProxyIdentifier<any>[] = values(ExtHostContext);
|
||||
@@ -209,22 +216,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
get onDidChangeSessions(): Event<vscode.AuthenticationSessionsChangeEvent> {
|
||||
return extHostAuthentication.onDidChangeSessions;
|
||||
},
|
||||
registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable {
|
||||
registerAuthenticationProvider(id: string, label: string, provider: vscode.AuthenticationProvider, options?: vscode.AuthenticationProviderOptions): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.registerAuthenticationProvider(provider);
|
||||
return extHostAuthentication.registerAuthenticationProvider(id, label, provider, options);
|
||||
},
|
||||
get onDidChangeAuthenticationProviders(): Event<vscode.AuthenticationProvidersChangeEvent> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.onDidChangeAuthenticationProviders;
|
||||
},
|
||||
getProviderIds(): Thenable<ReadonlyArray<string>> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.getProviderIds();
|
||||
},
|
||||
get providerIds(): string[] {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.providerIds;
|
||||
},
|
||||
get providers(): ReadonlyArray<vscode.AuthenticationProviderInformation> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.providers;
|
||||
@@ -232,22 +231,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
logout(providerId: string, sessionId: string): Thenable<void> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.logout(providerId, sessionId);
|
||||
},
|
||||
getPassword(key: string): Thenable<string | undefined> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.getPassword(extension, key);
|
||||
},
|
||||
setPassword(key: string, value: string): Thenable<void> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.setPassword(extension, key, value);
|
||||
},
|
||||
deletePassword(key: string): Thenable<void> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.deletePassword(extension, key);
|
||||
},
|
||||
get onDidChangePassword(): Event<void> {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostAuthentication.onDidChangePassword;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -311,8 +294,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
get shell() {
|
||||
return extHostTerminalService.getDefaultShell(false, configProvider);
|
||||
},
|
||||
openExternal(uri: URI) {
|
||||
return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.authority });
|
||||
openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string; }) {
|
||||
return extHostWindow.openUri(uri, {
|
||||
allowTunneling: !!initData.remote.authority,
|
||||
allowContributedOpeners: options?.allowContributedOpeners,
|
||||
});
|
||||
},
|
||||
asExternalUri(uri: URI) {
|
||||
if (uri.scheme === initData.environment.appUriScheme) {
|
||||
@@ -354,6 +340,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTesting.runTests(provider);
|
||||
},
|
||||
get onDidChangeTestResults() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTesting.onLastResultsChanged;
|
||||
},
|
||||
get testResults() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTesting.lastResults;
|
||||
},
|
||||
};
|
||||
|
||||
// namespace: extensions
|
||||
@@ -480,6 +474,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
getTokenInformationAtPosition(doc: vscode.TextDocument, pos: vscode.Position) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostLanguages.tokenAtPosition(doc, pos);
|
||||
},
|
||||
registerInlineHintsProvider(selector: vscode.DocumentSelector, provider: vscode.InlineHintsProvider): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostLanguageFeatures.registerInlineHintsProvider(extension, selector, provider);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -588,10 +586,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
id = extension.identifier.value;
|
||||
name = nls.localize('extensionLabel', "{0} (Extension)", extension.displayName || extension.name);
|
||||
alignment = alignmentOrOptions;
|
||||
priority = priority;
|
||||
}
|
||||
|
||||
return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority, accessibilityInformation, extension);
|
||||
return extHostStatusBar.createStatusBarEntry(id, name, alignment, priority, accessibilityInformation);
|
||||
},
|
||||
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): vscode.Disposable {
|
||||
return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable);
|
||||
@@ -691,6 +688,18 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
showNotebookDocument(document, options?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.showNotebookDocument(document, options);
|
||||
},
|
||||
registerExternalUriOpener(id: string, opener: vscode.ExternalUriOpener, metadata: vscode.ExternalUriOpenerMetadata) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostUriOpeners.registerExternalUriOpener(extension.identifier, id, opener, metadata);
|
||||
},
|
||||
get openEditors() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostEditorTabs.tabs;
|
||||
},
|
||||
get onDidChangeOpenEditors() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostEditorTabs.onDidChangeTabs;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1108,6 +1117,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall,
|
||||
CallHierarchyItem: extHostTypes.CallHierarchyItem,
|
||||
CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall,
|
||||
CancellationError: errors.CancellationError,
|
||||
CancellationTokenSource: CancellationTokenSource,
|
||||
CodeAction: extHostTypes.CodeAction,
|
||||
CodeActionKind: extHostTypes.CodeActionKind,
|
||||
@@ -1148,6 +1158,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
EventEmitter: Emitter,
|
||||
ExtensionKind: extHostTypes.ExtensionKind,
|
||||
ExtensionMode: extHostTypes.ExtensionMode,
|
||||
ExternalUriOpenerPriority: extHostTypes.ExternalUriOpenerPriority,
|
||||
FileChangeType: extHostTypes.FileChangeType,
|
||||
FileDecoration: extHostTypes.FileDecoration,
|
||||
FileSystemError: extHostTypes.FileSystemError,
|
||||
@@ -1157,6 +1168,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
FunctionBreakpoint: extHostTypes.FunctionBreakpoint,
|
||||
Hover: extHostTypes.Hover,
|
||||
IndentAction: languageConfiguration.IndentAction,
|
||||
InlineHint: extHostTypes.InlineHint,
|
||||
Location: extHostTypes.Location,
|
||||
MarkdownString: extHostTypes.MarkdownString,
|
||||
OverviewRulerLane: OverviewRulerLane,
|
||||
|
||||
@@ -21,6 +21,7 @@ import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs
|
||||
import { IExtHostWindow, ExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
|
||||
import { IExtHostConsumerFileSystem, ExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer';
|
||||
import { IExtHostFileSystemInfo, ExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
|
||||
import { IExtHostSecretState, ExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
|
||||
|
||||
registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
|
||||
registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService);
|
||||
@@ -39,3 +40,4 @@ registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService);
|
||||
registerSingleton(IExtHostTunnelService, ExtHostTunnelService);
|
||||
registerSingleton(IExtHostWindow, ExtHostWindow);
|
||||
registerSingleton(IExtHostWorkspace, ExtHostWorkspace);
|
||||
registerSingleton(IExtHostSecretState, ExtHostSecretState);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IRemoteConsoleLog } from 'vs/base/common/console';
|
||||
@@ -41,13 +42,13 @@ import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views';
|
||||
import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||
import { ITerminalDimensions, IShellLaunchConfig, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ActivationKind, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ActivationKind, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
import * as search from 'vs/workbench/services/search/common/search';
|
||||
import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor';
|
||||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { TunnelCreationOptions, TunnelProviderFeatures, 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 { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
@@ -57,7 +58,8 @@ import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib
|
||||
import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
||||
import { RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { InternalTestItem, InternalTestResults, RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
|
||||
export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
@@ -108,7 +110,8 @@ export interface IConfigurationInitData extends IConfigurationData {
|
||||
}
|
||||
|
||||
export interface IExtHostContext extends IRPCProtocol {
|
||||
remoteAuthority: string | null;
|
||||
readonly remoteAuthority: string | null;
|
||||
readonly extensionHostKind: ExtensionHostKind;
|
||||
}
|
||||
|
||||
export interface IMainContext extends IRPCProtocol {
|
||||
@@ -174,7 +177,9 @@ export interface MainThreadAuthenticationShape extends IDisposable {
|
||||
$getSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>>;
|
||||
$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession>;
|
||||
$logout(providerId: string, sessionId: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadSecretStateShape extends IDisposable {
|
||||
$getPassword(extensionId: string, key: string): Promise<string | undefined>;
|
||||
$setPassword(extensionId: string, key: string, value: string): Promise<void>;
|
||||
$deletePassword(extensionId: string, key: string): Promise<void>;
|
||||
@@ -330,7 +335,7 @@ export interface IIndentationRuleDto {
|
||||
export interface IOnEnterRuleDto {
|
||||
beforeText: IRegExpDto;
|
||||
afterText?: IRegExpDto;
|
||||
oneLineAboveText?: IRegExpDto;
|
||||
previousLineText?: IRegExpDto;
|
||||
action: EnterAction;
|
||||
}
|
||||
export interface ILanguageConfigurationDto {
|
||||
@@ -397,6 +402,8 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
||||
$registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void;
|
||||
$registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, displayName: string): void;
|
||||
$registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void;
|
||||
$registerInlineHintsProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void;
|
||||
$emitInlineHintsEvent(eventHandle: number, event?: any): void;
|
||||
$registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void;
|
||||
$registerDocumentColorProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void;
|
||||
@@ -415,6 +422,7 @@ export interface MainThreadLanguagesShape extends IDisposable {
|
||||
export interface MainThreadMessageOptions {
|
||||
extension?: IExtensionDescription;
|
||||
modal?: boolean;
|
||||
useCustom?: boolean;
|
||||
}
|
||||
|
||||
export interface MainThreadMessageServiceShape extends IDisposable {
|
||||
@@ -438,6 +446,16 @@ export interface MainThreadProgressShape extends IDisposable {
|
||||
$progressEnd(handle: number): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A terminal that is created on the extension host side is temporarily assigned
|
||||
* a UUID by the extension host that created it. Once the renderer side has assigned
|
||||
* a real numeric id, the numeric id will be used.
|
||||
*
|
||||
* All other terminals (that are not created on the extension host side) always
|
||||
* use the numeric id.
|
||||
*/
|
||||
export type TerminalIdentifier = number | string;
|
||||
|
||||
export interface TerminalLaunchConfig {
|
||||
name?: string;
|
||||
shellPath?: string;
|
||||
@@ -452,11 +470,11 @@ export interface TerminalLaunchConfig {
|
||||
}
|
||||
|
||||
export interface MainThreadTerminalServiceShape extends IDisposable {
|
||||
$createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string; }>;
|
||||
$dispose(terminalId: number): void;
|
||||
$hide(terminalId: number): void;
|
||||
$sendText(terminalId: number, text: string, addNewLine: boolean): void;
|
||||
$show(terminalId: number, preserveFocus: boolean): void;
|
||||
$createTerminal(extHostTerminalId: string, config: TerminalLaunchConfig): Promise<void>;
|
||||
$dispose(id: TerminalIdentifier): void;
|
||||
$hide(id: TerminalIdentifier): void;
|
||||
$sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void;
|
||||
$show(id: TerminalIdentifier, preserveFocus: boolean): void;
|
||||
$startSendingDataEvents(): void;
|
||||
$stopSendingDataEvents(): void;
|
||||
$startLinkProvider(): void;
|
||||
@@ -594,6 +612,24 @@ export interface ExtHostEditorInsetsShape {
|
||||
$onDidReceiveMessage(handle: number, message: any): void;
|
||||
}
|
||||
|
||||
//#region --- open editors model
|
||||
|
||||
export interface MainThreadEditorTabsShape extends IDisposable {
|
||||
// manage tabs: move, close, rearrange etc
|
||||
}
|
||||
|
||||
export interface IEditorTabDto {
|
||||
group: number;
|
||||
name: string;
|
||||
resource: UriComponents
|
||||
}
|
||||
|
||||
export interface IExtHostEditorTabsShape {
|
||||
$acceptEditorTabs(tabs: IEditorTabDto[]): void;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
export type WebviewHandle = string;
|
||||
|
||||
export interface WebviewPanelShowOptions {
|
||||
@@ -739,6 +775,7 @@ export enum NotebookEditorRevealType {
|
||||
Default = 0,
|
||||
InCenter = 1,
|
||||
InCenterIfOutsideViewport = 2,
|
||||
AtTop = 3
|
||||
}
|
||||
|
||||
export interface INotebookDocumentShowOptions {
|
||||
@@ -786,6 +823,16 @@ export interface ExtHostUrlsShape {
|
||||
$handleExternalUri(handle: number, uri: UriComponents): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadUriOpenersShape extends IDisposable {
|
||||
$registerUriOpener(id: string, schemes: readonly string[], extensionId: ExtensionIdentifier, label: string): Promise<void>;
|
||||
$unregisterUriOpener(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostUriOpenersShape {
|
||||
$canOpenUri(id: string, uri: UriComponents, token: CancellationToken): Promise<modes.ExternalUriOpenerPriority>;
|
||||
$openUri(id: string, context: { resolvedUri: UriComponents, sourceUri: UriComponents }, token: CancellationToken): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ITextSearchComplete {
|
||||
limitHit?: boolean;
|
||||
}
|
||||
@@ -853,6 +900,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable {
|
||||
$onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise<void>;
|
||||
$onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void;
|
||||
$onExtensionHostExit(code: number): Promise<void>;
|
||||
$setPerformanceMarks(marks: performance.PerformanceMark[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface SCMProviderFeatures {
|
||||
@@ -946,6 +994,7 @@ export interface MainThreadDebugServiceShape extends IDisposable {
|
||||
|
||||
export interface IOpenUriOptions {
|
||||
readonly allowTunneling?: boolean;
|
||||
readonly allowContributedOpeners?: boolean | string;
|
||||
}
|
||||
|
||||
export interface MainThreadWindowShape extends IDisposable {
|
||||
@@ -958,8 +1007,9 @@ export interface MainThreadTunnelServiceShape extends IDisposable {
|
||||
$openTunnel(tunnelOptions: TunnelOptions, source: string | undefined): Promise<TunnelDto | undefined>;
|
||||
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
|
||||
$getTunnels(): Promise<TunnelDescription[]>;
|
||||
$setTunnelProvider(): Promise<void>;
|
||||
$tunnelServiceReady(): Promise<void>;
|
||||
$setTunnelProvider(features: TunnelProviderFeatures): Promise<void>;
|
||||
$setCandidateFinder(): Promise<void>;
|
||||
$setCandidateFilter(): Promise<void>;
|
||||
$onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1052,7 +1102,7 @@ export interface ExtHostTreeViewsShape {
|
||||
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
|
||||
$setVisible(treeViewId: string, visible: boolean): void;
|
||||
$hasResolve(treeViewId: string): Promise<boolean>;
|
||||
$resolve(treeViewId: string, treeItemHandle: string): Promise<ITreeItem | undefined>;
|
||||
$resolve(treeViewId: string, treeItemHandle: string, token: CancellationToken): Promise<ITreeItem | undefined>;
|
||||
}
|
||||
|
||||
export interface ExtHostWorkspaceShape {
|
||||
@@ -1094,7 +1144,10 @@ export interface ExtHostAuthenticationShape {
|
||||
$onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent): Promise<void>;
|
||||
$onDidChangeAuthenticationProviders(added: modes.AuthenticationProviderInformation[], removed: modes.AuthenticationProviderInformation[]): Promise<void>;
|
||||
$setProviders(providers: modes.AuthenticationProviderInformation[]): Promise<void>;
|
||||
$onDidChangePassword(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostSecretStateShape {
|
||||
$onDidChangePassword(e: { extensionId: string, key: string }): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostSearchShape {
|
||||
@@ -1145,9 +1198,14 @@ export interface SourceTargetPair {
|
||||
target: UriComponents;
|
||||
}
|
||||
|
||||
export interface IWillRunFileOperationParticipation {
|
||||
edit: IWorkspaceEditDto;
|
||||
extensionNames: string[]
|
||||
}
|
||||
|
||||
export interface ExtHostFileSystemEventServiceShape {
|
||||
$onFileEvent(events: FileSystemEvents): void;
|
||||
$onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise<any>;
|
||||
$onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined>;
|
||||
$onDidRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[]): void;
|
||||
}
|
||||
|
||||
@@ -1252,6 +1310,18 @@ export interface ISignatureHelpContextDto {
|
||||
readonly activeSignatureHelp?: ISignatureHelpDto;
|
||||
}
|
||||
|
||||
export interface IInlineHintDto {
|
||||
text: string;
|
||||
range: IRange;
|
||||
hoverMessage?: string;
|
||||
whitespaceBefore?: boolean;
|
||||
whitespaceAfter?: boolean;
|
||||
}
|
||||
|
||||
export interface IInlineHintsDto {
|
||||
hints: IInlineHintDto[]
|
||||
}
|
||||
|
||||
export interface ILocationDto {
|
||||
uri: UriComponents;
|
||||
range: IRange;
|
||||
@@ -1441,6 +1511,7 @@ export interface ExtHostLanguageFeaturesShape {
|
||||
$releaseCompletionItems(handle: number, id: number): void;
|
||||
$provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise<ISignatureHelpDto | undefined>;
|
||||
$releaseSignatureHelp(handle: number, id: number): void;
|
||||
$provideInlineHints(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise<IInlineHintsDto | undefined>
|
||||
$provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise<ILinksListDto | undefined>;
|
||||
$resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise<ILinkDto | undefined>;
|
||||
$releaseDocumentLinks(handle: number, id: number): void;
|
||||
@@ -1473,6 +1544,7 @@ export interface IShellLaunchConfigDto {
|
||||
cwd?: string | UriComponents;
|
||||
env?: { [key: string]: string | null; };
|
||||
hideFromUser?: boolean;
|
||||
flowControl?: boolean;
|
||||
}
|
||||
|
||||
export interface IShellDefinitionDto {
|
||||
@@ -1503,7 +1575,7 @@ export interface ITerminalDimensionsDto {
|
||||
|
||||
export interface ExtHostTerminalServiceShape {
|
||||
$acceptTerminalClosed(id: number, exitCode: number | undefined): void;
|
||||
$acceptTerminalOpened(id: number, name: string, shellLaunchConfig: IShellLaunchConfigDto): void;
|
||||
$acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfig: IShellLaunchConfigDto): void;
|
||||
$acceptActiveTerminalChanged(id: number | null): void;
|
||||
$acceptTerminalProcessId(id: number, processId: number): void;
|
||||
$acceptTerminalProcessData(id: number, data: string): void;
|
||||
@@ -1512,6 +1584,7 @@ export interface ExtHostTerminalServiceShape {
|
||||
$acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void;
|
||||
$spawnExtHostProcess(id: number, shellLaunchConfig: IShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined>;
|
||||
$startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined>;
|
||||
$acceptProcessAckDataEvent(id: number, charCount: number): void;
|
||||
$acceptProcessInput(id: number, data: string): void;
|
||||
$acceptProcessResize(id: number, cols: number, rows: number): void;
|
||||
$acceptProcessShutdown(id: number, immediate: boolean): void;
|
||||
@@ -1725,7 +1798,7 @@ export interface ExtHostNotebookShape {
|
||||
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
|
||||
$backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string | undefined>;
|
||||
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
|
||||
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }): void;
|
||||
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined }): void;
|
||||
$onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void;
|
||||
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void;
|
||||
$acceptModelSaved(uriComponents: UriComponents): void;
|
||||
@@ -1749,9 +1822,11 @@ export interface MainThreadThemingShape extends IDisposable {
|
||||
}
|
||||
|
||||
export interface ExtHostTunnelServiceShape {
|
||||
$forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto> | undefined;
|
||||
$forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined>;
|
||||
$closeTunnel(remote: { host: string, port: number }, silent?: boolean): Promise<void>;
|
||||
$onDidTunnelsChange(): Promise<void>;
|
||||
$registerCandidateFinder(enable: boolean): Promise<void>;
|
||||
$applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]>;
|
||||
}
|
||||
|
||||
export interface ExtHostTimelineShape {
|
||||
@@ -1764,11 +1839,12 @@ export const enum ExtHostTestingResource {
|
||||
}
|
||||
|
||||
export interface ExtHostTestingShape {
|
||||
$runTestsForProvider(req: RunTestForProviderRequest): Promise<RunTestsResult>;
|
||||
$runTestsForProvider(req: RunTestForProviderRequest, token: CancellationToken): Promise<RunTestsResult>;
|
||||
$subscribeToTests(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
$unsubscribeFromTests(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
|
||||
$lookupTest(test: TestIdWithProvider): Promise<InternalTestItem | undefined>;
|
||||
$acceptDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void;
|
||||
$publishTestResults(results: InternalTestResults): void;
|
||||
}
|
||||
|
||||
export interface MainThreadTestingShape {
|
||||
@@ -1777,7 +1853,7 @@ export interface MainThreadTestingShape {
|
||||
$subscribeToDiffs(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
$unsubscribeFromDiffs(resource: ExtHostTestingResource, uri: UriComponents): void;
|
||||
$publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void;
|
||||
$runTests(req: RunTestsRequest): Promise<RunTestsResult>;
|
||||
$runTests(req: RunTestsRequest, token: CancellationToken): Promise<RunTestsResult>;
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
@@ -1798,6 +1874,7 @@ export const MainContext = {
|
||||
MainThreadDocumentContentProviders: createMainId<MainThreadDocumentContentProvidersShape>('MainThreadDocumentContentProviders'),
|
||||
MainThreadTextEditors: createMainId<MainThreadTextEditorsShape>('MainThreadTextEditors'),
|
||||
MainThreadEditorInsets: createMainId<MainThreadEditorInsetsShape>('MainThreadEditorInsets'),
|
||||
MainThreadEditorTabs: createMainId<MainThreadEditorTabsShape>('MainThreadEditorTabs'),
|
||||
MainThreadErrors: createMainId<MainThreadErrorsShape>('MainThreadErrors'),
|
||||
MainThreadTreeViews: createMainId<MainThreadTreeViewsShape>('MainThreadTreeViews'),
|
||||
MainThreadDownloadService: createMainId<MainThreadDownloadServiceShape>('MainThreadDownloadService'),
|
||||
@@ -1810,6 +1887,7 @@ export const MainContext = {
|
||||
MainThreadProgress: createMainId<MainThreadProgressShape>('MainThreadProgress'),
|
||||
MainThreadQuickOpen: createMainId<MainThreadQuickOpenShape>('MainThreadQuickOpen'),
|
||||
MainThreadStatusBar: createMainId<MainThreadStatusBarShape>('MainThreadStatusBar'),
|
||||
MainThreadSecretState: createMainId<MainThreadSecretStateShape>('MainThreadSecretState'),
|
||||
MainThreadStorage: createMainId<MainThreadStorageShape>('MainThreadStorage'),
|
||||
MainThreadTelemetry: createMainId<MainThreadTelemetryShape>('MainThreadTelemetry'),
|
||||
MainThreadTerminalService: createMainId<MainThreadTerminalServiceShape>('MainThreadTerminalService'),
|
||||
@@ -1818,6 +1896,7 @@ export const MainContext = {
|
||||
MainThreadWebviewViews: createMainId<MainThreadWebviewViewsShape>('MainThreadWebviewViews'),
|
||||
MainThreadCustomEditors: createMainId<MainThreadCustomEditorsShape>('MainThreadCustomEditors'),
|
||||
MainThreadUrls: createMainId<MainThreadUrlsShape>('MainThreadUrls'),
|
||||
MainThreadUriOpeners: createMainId<MainThreadUriOpenersShape>('MainThreadUriOpeners'),
|
||||
MainThreadWorkspace: createMainId<MainThreadWorkspaceShape>('MainThreadWorkspace'),
|
||||
MainThreadFileSystem: createMainId<MainThreadFileSystemShape>('MainThreadFileSystem'),
|
||||
MainThreadExtensionService: createMainId<MainThreadExtensionServiceShape>('MainThreadExtensionService'),
|
||||
@@ -1863,10 +1942,13 @@ export const ExtHostContext = {
|
||||
ExtHostCustomEditors: createExtId<ExtHostCustomEditorsShape>('ExtHostCustomEditors'),
|
||||
ExtHostWebviewViews: createExtId<ExtHostWebviewViewsShape>('ExtHostWebviewViews'),
|
||||
ExtHostEditorInsets: createExtId<ExtHostEditorInsetsShape>('ExtHostEditorInsets'),
|
||||
ExtHostEditorTabs: createExtId<IExtHostEditorTabsShape>('ExtHostEditorTabs'),
|
||||
ExtHostProgress: createMainId<ExtHostProgressShape>('ExtHostProgress'),
|
||||
ExtHostComments: createMainId<ExtHostCommentsShape>('ExtHostComments'),
|
||||
ExtHostSecretState: createMainId<ExtHostSecretStateShape>('ExtHostSecretState'),
|
||||
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
|
||||
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
|
||||
ExtHostUriOpeners: createExtId<ExtHostUriOpenersShape>('ExtHostUriOpeners'),
|
||||
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
|
||||
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
|
||||
ExtHostNotebook: createMainId<ExtHostNotebookShape>('ExtHostNotebook'),
|
||||
|
||||
@@ -20,6 +20,8 @@ import { IRange } from 'vs/editor/common/core/range';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
|
||||
|
||||
//#region --- NEW world
|
||||
|
||||
@@ -176,6 +178,57 @@ const newCommands: ApiCommand[] = [
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Number.with('linkResolveCount', 'Number of links that should be resolved, only when links are unresolved.').optional()],
|
||||
new ApiCommandResult<modes.ILink[], vscode.DocumentLink[]>('A promise that resolves to an array of DocumentLink-instances.', value => value.map(typeConverters.DocumentLink.to))
|
||||
),
|
||||
// --- semantic tokens
|
||||
new ApiCommand(
|
||||
'vscode.provideDocumentSemanticTokensLegend', '_provideDocumentSemanticTokensLegend', 'Provide semantic tokens legend for a document',
|
||||
[ApiCommandArgument.Uri],
|
||||
new ApiCommandResult<modes.SemanticTokensLegend, types.SemanticTokensLegend | undefined>('A promise that resolves to SemanticTokensLegend.', value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return new types.SemanticTokensLegend(value.tokenTypes, value.tokenModifiers);
|
||||
})
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.provideDocumentSemanticTokens', '_provideDocumentSemanticTokens', 'Provide semantic tokens for a document',
|
||||
[ApiCommandArgument.Uri],
|
||||
new ApiCommandResult<VSBuffer, types.SemanticTokens | undefined>('A promise that resolves to SemanticTokens.', value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const semanticTokensDto = decodeSemanticTokensDto(value);
|
||||
if (semanticTokensDto.type !== 'full') {
|
||||
// only accepting full semantic tokens from provideDocumentSemanticTokens
|
||||
return undefined;
|
||||
}
|
||||
return new types.SemanticTokens(semanticTokensDto.data, undefined);
|
||||
})
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.provideDocumentRangeSemanticTokensLegend', '_provideDocumentRangeSemanticTokensLegend', 'Provide semantic tokens legend for a document range',
|
||||
[ApiCommandArgument.Uri],
|
||||
new ApiCommandResult<modes.SemanticTokensLegend, types.SemanticTokensLegend | undefined>('A promise that resolves to SemanticTokensLegend.', value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return new types.SemanticTokensLegend(value.tokenTypes, value.tokenModifiers);
|
||||
})
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.provideDocumentRangeSemanticTokens', '_provideDocumentRangeSemanticTokens', 'Provide semantic tokens for a document range',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Range],
|
||||
new ApiCommandResult<VSBuffer, types.SemanticTokens | undefined>('A promise that resolves to SemanticTokens.', value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const semanticTokensDto = decodeSemanticTokensDto(value);
|
||||
if (semanticTokensDto.type !== 'full') {
|
||||
// only accepting full semantic tokens from provideDocumentRangeSemanticTokens
|
||||
return undefined;
|
||||
}
|
||||
return new types.SemanticTokens(semanticTokensDto.data, undefined);
|
||||
})
|
||||
),
|
||||
// --- completions
|
||||
new ApiCommand(
|
||||
'vscode.executeCompletionItemProvider', '_executeCompletionItemProvider', 'Execute completion item provider.',
|
||||
@@ -271,13 +324,21 @@ const newCommands: ApiCommand[] = [
|
||||
return [];
|
||||
})
|
||||
),
|
||||
// --- inline hints
|
||||
new ApiCommand(
|
||||
'vscode.executeInlineHintProvider', '_executeInlineHintProvider', 'Execute inline hints provider',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Range],
|
||||
new ApiCommandResult<modes.InlineHint[], vscode.InlineHint[]>('A promise that resolves to an array of InlineHint objects', result => {
|
||||
return result.map(typeConverters.InlineHint.to);
|
||||
})
|
||||
),
|
||||
// --- notebooks
|
||||
new ApiCommand(
|
||||
'vscode.resolveNotebookContentProviders', '_resolveNotebookContentProvider', 'Resolve Notebook Content Providers',
|
||||
[
|
||||
new ApiCommandArgument<string, string>('viewType', '', v => typeof v === 'string', v => v),
|
||||
new ApiCommandArgument<string, string>('displayName', '', v => typeof v === 'string', v => v),
|
||||
new ApiCommandArgument<object, object>('options', '', v => typeof v === 'object', v => v),
|
||||
// new ApiCommandArgument<string, string>('viewType', '', v => typeof v === 'string', v => v),
|
||||
// new ApiCommandArgument<string, string>('displayName', '', v => typeof v === 'string', v => v),
|
||||
// new ApiCommandArgument<object, object>('options', '', v => typeof v === 'object', v => v),
|
||||
],
|
||||
new ApiCommandResult<{
|
||||
viewType: string;
|
||||
|
||||
@@ -15,11 +15,15 @@ interface GetSessionsRequest {
|
||||
result: Promise<vscode.AuthenticationSession | undefined>;
|
||||
}
|
||||
|
||||
interface ProviderWithMetadata {
|
||||
label: string;
|
||||
provider: vscode.AuthenticationProvider;
|
||||
options: vscode.AuthenticationProviderOptions;
|
||||
}
|
||||
|
||||
export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
private _proxy: MainThreadAuthenticationShape;
|
||||
private _authenticationProviders: Map<string, vscode.AuthenticationProvider> = new Map<string, vscode.AuthenticationProvider>();
|
||||
|
||||
private _providerIds: string[] = [];
|
||||
private _authenticationProviders: Map<string, ProviderWithMetadata> = new Map<string, ProviderWithMetadata>();
|
||||
|
||||
private _providers: vscode.AuthenticationProviderInformation[] = [];
|
||||
|
||||
@@ -29,9 +33,6 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
private _onDidChangeSessions = new Emitter<vscode.AuthenticationSessionsChangeEvent>();
|
||||
readonly onDidChangeSessions: Event<vscode.AuthenticationSessionsChangeEvent> = this._onDidChangeSessions.event;
|
||||
|
||||
private _onDidChangePassword = new Emitter<void>();
|
||||
readonly onDidChangePassword: Event<void> = this._onDidChangePassword.event;
|
||||
|
||||
private _inFlightRequests = new Map<string, GetSessionsRequest[]>();
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
@@ -43,14 +44,6 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getProviderIds(): Promise<ReadonlyArray<string>> {
|
||||
return this._proxy.$getProviderIds();
|
||||
}
|
||||
|
||||
get providerIds(): string[] {
|
||||
return this._providerIds;
|
||||
}
|
||||
|
||||
get providers(): ReadonlyArray<vscode.AuthenticationProviderInformation> {
|
||||
return Object.freeze(this._providers.slice());
|
||||
}
|
||||
@@ -90,128 +83,120 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
|
||||
private async _getSession(requestingExtension: IExtensionDescription, extensionId: string, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions = {}): Promise<vscode.AuthenticationSession | undefined> {
|
||||
await this._proxy.$ensureProvider(providerId);
|
||||
const provider = this._authenticationProviders.get(providerId);
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
const extensionName = requestingExtension.displayName || requestingExtension.name;
|
||||
|
||||
if (!provider) {
|
||||
if (!providerData) {
|
||||
return this._proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
|
||||
}
|
||||
|
||||
const orderedScopes = scopes.sort().join(' ');
|
||||
const sessions = (await provider.getSessions()).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
|
||||
const sessions = (await providerData.provider.getSessions()).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
|
||||
|
||||
let session: vscode.AuthenticationSession | undefined = undefined;
|
||||
if (sessions.length) {
|
||||
if (!provider.supportsMultipleAccounts) {
|
||||
if (!providerData.options.supportsMultipleAccounts) {
|
||||
session = sessions[0];
|
||||
const allowed = await this._proxy.$getSessionsPrompt(providerId, session.account.label, provider.label, extensionId, extensionName);
|
||||
const allowed = await this._proxy.$getSessionsPrompt(providerId, session.account.label, providerData.label, extensionId, extensionName);
|
||||
if (!allowed) {
|
||||
throw new Error('User did not consent to login.');
|
||||
}
|
||||
} else {
|
||||
// On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid
|
||||
const selected = await this._proxy.$selectSession(providerId, provider.label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
|
||||
const selected = await this._proxy.$selectSession(providerId, providerData.label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
|
||||
session = sessions.find(session => session.id === selected.id);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (options.createIfNone) {
|
||||
const isAllowed = await this._proxy.$loginPrompt(provider.label, extensionName);
|
||||
const isAllowed = await this._proxy.$loginPrompt(providerData.label, extensionName);
|
||||
if (!isAllowed) {
|
||||
throw new Error('User did not consent to login.');
|
||||
}
|
||||
|
||||
session = await provider.login(scopes);
|
||||
session = await providerData.provider.login(scopes);
|
||||
await this._proxy.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
|
||||
} else {
|
||||
await this._proxy.$requestNewSession(providerId, scopes, extensionId, extensionName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
async logout(providerId: string, sessionId: string): Promise<void> {
|
||||
const provider = this._authenticationProviders.get(providerId);
|
||||
if (!provider) {
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
if (!providerData) {
|
||||
return this._proxy.$logout(providerId, sessionId);
|
||||
}
|
||||
|
||||
return provider.logout(sessionId);
|
||||
return providerData.provider.logout(sessionId);
|
||||
}
|
||||
|
||||
registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable {
|
||||
if (this._authenticationProviders.get(provider.id)) {
|
||||
throw new Error(`An authentication provider with id '${provider.id}' is already registered.`);
|
||||
registerAuthenticationProvider(id: string, label: string, provider: vscode.AuthenticationProvider, options?: vscode.AuthenticationProviderOptions): vscode.Disposable {
|
||||
if (this._authenticationProviders.get(id)) {
|
||||
throw new Error(`An authentication provider with id '${id}' is already registered.`);
|
||||
}
|
||||
|
||||
this._authenticationProviders.set(provider.id, provider);
|
||||
if (!this._providerIds.includes(provider.id)) {
|
||||
this._providerIds.push(provider.id);
|
||||
}
|
||||
this._authenticationProviders.set(id, { label, provider, options: options ?? { supportsMultipleAccounts: false } });
|
||||
|
||||
if (!this._providers.find(p => p.id === provider.id)) {
|
||||
if (!this._providers.find(p => p.id === id)) {
|
||||
this._providers.push({
|
||||
id: provider.id,
|
||||
label: provider.label
|
||||
id: id,
|
||||
label: label
|
||||
});
|
||||
}
|
||||
|
||||
const listener = provider.onDidChangeSessions(e => {
|
||||
this._proxy.$sendDidChangeSessions(provider.id, e);
|
||||
this._proxy.$sendDidChangeSessions(id, e);
|
||||
});
|
||||
|
||||
this._proxy.$registerAuthenticationProvider(provider.id, provider.label, provider.supportsMultipleAccounts);
|
||||
this._proxy.$registerAuthenticationProvider(id, label, options?.supportsMultipleAccounts ?? false);
|
||||
|
||||
return new Disposable(() => {
|
||||
listener.dispose();
|
||||
this._authenticationProviders.delete(provider.id);
|
||||
const index = this._providerIds.findIndex(id => id === provider.id);
|
||||
if (index > -1) {
|
||||
this._providerIds.splice(index);
|
||||
}
|
||||
this._authenticationProviders.delete(id);
|
||||
|
||||
const i = this._providers.findIndex(p => p.id === provider.id);
|
||||
const i = this._providers.findIndex(p => p.id === id);
|
||||
if (i > -1) {
|
||||
this._providers.splice(i);
|
||||
}
|
||||
|
||||
this._proxy.$unregisterAuthenticationProvider(provider.id);
|
||||
this._proxy.$unregisterAuthenticationProvider(id);
|
||||
});
|
||||
}
|
||||
|
||||
$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession> {
|
||||
const authProvider = this._authenticationProviders.get(providerId);
|
||||
if (authProvider) {
|
||||
return Promise.resolve(authProvider.login(scopes));
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
if (providerData) {
|
||||
return Promise.resolve(providerData.provider.login(scopes));
|
||||
}
|
||||
|
||||
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
|
||||
}
|
||||
|
||||
$logout(providerId: string, sessionId: string): Promise<void> {
|
||||
const authProvider = this._authenticationProviders.get(providerId);
|
||||
if (authProvider) {
|
||||
return Promise.resolve(authProvider.logout(sessionId));
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
if (providerData) {
|
||||
return Promise.resolve(providerData.provider.logout(sessionId));
|
||||
}
|
||||
|
||||
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
|
||||
}
|
||||
|
||||
$getSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
|
||||
const authProvider = this._authenticationProviders.get(providerId);
|
||||
if (authProvider) {
|
||||
return Promise.resolve(authProvider.getSessions());
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
if (providerData) {
|
||||
return Promise.resolve(providerData.provider.getSessions());
|
||||
}
|
||||
|
||||
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
|
||||
}
|
||||
|
||||
async $getSessionAccessToken(providerId: string, sessionId: string): Promise<string> {
|
||||
const authProvider = this._authenticationProviders.get(providerId);
|
||||
if (authProvider) {
|
||||
const sessions = await authProvider.getSessions();
|
||||
const providerData = this._authenticationProviders.get(providerId);
|
||||
if (providerData) {
|
||||
const sessions = await providerData.provider.getSessions();
|
||||
const session = sessions.find(session => session.id === sessionId);
|
||||
if (session) {
|
||||
return session.accessToken;
|
||||
@@ -245,23 +230,4 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
||||
this._onDidChangeAuthenticationProviders.fire({ added, removed });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async $onDidChangePassword(): Promise<void> {
|
||||
this._onDidChangePassword.fire();
|
||||
}
|
||||
|
||||
getPassword(requestingExtension: IExtensionDescription, key: string): Promise<string | undefined> {
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
return this._proxy.$getPassword(extensionId, key);
|
||||
}
|
||||
|
||||
setPassword(requestingExtension: IExtensionDescription, key: string, value: string): Promise<void> {
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
return this._proxy.$setPassword(extensionId, key, value);
|
||||
}
|
||||
|
||||
deletePassword(requestingExtension: IExtensionDescription, key: string): Promise<void> {
|
||||
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
|
||||
return this._proxy.$deletePassword(extensionId, key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export class ExtHostCommands implements ExtHostCommandsShape {
|
||||
|
||||
const internalArgs = apiCommand.args.map((arg, i) => {
|
||||
if (!arg.validate(apiArgs[i])) {
|
||||
throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', receieved: ${apiArgs[i]}`);
|
||||
throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', received: ${apiArgs[i]}`);
|
||||
}
|
||||
return arg.convert(apiArgs[i]);
|
||||
});
|
||||
@@ -194,7 +194,7 @@ export class ExtHostCommands implements ExtHostCommandsShape {
|
||||
}
|
||||
}
|
||||
|
||||
private _executeContributedCommand<T>(id: string, args: any[]): Promise<T> {
|
||||
private async _executeContributedCommand<T>(id: string, args: any[]): Promise<T> {
|
||||
const command = this._commands.get(id);
|
||||
if (!command) {
|
||||
throw new Error('Unknown command');
|
||||
@@ -205,17 +205,16 @@ export class ExtHostCommands implements ExtHostCommandsShape {
|
||||
try {
|
||||
validateConstraint(args[i], description.args[i].constraint);
|
||||
} catch (err) {
|
||||
return Promise.reject(new Error(`Running the contributed command: '${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`));
|
||||
throw new Error(`Running the contributed command: '${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = callback.apply(thisArg, args);
|
||||
return Promise.resolve(result);
|
||||
return await callback.apply(thisArg, args);
|
||||
} catch (err) {
|
||||
this._logService.error(err, id);
|
||||
return Promise.reject(new Error(`Running the contributed command: '${id}' failed.`));
|
||||
throw new Error(`Running the contributed command: '${id}' failed.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as modes from 'vs/editor/common/modes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview';
|
||||
import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels';
|
||||
import { EditorGroupColumn } from 'vs/workbench/common/editor';
|
||||
@@ -178,7 +179,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean },
|
||||
): vscode.Disposable {
|
||||
const disposables = new DisposableStore();
|
||||
if ('resolveCustomTextEditor' in provider) {
|
||||
if (isCustomTextEditorProvider(provider)) {
|
||||
disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider));
|
||||
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, {
|
||||
supportsMove: !!provider.moveCustomTextEditor,
|
||||
@@ -208,7 +209,6 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) {
|
||||
const entry = this._editorProviders.get(viewType);
|
||||
if (!entry) {
|
||||
@@ -261,8 +261,10 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
throw new Error(`No provider found for '${viewType}'`);
|
||||
}
|
||||
|
||||
const viewColumn = typeConverters.ViewColumn.to(position);
|
||||
|
||||
const webview = this._extHostWebview.createNewWebview(handle, options, entry.extension);
|
||||
const panel = this._extHostWebviewPanels.createNewWebviewPanel(handle, viewType, title, position, options, webview);
|
||||
const panel = this._extHostWebviewPanels.createNewWebviewPanel(handle, viewType, title, viewColumn, options, webview);
|
||||
|
||||
const revivedResource = URI.revive(resource);
|
||||
|
||||
@@ -350,7 +352,6 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
return backup.id;
|
||||
}
|
||||
|
||||
|
||||
private getCustomDocumentEntry(viewType: string, resource: UriComponents): CustomDocumentStoreEntry {
|
||||
const entry = this._documents.get(viewType, URI.revive(resource));
|
||||
if (!entry) {
|
||||
@@ -375,6 +376,9 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
|
||||
}
|
||||
}
|
||||
|
||||
function isCustomTextEditorProvider(provider: vscode.CustomReadonlyEditorProvider<vscode.CustomDocument> | vscode.CustomTextEditorProvider): provider is vscode.CustomTextEditorProvider {
|
||||
return typeof (provider as vscode.CustomTextEditorProvider).resolveCustomTextEditor === 'function';
|
||||
}
|
||||
|
||||
function isEditEvent(e: vscode.CustomDocumentContentChangeEvent | vscode.CustomDocumentEditEvent): e is vscode.CustomDocumentEditEvent {
|
||||
return typeof (e as vscode.CustomDocumentEditEvent).undo === 'function'
|
||||
|
||||
@@ -152,7 +152,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
|
||||
|
||||
const source = <any>src;
|
||||
|
||||
if (typeof source.sourceReference === 'number') {
|
||||
if (typeof source.sourceReference === 'number' && source.sourceReference > 0) {
|
||||
// src can be retrieved via DAP's "source" request
|
||||
|
||||
let debug = `debug:${encodeURIComponent(source.path || '')}`;
|
||||
|
||||
39
lib/vscode/src/vs/workbench/api/common/extHostEditorTabs.ts
Normal file
39
lib/vscode/src/vs/workbench/api/common/extHostEditorTabs.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import { IEditorTabDto, IExtHostEditorTabsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
|
||||
|
||||
export interface IEditorTab {
|
||||
name: string;
|
||||
group: number;
|
||||
resource: vscode.Uri
|
||||
}
|
||||
|
||||
export class ExtHostEditorTabs implements IExtHostEditorTabsShape {
|
||||
|
||||
private readonly _onDidChangeTabs = new Emitter<void>();
|
||||
readonly onDidChangeTabs: Event<void> = this._onDidChangeTabs.event;
|
||||
|
||||
private _tabs: IEditorTab[] = [];
|
||||
|
||||
get tabs(): readonly IEditorTab[] {
|
||||
return this._tabs;
|
||||
}
|
||||
|
||||
$acceptEditorTabs(tabs: IEditorTabDto[]): void {
|
||||
this._tabs = tabs.map(dto => {
|
||||
return {
|
||||
name: dto.name,
|
||||
group: dto.group,
|
||||
resource: URI.revive(dto.resource)
|
||||
};
|
||||
});
|
||||
this._onDidChangeTabs.fire();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { originalFSPath, joinPath } from 'vs/base/common/resources';
|
||||
import { Barrier, timeout } from 'vs/base/common/async';
|
||||
import { dispose, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -35,6 +36,8 @@ import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelServ
|
||||
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IExtensionActivationHost, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains';
|
||||
import { ExtHostSecretState, IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
|
||||
import { ExtensionSecrets } from 'vs/workbench/api/common/extHostSecrets';
|
||||
|
||||
interface ITestRunner {
|
||||
/** Old test runner API, as exported from `vscode/lib/testrunner` */
|
||||
@@ -50,7 +53,7 @@ export const IHostUtils = createDecorator<IHostUtils>('IHostUtils');
|
||||
|
||||
export interface IHostUtils {
|
||||
readonly _serviceBrand: undefined;
|
||||
exit(code?: number): void;
|
||||
exit(code: number): void;
|
||||
exists(path: string): Promise<boolean>;
|
||||
realpath(path: string): Promise<string>;
|
||||
}
|
||||
@@ -94,6 +97,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
private readonly _readyToRunExtensions: Barrier;
|
||||
protected readonly _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _storage: ExtHostStorage;
|
||||
private readonly _secretState: ExtHostSecretState;
|
||||
private readonly _storagePath: IExtensionStoragePaths;
|
||||
private readonly _activator: ExtensionsActivator;
|
||||
private _extensionPathIndex: Promise<TernarySearchTree<string, IExtensionDescription>> | null;
|
||||
@@ -115,7 +119,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService,
|
||||
@IExtensionStoragePaths storagePath: IExtensionStoragePaths,
|
||||
@IExtHostTunnelService extHostTunnelService: IExtHostTunnelService,
|
||||
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService
|
||||
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService,
|
||||
) {
|
||||
super();
|
||||
this._hostUtils = hostUtils;
|
||||
@@ -138,10 +142,12 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
this._readyToRunExtensions = new Barrier();
|
||||
this._registry = new ExtensionDescriptionRegistry(this._initData.extensions);
|
||||
this._storage = new ExtHostStorage(this._extHostContext);
|
||||
this._secretState = new ExtHostSecretState(this._extHostContext);
|
||||
this._storagePath = storagePath;
|
||||
|
||||
this._instaService = instaService.createChild(new ServiceCollection(
|
||||
[IExtHostStorage, this._storage]
|
||||
[IExtHostStorage, this._storage],
|
||||
[IExtHostSecretState, this._secretState]
|
||||
));
|
||||
|
||||
const hostExtensions = new Set<string>();
|
||||
@@ -184,6 +190,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
this._almostReadyToRunExtensions.open();
|
||||
|
||||
await this._extHostWorkspace.waitForInitializeCall();
|
||||
performance.mark('code/extHost/ready');
|
||||
this._readyToStartExtensionHost.open();
|
||||
|
||||
if (this._initData.autoStart) {
|
||||
@@ -362,10 +369,18 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
|
||||
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
|
||||
return Promise.all([
|
||||
<<<<<<< HEAD
|
||||
this._loadCommonJSModule<IExtensionModule>(joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder, !extensionDescription.browser),
|
||||
=======
|
||||
this._loadCommonJSModule<IExtensionModule>(extensionDescription.identifier, joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder),
|
||||
>>>>>>> 89b6e0164fa770333755b11504e19a4232b1a2d4
|
||||
this._loadExtensionContext(extensionDescription)
|
||||
]).then(values => {
|
||||
performance.mark(`code/extHost/willActivateExtension/${extensionDescription.identifier.value}`);
|
||||
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder);
|
||||
}).then((activatedExtension) => {
|
||||
performance.mark(`code/extHost/didActivateExtension/${extensionDescription.identifier.value}`);
|
||||
return activatedExtension;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -373,6 +388,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
|
||||
const globalState = new ExtensionGlobalMemento(extensionDescription, this._storage);
|
||||
const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage);
|
||||
const secrets = new ExtensionSecrets(extensionDescription, this._secretState);
|
||||
const extensionMode = extensionDescription.isUnderDevelopment
|
||||
? (this._initData.environment.extensionTestsLocationURI ? ExtensionMode.Test : ExtensionMode.Development)
|
||||
: ExtensionMode.Production;
|
||||
@@ -388,6 +404,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
return Object.freeze<vscode.ExtensionContext>({
|
||||
globalState,
|
||||
workspaceState,
|
||||
secrets,
|
||||
subscriptions: [],
|
||||
get extensionUri() { return extensionDescription.extensionLocation; },
|
||||
get extensionPath() { return extensionDescription.extensionLocation.fsPath; },
|
||||
@@ -456,6 +473,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
}
|
||||
|
||||
private _activateAllStartupFinished(): void {
|
||||
// startup is considered finished
|
||||
this._mainThreadExtensionsProxy.$setPerformanceMarks(performance.getMarks());
|
||||
|
||||
for (const desc of this._registry.getAllExtensionDescriptions()) {
|
||||
if (desc.activationEvents) {
|
||||
for (const activationEvent of desc.activationEvents) {
|
||||
@@ -541,7 +561,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
let testRunner: ITestRunner | INewTestRunner | undefined;
|
||||
let requireError: Error | undefined;
|
||||
try {
|
||||
testRunner = await this._loadCommonJSModule(URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false));
|
||||
testRunner = await this._loadCommonJSModule(null, URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false));
|
||||
} catch (error) {
|
||||
requireError = error;
|
||||
}
|
||||
@@ -586,11 +606,17 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
}
|
||||
|
||||
private _testRunnerExit(code: number): void {
|
||||
this._logService.info(`extension host terminating: test runner requested exit with code ${code}`);
|
||||
this._logService.flush();
|
||||
|
||||
// wait at most 5000ms for the renderer to confirm our exit request and for the renderer socket to drain
|
||||
// (this is to ensure all outstanding messages reach the renderer)
|
||||
const exitPromise = this._mainThreadExtensionsProxy.$onExtensionHostExit(code);
|
||||
const drainPromise = this._extHostContext.drain();
|
||||
Promise.race([Promise.all([exitPromise, drainPromise]), timeout(5000)]).then(() => {
|
||||
this._logService.info(`exiting with code ${code}`);
|
||||
this._logService.flush();
|
||||
|
||||
this._hostUtils.exit(code);
|
||||
});
|
||||
}
|
||||
@@ -644,7 +670,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
}
|
||||
|
||||
try {
|
||||
performance.mark(`code/extHost/willResolveAuthority/${authorityPrefix}`);
|
||||
const result = await resolver.resolve(remoteAuthority, { resolveAttempt });
|
||||
performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`);
|
||||
this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver));
|
||||
|
||||
// Split merged API result into separate authority/options
|
||||
@@ -667,6 +695,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
performance.mark(`code/extHost/didResolveAuthorityError/${authorityPrefix}`);
|
||||
if (err instanceof RemoteAuthorityResolverError) {
|
||||
return {
|
||||
type: 'error',
|
||||
@@ -754,7 +783,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
|
||||
protected abstract _beforeAlmostReadyToRunExtensions(): Promise<void>;
|
||||
protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined;
|
||||
<<<<<<< HEAD
|
||||
protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder, isRemote?: boolean): Promise<T>;
|
||||
=======
|
||||
protected abstract _loadCommonJSModule<T>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
|
||||
>>>>>>> 89b6e0164fa770333755b11504e19a4232b1a2d4
|
||||
public abstract $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IRelativePattern, parse } from 'vs/base/common/glob';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import type * as vscode from 'vscode';
|
||||
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, SourceTargetPair, IWorkspaceEditDto, MainThreadBulkEditsShape } from './extHost.protocol';
|
||||
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, SourceTargetPair, IWorkspaceEditDto, IWillRunFileOperationParticipation } from './extHost.protocol';
|
||||
import * as typeConverter from './extHostTypeConverters';
|
||||
import { Disposable, WorkspaceEdit } from './extHostTypes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
@@ -122,8 +122,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape = mainContext.getProxy(MainContext.MainThreadBulkEdits)
|
||||
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors
|
||||
) {
|
||||
//
|
||||
}
|
||||
@@ -178,24 +177,21 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
|
||||
};
|
||||
}
|
||||
|
||||
async $onWillRunFileOperation(operation: FileOperation, files: SourceTargetPair[], undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise<any> {
|
||||
async $onWillRunFileOperation(operation: FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined> {
|
||||
switch (operation) {
|
||||
case FileOperation.MOVE:
|
||||
await this._fireWillEvent(this._onWillRenameFile, { files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }, undoRedoGroupId, timeout, token);
|
||||
break;
|
||||
return await this._fireWillEvent(this._onWillRenameFile, { files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }, timeout, token);
|
||||
case FileOperation.DELETE:
|
||||
await this._fireWillEvent(this._onWillDeleteFile, { files: files.map(f => URI.revive(f.target)) }, undoRedoGroupId, timeout, token);
|
||||
break;
|
||||
return await this._fireWillEvent(this._onWillDeleteFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token);
|
||||
case FileOperation.CREATE:
|
||||
await this._fireWillEvent(this._onWillCreateFile, { files: files.map(f => URI.revive(f.target)) }, undoRedoGroupId, timeout, token);
|
||||
break;
|
||||
default:
|
||||
//ignore, dont send
|
||||
return await this._fireWillEvent(this._onWillCreateFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async _fireWillEvent<E extends IWaitUntil>(emitter: AsyncEmitter<E>, data: Omit<E, 'waitUntil'>, undoRedoGroupId: number | undefined, timeout: number, token: CancellationToken): Promise<any> {
|
||||
private async _fireWillEvent<E extends IWaitUntil>(emitter: AsyncEmitter<E>, data: Omit<E, 'waitUntil'>, timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined> {
|
||||
|
||||
const extensionNames = new Set<string>();
|
||||
const edits: WorkspaceEdit[] = [];
|
||||
|
||||
await emitter.fireAsync(data, token, async (thenable, listener) => {
|
||||
@@ -204,25 +200,28 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
|
||||
const result = await Promise.resolve(thenable);
|
||||
if (result instanceof WorkspaceEdit) {
|
||||
edits.push(result);
|
||||
extensionNames.add((<IExtensionListener<E>>listener).extension.displayName ?? (<IExtensionListener<E>>listener).extension.identifier.value);
|
||||
}
|
||||
|
||||
if (Date.now() - now > timeout) {
|
||||
this._logService.warn('SLOW file-participant', (<IExtensionListener<E>>listener).extension?.identifier);
|
||||
this._logService.warn('SLOW file-participant', (<IExtensionListener<E>>listener).extension.identifier);
|
||||
}
|
||||
});
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (edits.length > 0) {
|
||||
// concat all WorkspaceEdits collected via waitUntil-call and apply them in one go.
|
||||
const dto: IWorkspaceEditDto = { edits: [] };
|
||||
for (let edit of edits) {
|
||||
let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
|
||||
dto.edits = dto.edits.concat(edits);
|
||||
}
|
||||
return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit(dto, undoRedoGroupId);
|
||||
if (edits.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// concat all WorkspaceEdits collected via waitUntil-call and send them over to the renderer
|
||||
const dto: IWorkspaceEditDto = { edits: [] };
|
||||
for (let edit of edits) {
|
||||
let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
|
||||
dto.edits = dto.edits.concat(edits);
|
||||
}
|
||||
return { edit: dto, extensionNames: Array.from(extensionNames) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,12 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto';
|
||||
import { encodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
|
||||
import { Cache } from './cache';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { CancellationError } from 'vs/base/common/errors';
|
||||
|
||||
// --- adapter
|
||||
|
||||
@@ -1062,6 +1063,20 @@ class SignatureHelpAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
class InlineHintsAdapter {
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.InlineHintsProvider,
|
||||
) { }
|
||||
|
||||
provideInlineHints(resource: URI, range: IRange, token: CancellationToken): Promise<extHostProtocol.IInlineHintsDto | undefined> {
|
||||
const doc = this._documents.getDocument(resource);
|
||||
return asPromise(() => this._provider.provideInlineHints(doc, typeConvert.Range.to(range), token)).then(value => {
|
||||
return value ? { hints: value.map(typeConvert.InlineHint.from) } : undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class LinkProviderAdapter {
|
||||
|
||||
private _cache = new Cache<vscode.DocumentLink>('DocumentLink');
|
||||
@@ -1320,7 +1335,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov
|
||||
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
|
||||
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
|
||||
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter
|
||||
| LinkedEditingRangeAdapter;
|
||||
| LinkedEditingRangeAdapter | InlineHintsAdapter;
|
||||
|
||||
class AdapterData {
|
||||
constructor(
|
||||
@@ -1403,7 +1418,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
return ExtHostLanguageFeatures._handlePool++;
|
||||
}
|
||||
|
||||
private _withAdapter<A, R>(handle: number, ctor: { new(...args: any[]): A; }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise<R>, fallbackValue: R): Promise<R> {
|
||||
private _withAdapter<A, R>(handle: number, ctor: { new(...args: any[]): A; }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise<R>, fallbackValue: R, allowCancellationError: boolean = false): Promise<R> {
|
||||
const data = this._adapter.get(handle);
|
||||
if (!data) {
|
||||
return Promise.resolve(fallbackValue);
|
||||
@@ -1421,8 +1436,11 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
Promise.resolve(p).then(
|
||||
() => this._logService.trace(`[${extension.identifier.value}] provider DONE after ${Date.now() - t1}ms`),
|
||||
err => {
|
||||
this._logService.error(`[${extension.identifier.value}] provider FAILED`);
|
||||
this._logService.error(err);
|
||||
const isExpectedError = allowCancellationError && (err instanceof CancellationError);
|
||||
if (!isExpectedError) {
|
||||
this._logService.error(`[${extension.identifier.value}] provider FAILED`);
|
||||
this._logService.error(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1711,7 +1729,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
}
|
||||
|
||||
$provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise<VSBuffer | null> {
|
||||
return this._withAdapter(handle, DocumentSemanticTokensAdapter, adapter => adapter.provideDocumentSemanticTokens(URI.revive(resource), previousResultId, token), null);
|
||||
return this._withAdapter(handle, DocumentSemanticTokensAdapter, adapter => adapter.provideDocumentSemanticTokens(URI.revive(resource), previousResultId, token), null, true);
|
||||
}
|
||||
|
||||
$releaseDocumentSemanticTokens(handle: number, semanticColoringResultId: number): void {
|
||||
@@ -1725,7 +1743,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
}
|
||||
|
||||
$provideDocumentRangeSemanticTokens(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise<VSBuffer | null> {
|
||||
return this._withAdapter(handle, DocumentRangeSemanticTokensAdapter, adapter => adapter.provideDocumentRangeSemanticTokens(URI.revive(resource), range, token), null);
|
||||
return this._withAdapter(handle, DocumentRangeSemanticTokensAdapter, adapter => adapter.provideDocumentRangeSemanticTokens(URI.revive(resource), range, token), null, true);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -1770,6 +1788,27 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.releaseSignatureHelp(id), undefined);
|
||||
}
|
||||
|
||||
// --- inline hints
|
||||
|
||||
registerInlineHintsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineHintsProvider): vscode.Disposable {
|
||||
|
||||
const eventHandle = typeof provider.onDidChangeInlineHints === 'function' ? this._nextHandle() : undefined;
|
||||
const handle = this._addNewAdapter(new InlineHintsAdapter(this._documents, provider), extension);
|
||||
|
||||
this._proxy.$registerInlineHintsProvider(handle, this._transformDocumentSelector(selector), eventHandle);
|
||||
let result = this._createDisposable(handle);
|
||||
|
||||
if (eventHandle !== undefined) {
|
||||
const subscription = provider.onDidChangeInlineHints!(_ => this._proxy.$emitInlineHintsEvent(eventHandle));
|
||||
result = Disposable.from(result, subscription);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
$provideInlineHints(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise<extHostProtocol.IInlineHintsDto | undefined> {
|
||||
return this._withAdapter(handle, InlineHintsAdapter, adapter => adapter.provideInlineHints(URI.revive(resource), range, token), undefined);
|
||||
}
|
||||
|
||||
// --- links
|
||||
|
||||
registerDocumentLinkProvider(extension: IExtensionDescription | undefined, selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable {
|
||||
@@ -1882,7 +1921,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
return {
|
||||
beforeText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.beforeText),
|
||||
afterText: onEnterRule.afterText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText) : undefined,
|
||||
oneLineAboveText: onEnterRule.oneLineAboveText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.oneLineAboveText) : undefined,
|
||||
previousLineText: onEnterRule.previousLineText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.previousLineText) : undefined,
|
||||
action: onEnterRule.action
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type * as vscode from 'vscode';
|
||||
import { MainContext, MainThreadMessageServiceShape, MainThreadMessageOptions, IMainContext } from './extHost.protocol';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
function isMessageItem(item: any): item is vscode.MessageItem {
|
||||
return item && item.title;
|
||||
@@ -37,9 +38,14 @@ export class ExtHostMessageService {
|
||||
items = [optionsOrFirstItem, ...rest];
|
||||
} else {
|
||||
options.modal = optionsOrFirstItem && optionsOrFirstItem.modal;
|
||||
options.useCustom = optionsOrFirstItem && optionsOrFirstItem.useCustom;
|
||||
items = rest;
|
||||
}
|
||||
|
||||
if (options.useCustom) {
|
||||
checkProposedApiEnabled(extension);
|
||||
}
|
||||
|
||||
const commands: { title: string; isCloseAffordance: boolean; handle: number; }[] = [];
|
||||
|
||||
for (let handle = 0; handle < items.length; handle++) {
|
||||
|
||||
@@ -78,8 +78,8 @@ export interface ExtHostNotebookOutputRenderingHandler {
|
||||
}
|
||||
|
||||
export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
private _kernelToId = new Map<vscode.NotebookKernel, string>();
|
||||
private _idToKernel = new Map<string, vscode.NotebookKernel>();
|
||||
private _kernelToFriendlyId = new Map<vscode.NotebookKernel, string>();
|
||||
private _friendlyIdToKernel = new Map<string, vscode.NotebookKernel>();
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadNotebookShape,
|
||||
private readonly _handle: number,
|
||||
@@ -101,24 +101,25 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
|
||||
const newMap = new Map<vscode.NotebookKernel, string>();
|
||||
let kernel_unique_pool = 0;
|
||||
const kernelIdCache = new Set<string>();
|
||||
const kernelFriendlyIdCache = new Set<string>();
|
||||
|
||||
const transformedData: INotebookKernelInfoDto2[] = data.map(kernel => {
|
||||
let id = this._kernelToId.get(kernel);
|
||||
if (id === undefined) {
|
||||
if (kernel.id && kernelIdCache.has(kernel.id)) {
|
||||
id = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`;
|
||||
let friendlyId = this._kernelToFriendlyId.get(kernel);
|
||||
if (friendlyId === undefined) {
|
||||
if (kernel.id && kernelFriendlyIdCache.has(kernel.id)) {
|
||||
friendlyId = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`;
|
||||
} else {
|
||||
id = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`;
|
||||
friendlyId = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`;
|
||||
}
|
||||
|
||||
this._kernelToId.set(kernel, id);
|
||||
this._kernelToFriendlyId.set(kernel, friendlyId);
|
||||
}
|
||||
|
||||
newMap.set(kernel, id);
|
||||
newMap.set(kernel, friendlyId);
|
||||
|
||||
return {
|
||||
id,
|
||||
id: kernel.id,
|
||||
friendlyId: friendlyId,
|
||||
label: kernel.label,
|
||||
extension: this._extension.identifier,
|
||||
extensionLocation: this._extension.extensionLocation,
|
||||
@@ -129,22 +130,22 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
};
|
||||
});
|
||||
|
||||
this._kernelToId = newMap;
|
||||
this._kernelToFriendlyId = newMap;
|
||||
|
||||
this._idToKernel.clear();
|
||||
this._kernelToId.forEach((value, key) => {
|
||||
this._idToKernel.set(value, key);
|
||||
this._friendlyIdToKernel.clear();
|
||||
this._kernelToFriendlyId.forEach((value, key) => {
|
||||
this._friendlyIdToKernel.set(value, key);
|
||||
});
|
||||
|
||||
return transformedData;
|
||||
}
|
||||
|
||||
getKernel(kernelId: string) {
|
||||
return this._idToKernel.get(kernelId);
|
||||
getKernelByFriendlyId(kernelId: string) {
|
||||
return this._friendlyIdToKernel.get(kernelId);
|
||||
}
|
||||
|
||||
async resolveNotebook(kernelId: string, document: ExtHostNotebookDocument, webview: vscode.NotebookCommunication, token: CancellationToken) {
|
||||
const kernel = this._idToKernel.get(kernelId);
|
||||
const kernel = this._friendlyIdToKernel.get(kernelId);
|
||||
|
||||
if (kernel && this._provider.resolveKernel) {
|
||||
return this._provider.resolveKernel(kernel, document.notebookDocument, webview, token);
|
||||
@@ -152,7 +153,7 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
}
|
||||
|
||||
async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) {
|
||||
const kernel = this._idToKernel.get(kernelId);
|
||||
const kernel = this._friendlyIdToKernel.get(kernelId);
|
||||
|
||||
if (!kernel) {
|
||||
return;
|
||||
@@ -166,7 +167,7 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
}
|
||||
|
||||
async cancelNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) {
|
||||
const kernel = this._idToKernel.get(kernelId);
|
||||
const kernel = this._friendlyIdToKernel.get(kernelId);
|
||||
|
||||
if (!kernel) {
|
||||
return;
|
||||
@@ -580,10 +581,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
|
||||
this._outputDisplayOrder = displayOrder;
|
||||
}
|
||||
|
||||
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined; }) {
|
||||
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }) {
|
||||
if (event.providerHandle !== undefined) {
|
||||
this._withAdapter(event.providerHandle, event.uri, async (adapter, document) => {
|
||||
const kernel = event.kernelId ? adapter.getKernel(event.kernelId) : undefined;
|
||||
const kernel = event.kernelFriendlyId ? adapter.getKernelByFriendlyId(event.kernelFriendlyId) : undefined;
|
||||
this._editors.forEach(editor => {
|
||||
if (editor.editor.notebookData === document) {
|
||||
editor.editor._acceptKernel(kernel);
|
||||
|
||||
@@ -113,14 +113,17 @@ export class ExtHostCell extends Disposable {
|
||||
get cell(): vscode.NotebookCell {
|
||||
if (!this._cell) {
|
||||
const that = this;
|
||||
const document = this._extHostDocument.getDocument(this.uri)!.document;
|
||||
const data = this._extHostDocument.getDocument(this.uri);
|
||||
if (!data) {
|
||||
throw new Error(`MISSING extHostDocument for notebook cell: ${this.uri}`);
|
||||
}
|
||||
this._cell = Object.freeze({
|
||||
get index() { return that._notebook.getCellIndex(that); },
|
||||
notebook: that._notebook.notebookDocument,
|
||||
uri: that.uri,
|
||||
cellKind: this._cellData.cellKind,
|
||||
document,
|
||||
get language() { return document.languageId; },
|
||||
document: data.document,
|
||||
get language() { return data!.document.languageId; },
|
||||
get outputs() { return that._outputs; },
|
||||
set outputs(value) { that._updateOutputs(value); },
|
||||
get metadata() { return that._metadata; },
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { MainThreadTelemetryShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
@@ -52,7 +53,9 @@ export abstract class RequireInterceptor {
|
||||
|
||||
this._installInterceptor();
|
||||
|
||||
performance.mark('code/extHost/willWaitForConfig');
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
performance.mark('code/extHost/didWaitForConfig');
|
||||
const extensionPaths = await this._extHostExtensionService.getExtensionPathIndex();
|
||||
|
||||
this.register(new VSCodeNodeModuleFactory(this._apiFactory, extensionPaths, this._extensionRegistry, configProvider, this._logService));
|
||||
|
||||
43
lib/vscode/src/vs/workbench/api/common/extHostSecrets.ts
Normal file
43
lib/vscode/src/vs/workbench/api/common/extHostSecrets.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
import { ExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
|
||||
export class ExtensionSecrets implements vscode.SecretStorage {
|
||||
|
||||
protected readonly _id: string;
|
||||
readonly #secretState: ExtHostSecretState;
|
||||
|
||||
private _onDidChange = new Emitter<vscode.SecretStorageChangeEvent>();
|
||||
readonly onDidChange: Event<vscode.SecretStorageChangeEvent> = this._onDidChange.event;
|
||||
|
||||
|
||||
constructor(extensionDescription: IExtensionDescription, secretState: ExtHostSecretState) {
|
||||
this._id = ExtensionIdentifier.toKey(extensionDescription.identifier);
|
||||
this.#secretState = secretState;
|
||||
|
||||
this.#secretState.onDidChangePassword(e => {
|
||||
if (e.extensionId === this._id) {
|
||||
this._onDidChange.fire({ key: e.key });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get(key: string): Promise<string | undefined> {
|
||||
return this.#secretState.get(this._id, key);
|
||||
}
|
||||
|
||||
store(key: string, value: string): Promise<void> {
|
||||
return this.#secretState.store(this._id, key, value);
|
||||
}
|
||||
|
||||
delete(key: string): Promise<void> {
|
||||
return this.#secretState.delete(this._id, key);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,6 @@ import { MainContext, MainThreadStatusBarShape, IMainContext, ICommandDto } from
|
||||
import { localize } from 'vs/nls';
|
||||
import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
private static ID_GEN = 0;
|
||||
@@ -48,9 +46,8 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
private _proxy: MainThreadStatusBarShape;
|
||||
private _commands: CommandsConverter;
|
||||
private _accessibilityInformation?: vscode.AccessibilityInformation;
|
||||
private _extension?: IExtensionDescription;
|
||||
|
||||
constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation, extension?: IExtensionDescription) {
|
||||
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;
|
||||
@@ -59,7 +56,6 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
this._alignment = alignment;
|
||||
this._priority = priority;
|
||||
this._accessibilityInformation = accessibilityInformation;
|
||||
this._extension = extension;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
@@ -87,10 +83,6 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
}
|
||||
|
||||
public get backgroundColor(): ThemeColor | undefined {
|
||||
if (this._extension) {
|
||||
checkProposedApiEnabled(this._extension);
|
||||
}
|
||||
|
||||
return this._backgroundColor;
|
||||
}
|
||||
|
||||
@@ -118,10 +110,6 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
|
||||
}
|
||||
|
||||
public set backgroundColor(color: ThemeColor | undefined) {
|
||||
if (this._extension) {
|
||||
checkProposedApiEnabled(this._extension);
|
||||
}
|
||||
|
||||
if (color && !ExtHostStatusBarEntry.ALLOWED_BACKGROUND_COLORS.has(color.id)) {
|
||||
color = undefined;
|
||||
}
|
||||
@@ -248,8 +236,8 @@ export class ExtHostStatusBar {
|
||||
this._statusMessage = new StatusBarMessage(this);
|
||||
}
|
||||
|
||||
createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation, extension?: IExtensionDescription): vscode.StatusBarItem {
|
||||
return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority, accessibilityInformation, extension);
|
||||
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 {
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ITerminalChildProcess, EXT_HOST_CREATION_DELAY, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { ITerminalChildProcess, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering';
|
||||
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -21,6 +20,7 @@ import { localize } from 'vs/nls';
|
||||
import { NotSupportedError } from 'vs/base/common/errors';
|
||||
import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable {
|
||||
|
||||
@@ -47,63 +47,8 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID
|
||||
|
||||
export const IExtHostTerminalService = createDecorator<IExtHostTerminalService>('IExtHostTerminalService');
|
||||
|
||||
export class BaseExtHostTerminal {
|
||||
public _id: number | undefined;
|
||||
protected _idPromise: Promise<number>;
|
||||
private _idPromiseComplete: ((value: number) => any) | undefined;
|
||||
export class ExtHostTerminal implements vscode.Terminal {
|
||||
private _disposed: boolean = false;
|
||||
private _queuedRequests: ApiRequest[] = [];
|
||||
|
||||
constructor(
|
||||
protected _proxy: MainThreadTerminalServiceShape,
|
||||
id?: number
|
||||
) {
|
||||
this._idPromise = new Promise<number>(c => {
|
||||
if (id !== undefined) {
|
||||
this._id = id;
|
||||
c(id);
|
||||
} else {
|
||||
this._idPromiseComplete = c;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._disposed) {
|
||||
this._disposed = true;
|
||||
this._queueApiRequest(this._proxy.$dispose, []);
|
||||
}
|
||||
}
|
||||
|
||||
protected _checkDisposed() {
|
||||
if (this._disposed) {
|
||||
throw new Error('Terminal has already been disposed');
|
||||
}
|
||||
}
|
||||
|
||||
protected _queueApiRequest(callback: (...args: any[]) => void, args: any[]): void {
|
||||
const request: ApiRequest = new ApiRequest(callback, args);
|
||||
if (!this._id) {
|
||||
this._queuedRequests.push(request);
|
||||
return;
|
||||
}
|
||||
request.run(this._proxy, this._id);
|
||||
}
|
||||
|
||||
public _runQueuedRequests(id: number): void {
|
||||
this._id = id;
|
||||
if (this._idPromiseComplete) {
|
||||
this._idPromiseComplete(id);
|
||||
this._idPromiseComplete = undefined;
|
||||
}
|
||||
this._queuedRequests.forEach((r) => {
|
||||
r.run(this._proxy, id);
|
||||
});
|
||||
this._queuedRequests.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal {
|
||||
private _pidPromise: Promise<number | undefined>;
|
||||
private _cols: number | undefined;
|
||||
private _pidPromiseComplete: ((value: number | undefined) => any) | undefined;
|
||||
@@ -113,12 +58,11 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
public isOpen: boolean = false;
|
||||
|
||||
constructor(
|
||||
proxy: MainThreadTerminalServiceShape,
|
||||
private _proxy: MainThreadTerminalServiceShape,
|
||||
public _id: TerminalIdentifier,
|
||||
private readonly _creationOptions: vscode.TerminalOptions | vscode.ExtensionTerminalOptions,
|
||||
private _name?: string,
|
||||
id?: number
|
||||
) {
|
||||
super(proxy, id);
|
||||
this._creationOptions = Object.freeze(this._creationOptions);
|
||||
this._pidPromise = new Promise<number | undefined>(c => this._pidPromiseComplete = c);
|
||||
}
|
||||
@@ -133,16 +77,35 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
hideFromUser?: boolean,
|
||||
isFeatureTerminal?: boolean
|
||||
): Promise<void> {
|
||||
const result = await this._proxy.$createTerminal({ name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal });
|
||||
this._name = result.name;
|
||||
this._runQueuedRequests(result.id);
|
||||
if (typeof this._id !== 'string') {
|
||||
throw new Error('Terminal has already been created');
|
||||
}
|
||||
await this._proxy.$createTerminal(this._id, { name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal });
|
||||
}
|
||||
|
||||
public async createExtensionTerminal(): Promise<number> {
|
||||
const result = await this._proxy.$createTerminal({ name: this._name, isExtensionTerminal: true });
|
||||
this._name = result.name;
|
||||
this._runQueuedRequests(result.id);
|
||||
return result.id;
|
||||
if (typeof this._id !== 'string') {
|
||||
throw new Error('Terminal has already been created');
|
||||
}
|
||||
await this._proxy.$createTerminal(this._id, { name: this._name, isExtensionTerminal: true });
|
||||
// At this point, the id has been set via `$acceptTerminalOpened`
|
||||
if (typeof this._id === 'string') {
|
||||
throw new Error('Terminal creation failed');
|
||||
}
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._disposed) {
|
||||
this._disposed = true;
|
||||
this._proxy.$dispose(this._id);
|
||||
}
|
||||
}
|
||||
|
||||
private _checkDisposed() {
|
||||
if (this._disposed) {
|
||||
throw new Error('Terminal has already been disposed');
|
||||
}
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
@@ -194,17 +157,17 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
|
||||
public sendText(text: string, addNewLine: boolean = true): void {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$sendText, [text, addNewLine]);
|
||||
this._proxy.$sendText(this._id, text, addNewLine);
|
||||
}
|
||||
|
||||
public show(preserveFocus: boolean): void {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$show, [preserveFocus]);
|
||||
this._proxy.$show(this._id, preserveFocus);
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$hide, []);
|
||||
this._proxy.$hide(this._id);
|
||||
}
|
||||
|
||||
public _setProcessId(processId: number | undefined): void {
|
||||
@@ -223,20 +186,6 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
}
|
||||
}
|
||||
|
||||
class ApiRequest {
|
||||
private _callback: (...args: any[]) => void;
|
||||
private _args: any[];
|
||||
|
||||
constructor(callback: (...args: any[]) => void, args: any[]) {
|
||||
this._callback = callback;
|
||||
this._args = args;
|
||||
}
|
||||
|
||||
public run(proxy: MainThreadTerminalServiceShape, id: number) {
|
||||
this._callback.apply(proxy, [id].concat(this._args));
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostPseudoterminal implements ITerminalChildProcess {
|
||||
private readonly _onProcessData = new Emitter<string>();
|
||||
public readonly onProcessData: Event<string> = this._onProcessData.event;
|
||||
@@ -271,6 +220,11 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
|
||||
}
|
||||
}
|
||||
|
||||
acknowledgeDataEvent(charCount: number): void {
|
||||
// No-op, flow control is not supported in extension owned terminals. If this is ever
|
||||
// implemented it will need new pause and resume VS Code APIs.
|
||||
}
|
||||
|
||||
getInitialCwd(): Promise<string> {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
@@ -296,6 +250,11 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
|
||||
}
|
||||
|
||||
this._pty.open(initialDimensions ? initialDimensions : undefined);
|
||||
|
||||
if (this._pty.setDimensions && initialDimensions) {
|
||||
this._pty.setDimensions(initialDimensions);
|
||||
}
|
||||
|
||||
this._onProcessReady.fire({ pid: -1, cwd: '' });
|
||||
}
|
||||
}
|
||||
@@ -370,7 +329,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void;
|
||||
|
||||
public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal {
|
||||
const terminal = new ExtHostTerminal(this._proxy, options, options.name);
|
||||
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
|
||||
const p = new ExtHostPseudoterminal(options.pty);
|
||||
terminal.createExtensionTerminal().then(id => {
|
||||
const disposable = this._setupExtHostProcessListeners(id, p);
|
||||
@@ -381,7 +340,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
}
|
||||
|
||||
public attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void {
|
||||
const terminal = this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (!terminal) {
|
||||
throw new Error(`Cannot resolve terminal with id ${id} for virtual process`);
|
||||
}
|
||||
@@ -399,7 +358,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
}
|
||||
return;
|
||||
}
|
||||
const terminal = await this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
this._activeTerminal = terminal;
|
||||
if (original !== this._activeTerminal) {
|
||||
@@ -409,14 +368,14 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
}
|
||||
|
||||
public async $acceptTerminalProcessData(id: number, data: string): Promise<void> {
|
||||
const terminal = await this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
this._onDidWriteTerminalData.fire({ terminal, data });
|
||||
}
|
||||
}
|
||||
|
||||
public async $acceptTerminalDimensions(id: number, cols: number, rows: number): Promise<void> {
|
||||
const terminal = await this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
if (terminal.setDimensions(cols, rows)) {
|
||||
this._onDidChangeTerminalDimensions.fire({
|
||||
@@ -428,23 +387,19 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
}
|
||||
|
||||
public async $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): Promise<void> {
|
||||
await this._getTerminalByIdEventually(id);
|
||||
|
||||
// Extension pty terminal only - when virtual process resize fires it means that the
|
||||
// terminal's maximum dimensions changed
|
||||
this._terminalProcesses.get(id)?.resize(cols, rows);
|
||||
}
|
||||
|
||||
public async $acceptTerminalTitleChange(id: number, name: string): Promise<void> {
|
||||
await this._getTerminalByIdEventually(id);
|
||||
const extHostTerminal = this._getTerminalObjectById(this.terminals, id);
|
||||
if (extHostTerminal) {
|
||||
extHostTerminal.name = name;
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
terminal.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public async $acceptTerminalClosed(id: number, exitCode: number | undefined): Promise<void> {
|
||||
await this._getTerminalByIdEventually(id);
|
||||
const index = this._getTerminalObjectIndexById(this.terminals, id);
|
||||
if (index !== null) {
|
||||
const terminal = this._terminals.splice(index, 1)[0];
|
||||
@@ -453,13 +408,17 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
}
|
||||
}
|
||||
|
||||
public $acceptTerminalOpened(id: number, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void {
|
||||
const index = this._getTerminalObjectIndexById(this._terminals, id);
|
||||
if (index !== null) {
|
||||
// The terminal has already been created (via createTerminal*), only fire the event
|
||||
this._onDidOpenTerminal.fire(this.terminals[index]);
|
||||
this.terminals[index].isOpen = true;
|
||||
return;
|
||||
public $acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void {
|
||||
if (extHostTerminalId) {
|
||||
// Resolve with the renderer generated id
|
||||
const index = this._getTerminalObjectIndexById(this._terminals, extHostTerminalId);
|
||||
if (index !== null) {
|
||||
// The terminal has already been created (via createTerminal*), only fire the event
|
||||
this.terminals[index]._id = id;
|
||||
this._onDidOpenTerminal.fire(this.terminals[index]);
|
||||
this.terminals[index].isOpen = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const creationOptions: vscode.TerminalOptions = {
|
||||
@@ -470,14 +429,14 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
env: shellLaunchConfigDto.env,
|
||||
hideFromUser: shellLaunchConfigDto.hideFromUser
|
||||
};
|
||||
const terminal = new ExtHostTerminal(this._proxy, creationOptions, name, id);
|
||||
const terminal = new ExtHostTerminal(this._proxy, id, creationOptions, name);
|
||||
this._terminals.push(terminal);
|
||||
this._onDidOpenTerminal.fire(terminal);
|
||||
terminal.isOpen = true;
|
||||
}
|
||||
|
||||
public async $acceptTerminalProcessId(id: number, processId: number): Promise<void> {
|
||||
const terminal = await this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
terminal._setProcessId(processId);
|
||||
}
|
||||
@@ -486,7 +445,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined> {
|
||||
// Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call
|
||||
// Pseudoterminal.start
|
||||
const terminal = await this._getTerminalByIdEventually(id);
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (!terminal) {
|
||||
return { message: localize('launchFail.idMissingOnExtHost', "Could not find the terminal with id {0} on the extension host", id) };
|
||||
}
|
||||
@@ -539,6 +498,10 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
return disposables;
|
||||
}
|
||||
|
||||
public $acceptProcessAckDataEvent(id: number, charCount: number): void {
|
||||
this._terminalProcesses.get(id)?.acknowledgeDataEvent(charCount);
|
||||
}
|
||||
|
||||
public $acceptProcessInput(id: number, data: string): void {
|
||||
this._terminalProcesses.get(id)?.input(data);
|
||||
}
|
||||
@@ -670,32 +633,6 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
this._proxy.$sendProcessExit(id, exitCode);
|
||||
}
|
||||
|
||||
// TODO: This could be improved by using a single promise and resolve it when the terminal is ready
|
||||
private _getTerminalByIdEventually(id: number, retries: number = 5): Promise<ExtHostTerminal | undefined> {
|
||||
if (!this._getTerminalPromises[id]) {
|
||||
this._getTerminalPromises[id] = this._createGetTerminalPromise(id, retries);
|
||||
}
|
||||
return this._getTerminalPromises[id];
|
||||
}
|
||||
|
||||
private _createGetTerminalPromise(id: number, retries: number = 5): Promise<ExtHostTerminal | undefined> {
|
||||
return new Promise(c => {
|
||||
if (retries === 0) {
|
||||
c(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
c(terminal);
|
||||
} else {
|
||||
// This should only be needed immediately after createTerminalRenderer is called as
|
||||
// the ExtHostTerminal has not yet been iniitalized
|
||||
timeout(EXT_HOST_CREATION_DELAY * 2).then(() => c(this._createGetTerminalPromise(id, retries - 1)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _getTerminalById(id: number): ExtHostTerminal | null {
|
||||
return this._getTerminalObjectById(this._terminals, id);
|
||||
}
|
||||
@@ -705,7 +642,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
||||
return index !== null ? array[index] : null;
|
||||
}
|
||||
|
||||
private _getTerminalObjectIndexById<T extends ExtHostTerminal>(array: T[], id: number): number | null {
|
||||
private _getTerminalObjectIndexById<T extends ExtHostTerminal>(array: T[], id: TerminalIdentifier): number | null {
|
||||
let index: number | null = null;
|
||||
array.some((item, i) => {
|
||||
const thisId = item._id;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { mapFind } from 'vs/base/common/arrays';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { throttle } from 'vs/base/common/decorators';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -14,25 +13,35 @@ import { isDefined } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { ExtHostTestingResource, ExtHostTestingShape, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
|
||||
import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { TestItem } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { Disposable, RequiredTestItem } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { OwnedTestCollection, SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
|
||||
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, InternalTestItemWithChildren, InternalTestResults, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`;
|
||||
|
||||
export class ExtHostTesting implements ExtHostTestingShape {
|
||||
private readonly resultsChangedEmitter = new Emitter<void>();
|
||||
private readonly providers = new Map<string, vscode.TestProvider>();
|
||||
private readonly proxy: MainThreadTestingShape;
|
||||
private readonly ownedTests = new OwnedTestCollection();
|
||||
private readonly testSubscriptions = new Map<string, { collection: SingleUseTestCollection, store: IDisposable }>();
|
||||
private readonly testSubscriptions = new Map<string, {
|
||||
collection: SingleUseTestCollection;
|
||||
store: IDisposable;
|
||||
subscribeFn: (id: string, provider: vscode.TestProvider) => void;
|
||||
}>();
|
||||
|
||||
private workspaceObservers: WorkspaceFolderTestObserverFactory;
|
||||
private textDocumentObservers: TextDocumentTestObserverFactory;
|
||||
|
||||
public onLastResultsChanged = this.resultsChangedEmitter.event;
|
||||
public lastResults?: vscode.TestResults;
|
||||
|
||||
constructor(@IExtHostRpcService rpc: IExtHostRpcService, @IExtHostDocumentsAndEditors private readonly documents: IExtHostDocumentsAndEditors, @IExtHostWorkspace private readonly workspace: IExtHostWorkspace) {
|
||||
this.proxy = rpc.getProxy(MainContext.MainThreadTesting);
|
||||
this.workspaceObservers = new WorkspaceFolderTestObserverFactory(this.proxy);
|
||||
@@ -47,6 +56,14 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
this.providers.set(providerId, provider);
|
||||
this.proxy.$registerTestProvider(providerId);
|
||||
|
||||
// give the ext a moment to register things rather than synchronously invoking within activate()
|
||||
const toSubscribe = [...this.testSubscriptions.keys()];
|
||||
setTimeout(() => {
|
||||
for (const subscription of toSubscribe) {
|
||||
this.testSubscriptions.get(subscription)?.subscribeFn(providerId, provider);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return new Disposable(() => {
|
||||
this.providers.delete(providerId);
|
||||
this.proxy.$unregisterTestProvider(providerId);
|
||||
@@ -70,7 +87,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
/**
|
||||
* Implements vscode.test.runTests
|
||||
*/
|
||||
public async runTests(req: vscode.TestRunOptions<vscode.TestItem>) {
|
||||
public async runTests(req: vscode.TestRunOptions<vscode.TestItem>, token = CancellationToken.None) {
|
||||
await this.proxy.$runTests({
|
||||
tests: req.tests
|
||||
// Find workspace items first, then owned tests, then document tests.
|
||||
@@ -82,14 +99,27 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
.filter(isDefined)
|
||||
.map(item => ({ providerId: item.providerId, testId: item.id })),
|
||||
debug: req.debug
|
||||
});
|
||||
}, token);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates test results shown to extensions.
|
||||
* @override
|
||||
*/
|
||||
public $publishTestResults(results: InternalTestResults): void {
|
||||
const convert = (item: InternalTestItemWithChildren): vscode.RequiredTestItem =>
|
||||
({ ...TestItem.toShallow(item.item), children: item.children.map(convert) });
|
||||
|
||||
this.lastResults = { tests: results.tests.map(convert) };
|
||||
this.resultsChangedEmitter.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to read tests for a file, or workspace.
|
||||
* @override
|
||||
*/
|
||||
public $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) {
|
||||
public async $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) {
|
||||
const uri = URI.revive(uriComponents);
|
||||
const subscriptionKey = getTestSubscriptionKey(resource, uri);
|
||||
if (this.testSubscriptions.has(subscriptionKey)) {
|
||||
@@ -98,12 +128,29 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
|
||||
let method: undefined | ((p: vscode.TestProvider) => vscode.TestHierarchy<vscode.TestItem> | undefined);
|
||||
if (resource === ExtHostTestingResource.TextDocument) {
|
||||
const document = this.documents.getDocument(uri);
|
||||
let document = this.documents.getDocument(uri);
|
||||
|
||||
// we can ask to subscribe to tests before the documents are populated in
|
||||
// the extension host. Try to wait.
|
||||
if (!document) {
|
||||
const store = new DisposableStore();
|
||||
document = await new Promise<ExtHostDocumentData | undefined>(resolve => {
|
||||
store.add(disposableTimeout(() => resolve(undefined), 5000));
|
||||
store.add(this.documents.onDidAddDocuments(e => {
|
||||
const data = e.find(data => data.document.uri.toString() === uri.toString());
|
||||
if (data) { resolve(data); }
|
||||
}));
|
||||
}).finally(() => store.dispose());
|
||||
}
|
||||
|
||||
if (document) {
|
||||
method = p => p.createDocumentTestHierarchy?.(document.document);
|
||||
const folder = await this.workspace.getWorkspaceFolder2(uri, false);
|
||||
method = p => p.createDocumentTestHierarchy
|
||||
? p.createDocumentTestHierarchy(document!.document)
|
||||
: this.createDefaultDocumentTestHierarchy(p, document!.document, folder);
|
||||
}
|
||||
} else {
|
||||
const folder = this.workspace.getWorkspaceFolder(uri, false);
|
||||
const folder = await this.workspace.getWorkspaceFolder2(uri, false);
|
||||
if (folder) {
|
||||
method = p => p.createWorkspaceTestHierarchy?.(folder);
|
||||
}
|
||||
@@ -113,24 +160,34 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
return;
|
||||
}
|
||||
|
||||
const disposable = new DisposableStore();
|
||||
const collection = disposable.add(this.ownedTests.createForHierarchy(diff => this.proxy.$publishDiff(resource, uriComponents, diff)));
|
||||
for (const [id, provider] of this.providers) {
|
||||
const subscribeFn = (id: string, provider: vscode.TestProvider) => {
|
||||
try {
|
||||
const hierarchy = method(provider);
|
||||
const hierarchy = method!(provider);
|
||||
if (!hierarchy) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, 1]);
|
||||
disposable.add(hierarchy);
|
||||
collection.addRoot(hierarchy.root, id);
|
||||
Promise.resolve(hierarchy.discoveredInitialTests).then(() => collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, -1]));
|
||||
hierarchy.onDidChangeTest(e => collection.onItemChange(e, id));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const disposable = new DisposableStore();
|
||||
const collection = disposable.add(this.ownedTests.createForHierarchy(diff => this.proxy.$publishDiff(resource, uriComponents, diff)));
|
||||
for (const [id, provider] of this.providers) {
|
||||
subscribeFn(id, provider);
|
||||
}
|
||||
|
||||
this.testSubscriptions.set(subscriptionKey, { store: disposable, collection });
|
||||
// note: we don't increment the root count initially -- this is done by the
|
||||
// main thread, incrementing once per extension host. We just push the
|
||||
// diff to signal that roots have been discovered.
|
||||
collection.pushDiff([TestDiffOpType.DeltaRootsComplete, -1]);
|
||||
this.testSubscriptions.set(subscriptionKey, { store: disposable, collection, subscribeFn });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,217 +219,193 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
* providers to be run.
|
||||
* @override
|
||||
*/
|
||||
public async $runTestsForProvider(req: RunTestForProviderRequest): Promise<RunTestsResult> {
|
||||
public async $runTestsForProvider(req: RunTestForProviderRequest, cancellation: CancellationToken): Promise<RunTestsResult> {
|
||||
const provider = this.providers.get(req.providerId);
|
||||
if (!provider || !provider.runTests) {
|
||||
return EMPTY_TEST_RESULT;
|
||||
}
|
||||
|
||||
const tests = req.ids.map(id => this.ownedTests.getTestById(id)?.actual).filter(isDefined);
|
||||
const tests = req.ids.map(id => this.ownedTests.getTestById(id)?.actual)
|
||||
.filter(isDefined)
|
||||
// Only send the actual TestItem's to the user to run.
|
||||
.map(t => t instanceof TestItemFilteredWrapper ? t.actual : t);
|
||||
if (!tests.length) {
|
||||
return EMPTY_TEST_RESULT;
|
||||
}
|
||||
|
||||
await provider.runTests({ tests, debug: req.debug }, CancellationToken.None);
|
||||
return EMPTY_TEST_RESULT;
|
||||
}
|
||||
}
|
||||
|
||||
const keyMap: { [K in keyof Omit<RequiredTestItem, 'children'>]: null } = {
|
||||
label: null,
|
||||
location: null,
|
||||
state: null,
|
||||
debuggable: null,
|
||||
description: null,
|
||||
runnable: null
|
||||
};
|
||||
|
||||
const simpleProps = Object.keys(keyMap) as ReadonlyArray<keyof typeof keyMap>;
|
||||
|
||||
const itemEqualityComparator = (a: vscode.TestItem) => {
|
||||
const values: unknown[] = [];
|
||||
for (const prop of simpleProps) {
|
||||
values.push(a[prop]);
|
||||
}
|
||||
|
||||
return (b: vscode.TestItem) => {
|
||||
for (let i = 0; i < simpleProps.length; i++) {
|
||||
if (values[i] !== b[simpleProps[i]]) {
|
||||
return false;
|
||||
try {
|
||||
await provider.runTests({ tests, debug: req.debug }, cancellation);
|
||||
for (const { collection } of this.testSubscriptions.values()) {
|
||||
collection.flushDiff(); // ensure all states are updated
|
||||
}
|
||||
|
||||
return EMPTY_TEST_RESULT;
|
||||
} catch (e) {
|
||||
console.error(e); // so it appears to attached debuggers
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public $lookupTest(req: TestIdWithProvider): Promise<InternalTestItem | undefined> {
|
||||
const owned = this.ownedTests.getTestById(req.testId);
|
||||
if (!owned) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export interface OwnedCollectionTestItem extends InternalTestItem {
|
||||
actual: vscode.TestItem;
|
||||
previousChildren: Set<string>;
|
||||
previousEquals: (v: vscode.TestItem) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class OwnedTestCollection {
|
||||
protected readonly testIdToInternal = new Map<string, OwnedCollectionTestItem>();
|
||||
|
||||
/**
|
||||
* Gets test information by ID, if it was defined and still exists in this
|
||||
* extension host.
|
||||
*/
|
||||
public getTestById(id: string) {
|
||||
return this.testIdToInternal.get(id);
|
||||
const { actual, previousChildren, previousEquals, ...item } = owned;
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new test collection for a specific hierarchy for a workspace
|
||||
* or document observation.
|
||||
*/
|
||||
public createForHierarchy(publishDiff: (diff: TestsDiff) => void = () => undefined) {
|
||||
return new SingleUseTestCollection(this.testIdToInternal, publishDiff);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintains tests created and registered for a single set of hierarchies
|
||||
* for a workspace or document.
|
||||
* @private
|
||||
*/
|
||||
export class SingleUseTestCollection implements IDisposable {
|
||||
protected readonly testItemToInternal = new Map<vscode.TestItem, OwnedCollectionTestItem>();
|
||||
protected diff: TestsDiff = [];
|
||||
private disposed = false;
|
||||
|
||||
constructor(private readonly testIdToInternal: Map<string, OwnedCollectionTestItem>, private readonly publishDiff: (diff: TestsDiff) => void) { }
|
||||
|
||||
/**
|
||||
* Adds a new root node to the collection.
|
||||
*/
|
||||
public addRoot(item: vscode.TestItem, providerId: string) {
|
||||
this.addItem(item, providerId, null);
|
||||
this.throttleSendDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets test information by its reference, if it was defined and still exists
|
||||
* in this extension host.
|
||||
*/
|
||||
public getTestByReference(item: vscode.TestItem) {
|
||||
return this.testItemToInternal.get(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when an item change is fired on the test provider.
|
||||
*/
|
||||
public onItemChange(item: vscode.TestItem, providerId: string) {
|
||||
const existing = this.testItemToInternal.get(item);
|
||||
if (!existing) {
|
||||
if (!this.disposed) {
|
||||
console.warn(`Received a TestProvider.onDidChangeTest for a test that wasn't seen before as a child.`);
|
||||
}
|
||||
private createDefaultDocumentTestHierarchy(provider: vscode.TestProvider, document: vscode.TextDocument, folder: vscode.WorkspaceFolder | undefined): vscode.TestHierarchy<vscode.TestItem> | undefined {
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addItem(item, providerId, existing.parent);
|
||||
this.throttleSendDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a diff of all changes that have been made, and clears the diff queue.
|
||||
*/
|
||||
public collectDiff() {
|
||||
const diff = this.diff;
|
||||
this.diff = [];
|
||||
return diff;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
for (const item of this.testItemToInternal.values()) {
|
||||
this.testIdToInternal.delete(item.id);
|
||||
const workspaceHierarchy = provider.createWorkspaceTestHierarchy?.(folder);
|
||||
if (!workspaceHierarchy) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.testIdToInternal.clear();
|
||||
this.diff = [];
|
||||
this.disposed = true;
|
||||
}
|
||||
const onDidChangeTest = new Emitter<vscode.TestItem>();
|
||||
workspaceHierarchy.onDidChangeTest(node => {
|
||||
const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(node, document);
|
||||
const previouslySeen = wrapper.hasNodeMatchingFilter;
|
||||
|
||||
protected getId(): string {
|
||||
return generateUuid();
|
||||
}
|
||||
if (previouslySeen) {
|
||||
// reset cache and get whether you can currently see the TestItem.
|
||||
wrapper.reset();
|
||||
const currentlySeen = wrapper.hasNodeMatchingFilter;
|
||||
|
||||
private addItem(actual: vscode.TestItem, providerId: string, parent: string | null) {
|
||||
let internal = this.testItemToInternal.get(actual);
|
||||
if (!internal) {
|
||||
internal = {
|
||||
actual,
|
||||
id: this.getId(),
|
||||
parent,
|
||||
item: TestItem.from(actual),
|
||||
providerId,
|
||||
previousChildren: new Set(),
|
||||
previousEquals: itemEqualityComparator(actual),
|
||||
};
|
||||
if (currentlySeen) {
|
||||
onDidChangeTest.fire(wrapper);
|
||||
return;
|
||||
}
|
||||
|
||||
this.testItemToInternal.set(actual, internal);
|
||||
this.testIdToInternal.set(internal.id, internal);
|
||||
this.diff.push([TestDiffOpType.Add, { id: internal.id, parent, providerId, item: internal.item }]);
|
||||
} else if (!internal.previousEquals(actual)) {
|
||||
internal.item = TestItem.from(actual);
|
||||
internal.previousEquals = itemEqualityComparator(actual);
|
||||
this.diff.push([TestDiffOpType.Update, { id: internal.id, parent, providerId, item: internal.item }]);
|
||||
}
|
||||
|
||||
// If there are children, track which ones are deleted
|
||||
// and recursively and/update them.
|
||||
if (actual.children) {
|
||||
const deletedChildren = internal.previousChildren;
|
||||
const currentChildren = new Set<string>();
|
||||
for (const child of actual.children) {
|
||||
const c = this.addItem(child, providerId, internal.id);
|
||||
deletedChildren.delete(c.id);
|
||||
currentChildren.add(c.id);
|
||||
// Fire the event to say that the current visible parent has changed.
|
||||
onDidChangeTest.fire(wrapper.visibleParent);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const child of deletedChildren) {
|
||||
this.removeItembyId(child);
|
||||
const previousParent = wrapper.visibleParent;
|
||||
wrapper.reset();
|
||||
const currentlySeen = wrapper.hasNodeMatchingFilter;
|
||||
|
||||
// It wasn't previously seen and isn't currently seen so
|
||||
// nothing has actually changed.
|
||||
if (!currentlySeen) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal.previousChildren = currentChildren;
|
||||
}
|
||||
// The test is now visible so we need to refresh the cache
|
||||
// of the previous visible parent and fire that it has changed.
|
||||
previousParent.reset();
|
||||
onDidChangeTest.fire(previousParent);
|
||||
});
|
||||
|
||||
return {
|
||||
root: TestItemFilteredWrapper.getWrapperForTestItem(workspaceHierarchy.root, document),
|
||||
dispose: () => {
|
||||
onDidChangeTest.dispose();
|
||||
TestItemFilteredWrapper.removeFilter(document);
|
||||
},
|
||||
onDidChangeTest: onDidChangeTest.event
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return internal;
|
||||
/*
|
||||
* A class which wraps a vscode.TestItem that provides the ability to filter a TestItem's children
|
||||
* to only the children that are located in a certain vscode.Uri.
|
||||
*/
|
||||
export class TestItemFilteredWrapper implements vscode.TestItem {
|
||||
private static wrapperMap = new WeakMap<vscode.TextDocument, WeakMap<vscode.TestItem, TestItemFilteredWrapper>>();
|
||||
public static removeFilter(document: vscode.TextDocument): void {
|
||||
this.wrapperMap.delete(document);
|
||||
}
|
||||
|
||||
private removeItembyId(id: string) {
|
||||
this.diff.push([TestDiffOpType.Remove, id]);
|
||||
|
||||
const queue = [this.testIdToInternal.get(id)];
|
||||
while (queue.length) {
|
||||
const item = queue.pop();
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.testIdToInternal.delete(item.id);
|
||||
this.testItemToInternal.delete(item.actual);
|
||||
for (const child of item.previousChildren) {
|
||||
queue.push(this.testIdToInternal.get(child));
|
||||
}
|
||||
// Wraps the TestItem specified in a TestItemFilteredWrapper and pulls from a cache if it already exists.
|
||||
public static getWrapperForTestItem(item: vscode.TestItem, filterDocument: vscode.TextDocument, parent?: TestItemFilteredWrapper): TestItemFilteredWrapper {
|
||||
let innerMap = this.wrapperMap.get(filterDocument);
|
||||
if (innerMap?.has(item)) {
|
||||
return innerMap.get(item)!;
|
||||
}
|
||||
|
||||
if (!innerMap) {
|
||||
innerMap = new WeakMap<vscode.TestItem, TestItemFilteredWrapper>();
|
||||
this.wrapperMap.set(filterDocument, innerMap);
|
||||
|
||||
}
|
||||
|
||||
const w = new TestItemFilteredWrapper(item, filterDocument, parent);
|
||||
innerMap.set(item, w);
|
||||
return w;
|
||||
}
|
||||
|
||||
@throttle(200)
|
||||
protected throttleSendDiff() {
|
||||
const diff = this.collectDiff();
|
||||
if (diff.length) {
|
||||
this.publishDiff(diff);
|
||||
public get label() {
|
||||
return this.actual.label;
|
||||
}
|
||||
|
||||
public get debuggable() {
|
||||
return this.actual.debuggable;
|
||||
}
|
||||
|
||||
public get description() {
|
||||
return this.actual.description;
|
||||
}
|
||||
|
||||
public get location() {
|
||||
return this.actual.location;
|
||||
}
|
||||
|
||||
public get runnable() {
|
||||
return this.actual.runnable;
|
||||
}
|
||||
|
||||
public get state() {
|
||||
return this.actual.state;
|
||||
}
|
||||
|
||||
public get children() {
|
||||
// We only want children that match the filter.
|
||||
return this.getWrappedChildren().filter(child => child.hasNodeMatchingFilter);
|
||||
}
|
||||
|
||||
public get visibleParent(): TestItemFilteredWrapper {
|
||||
return this.hasNodeMatchingFilter ? this : this.parent!.visibleParent;
|
||||
}
|
||||
|
||||
private matchesFilter: boolean | undefined;
|
||||
|
||||
// Determines if the TestItem matches the filter. This would be true if:
|
||||
// 1. We don't have a parent (because the root is the workspace root node)
|
||||
// 2. The URI of the current node matches the filter URI
|
||||
// 3. Some child of the current node matches the filter URI
|
||||
public get hasNodeMatchingFilter(): boolean {
|
||||
if (this.matchesFilter === undefined) {
|
||||
this.matchesFilter = !this.parent
|
||||
|| this.actual.location?.uri.toString() === this.filterDocument.uri.toString()
|
||||
|| this.getWrappedChildren().some(child => child.hasNodeMatchingFilter);
|
||||
}
|
||||
|
||||
return this.matchesFilter;
|
||||
}
|
||||
|
||||
// Reset the cache of whether or not you can see a node from a particular node
|
||||
// up to it's visible parent.
|
||||
public reset(): void {
|
||||
if (this !== this.visibleParent) {
|
||||
this.parent?.reset();
|
||||
}
|
||||
this.matchesFilter = undefined;
|
||||
}
|
||||
|
||||
|
||||
private constructor(public readonly actual: vscode.TestItem, private filterDocument: vscode.TextDocument, private readonly parent?: TestItemFilteredWrapper) {
|
||||
this.getWrappedChildren();
|
||||
}
|
||||
|
||||
private getWrappedChildren() {
|
||||
return this.actual.children?.map(t => TestItemFilteredWrapper.getWrapperForTestItem(t, this.filterDocument, this)) || [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +415,7 @@ export class SingleUseTestCollection implements IDisposable {
|
||||
interface MirroredCollectionTestItem extends IncrementalTestCollectionItem {
|
||||
revived: vscode.TestItem;
|
||||
depth: number;
|
||||
wrapped?: vscode.TestItem;
|
||||
wrapped?: vscode.RequiredTestItem;
|
||||
}
|
||||
|
||||
class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollectionTestItem> {
|
||||
@@ -411,7 +444,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
|
||||
* @override
|
||||
*/
|
||||
public update(node: MirroredCollectionTestItem): void {
|
||||
Object.assign(node.revived, TestItem.to(node.item));
|
||||
Object.assign(node.revived, TestItem.toShallow(node.item));
|
||||
if (!this.added.has(node)) {
|
||||
this.updated.add(node);
|
||||
}
|
||||
@@ -545,8 +578,8 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
|
||||
/**
|
||||
* Translates the item IDs to TestItems for exposure to extensions.
|
||||
*/
|
||||
public getAllAsTestItem(itemIds: Iterable<string>): vscode.TestItem[] {
|
||||
let output: vscode.TestItem[] = [];
|
||||
public getAllAsTestItem(itemIds: Iterable<string>): vscode.RequiredTestItem[] {
|
||||
let output: vscode.RequiredTestItem[] = [];
|
||||
for (const itemId of itemIds) {
|
||||
const item = this.items.get(itemId);
|
||||
if (item) {
|
||||
@@ -577,7 +610,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
|
||||
* @override
|
||||
*/
|
||||
protected createItem(item: InternalTestItem, parent?: MirroredCollectionTestItem): MirroredCollectionTestItem {
|
||||
return { ...item, revived: TestItem.to(item.item), depth: parent ? parent.depth + 1 : 0, children: new Set() };
|
||||
return { ...item, revived: TestItem.toShallow(item.item), depth: parent ? parent.depth + 1 : 0, children: new Set() };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -590,9 +623,9 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
|
||||
/**
|
||||
* Gets the public test item instance for the given mirrored record.
|
||||
*/
|
||||
public getPublicTestItem(item: MirroredCollectionTestItem): vscode.TestItem {
|
||||
public getPublicTestItem(item: MirroredCollectionTestItem): vscode.RequiredTestItem {
|
||||
if (!item.wrapped) {
|
||||
item.wrapped = new ExtHostTestItem(item, this);
|
||||
item.wrapped = new TestItemFromMirror(item, this);
|
||||
}
|
||||
|
||||
return item.wrapped;
|
||||
@@ -605,10 +638,11 @@ const getMirroredItemId = (item: vscode.TestItem) => {
|
||||
|
||||
const MirroredItemId = Symbol('MirroredItemId');
|
||||
|
||||
class ExtHostTestItem implements vscode.TestItem, RequiredTestItem {
|
||||
class TestItemFromMirror implements vscode.RequiredTestItem {
|
||||
readonly #internal: MirroredCollectionTestItem;
|
||||
readonly #collection: MirroredTestCollection;
|
||||
|
||||
public get id() { return this.#internal.revived.id!; }
|
||||
public get label() { return this.#internal.revived.label; }
|
||||
public get description() { return this.#internal.revived.description; }
|
||||
public get state() { return this.#internal.revived.state; }
|
||||
@@ -627,14 +661,18 @@ class ExtHostTestItem implements vscode.TestItem, RequiredTestItem {
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
const serialized: RequiredTestItem = {
|
||||
const serialized: vscode.RequiredTestItem & TestIdWithProvider = {
|
||||
id: this.id,
|
||||
label: this.label,
|
||||
description: this.description,
|
||||
state: this.state,
|
||||
location: this.location,
|
||||
runnable: this.runnable,
|
||||
debuggable: this.debuggable,
|
||||
children: this.children.map(c => (c as ExtHostTestItem).toJSON()),
|
||||
children: this.children.map(c => (c as TestItemFromMirror).toJSON()),
|
||||
|
||||
providerId: this.#internal.providerId,
|
||||
testId: this.#internal.id,
|
||||
};
|
||||
|
||||
return serialized;
|
||||
@@ -655,6 +693,7 @@ abstract class AbstractTestObserverFactory {
|
||||
const resourceKey = resourceUri.toString();
|
||||
const resource = this.resources.get(resourceKey) ?? this.createObserverData(resourceUri);
|
||||
|
||||
resource.pendingDeletion?.dispose();
|
||||
resource.observers++;
|
||||
|
||||
return {
|
||||
@@ -778,16 +817,9 @@ class TextDocumentTestObserverFactory extends AbstractTestObserverFactory {
|
||||
const uriString = resourceUri.toString();
|
||||
this.diffListeners.set(uriString, onDiff);
|
||||
|
||||
const disposeListener = this.documents.onDidRemoveDocuments(evt => {
|
||||
if (evt.some(delta => delta.document.uri.toString() === uriString)) {
|
||||
this.unlisten(resourceUri);
|
||||
}
|
||||
});
|
||||
|
||||
this.proxy.$subscribeToDiffs(ExtHostTestingResource.TextDocument, resourceUri);
|
||||
return new Disposable(() => {
|
||||
this.proxy.$unsubscribeFromDiffs(ExtHostTestingResource.TextDocument, resourceUri);
|
||||
disposeListener.dispose();
|
||||
this.diffListeners.delete(uriString);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'
|
||||
import { MarkdownString } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Command } from 'vs/editor/common/modes';
|
||||
|
||||
type TreeItemHandle = string;
|
||||
|
||||
@@ -132,12 +133,12 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
return treeView.hasResolve;
|
||||
}
|
||||
|
||||
$resolve(treeViewId: string, treeItemHandle: string): Promise<ITreeItem | undefined> {
|
||||
$resolve(treeViewId: string, treeItemHandle: string, token: vscode.CancellationToken): Promise<ITreeItem | undefined> {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
|
||||
}
|
||||
return treeView.resolveTreeItem(treeItemHandle);
|
||||
return treeView.resolveTreeItem(treeItemHandle, token);
|
||||
}
|
||||
|
||||
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void {
|
||||
@@ -184,6 +185,7 @@ interface TreeNode extends IDisposable {
|
||||
extensionItem: vscode.TreeItem;
|
||||
parent: TreeNode | Root;
|
||||
children?: TreeNode[];
|
||||
disposableStore: DisposableStore;
|
||||
}
|
||||
|
||||
class ExtHostTreeView<T> extends Disposable {
|
||||
@@ -370,7 +372,7 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
return !!this.dataProvider.resolveTreeItem;
|
||||
}
|
||||
|
||||
async resolveTreeItem(treeItemHandle: string): Promise<ITreeItem | undefined> {
|
||||
async resolveTreeItem(treeItemHandle: string, token: vscode.CancellationToken): Promise<ITreeItem | undefined> {
|
||||
if (!this.dataProvider.resolveTreeItem) {
|
||||
return;
|
||||
}
|
||||
@@ -378,9 +380,10 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
if (element) {
|
||||
const node = this.nodes.get(element);
|
||||
if (node) {
|
||||
const resolve = await this.dataProvider.resolveTreeItem(node.extensionItem, element) ?? node.extensionItem;
|
||||
// Resolvable elements. Currently only tooltip.
|
||||
const resolve = await this.dataProvider.resolveTreeItem(node.extensionItem, element, token) ?? node.extensionItem;
|
||||
// Resolvable elements. Currently only tooltip and command.
|
||||
node.item.tooltip = this.getTooltip(resolve.tooltip);
|
||||
node.item.command = this.getCommand(node.disposableStore, resolve.command);
|
||||
return node.item;
|
||||
}
|
||||
}
|
||||
@@ -573,8 +576,12 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
private getCommand(disposable: DisposableStore, command?: vscode.Command): Command | undefined {
|
||||
return command ? this.commands.toInternal(command, disposable) : undefined;
|
||||
}
|
||||
|
||||
private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode {
|
||||
const disposable = new DisposableStore();
|
||||
const disposableStore = new DisposableStore();
|
||||
const handle = this.createHandle(element, extensionTreeItem, parent);
|
||||
const icon = this.getLightIconPath(extensionTreeItem);
|
||||
const item: ITreeItem = {
|
||||
@@ -584,7 +591,7 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
description: extensionTreeItem.description,
|
||||
resourceUri: extensionTreeItem.resourceUri,
|
||||
tooltip: this.getTooltip(extensionTreeItem.tooltip),
|
||||
command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command, disposable) : undefined,
|
||||
command: this.getCommand(disposableStore, extensionTreeItem.command),
|
||||
contextValue: extensionTreeItem.contextValue,
|
||||
icon,
|
||||
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
|
||||
@@ -598,7 +605,8 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
extensionItem: extensionTreeItem,
|
||||
parent,
|
||||
children: undefined,
|
||||
dispose(): void { disposable.dispose(); }
|
||||
disposableStore,
|
||||
dispose(): void { disposableStore.dispose(); }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtHostTunnelServiceShape, MainContext, MainThreadTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import * as vscode from 'vscode';
|
||||
import { RemoteTunnel, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||
@@ -11,18 +11,27 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
|
||||
export interface TunnelDto {
|
||||
remoteAddress: { port: number, host: string };
|
||||
localAddress: { port: number, host: string } | string;
|
||||
public: boolean;
|
||||
}
|
||||
|
||||
export namespace TunnelDto {
|
||||
export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto {
|
||||
return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress };
|
||||
return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress, public: !!tunnel.public };
|
||||
}
|
||||
export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto {
|
||||
return { remoteAddress: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress };
|
||||
return {
|
||||
remoteAddress: {
|
||||
host: tunnel.tunnelRemoteHost,
|
||||
port: tunnel.tunnelRemotePort
|
||||
},
|
||||
localAddress: tunnel.localAddress,
|
||||
public: tunnel.public
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,12 +53,13 @@ export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IEx
|
||||
export class ExtHostTunnelService implements IExtHostTunnelService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
onDidChangeTunnels: vscode.Event<void> = (new Emitter<void>()).event;
|
||||
private readonly _proxy: MainThreadTunnelServiceShape;
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
) {
|
||||
this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService);
|
||||
}
|
||||
async $applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]> {
|
||||
return candidates;
|
||||
}
|
||||
|
||||
async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
|
||||
@@ -59,10 +69,10 @@ export class ExtHostTunnelService implements IExtHostTunnelService {
|
||||
return [];
|
||||
}
|
||||
async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
|
||||
await this._proxy.$tunnelServiceReady();
|
||||
return { dispose: () => { } };
|
||||
}
|
||||
$forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto> | undefined { return undefined; }
|
||||
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> { return undefined; }
|
||||
async $closeTunnel(remote: { host: string, port: number }): Promise<void> { }
|
||||
async $onDidTunnelsChange(): Promise<void> { }
|
||||
async $registerCandidateFinder(): Promise<void> { }
|
||||
}
|
||||
|
||||
@@ -1015,6 +1015,29 @@ export namespace SignatureHelp {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace InlineHint {
|
||||
|
||||
export function from(hint: vscode.InlineHint): modes.InlineHint {
|
||||
return {
|
||||
text: hint.text,
|
||||
range: Range.from(hint.range),
|
||||
description: hint.description && MarkdownString.fromStrict(hint.description),
|
||||
whitespaceBefore: hint.whitespaceBefore,
|
||||
whitespaceAfter: hint.whitespaceAfter
|
||||
};
|
||||
}
|
||||
|
||||
export function to(hint: modes.InlineHint): vscode.InlineHint {
|
||||
return new types.InlineHint(
|
||||
hint.text,
|
||||
Range.to(hint.range),
|
||||
htmlContent.isMarkdownString(hint.description) ? MarkdownString.to(hint.description) : hint.description,
|
||||
hint.whitespaceBefore,
|
||||
hint.whitespaceAfter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace DocumentLink {
|
||||
|
||||
export function from(link: vscode.DocumentLink): modes.ILink {
|
||||
@@ -1389,19 +1412,21 @@ export namespace TestState {
|
||||
|
||||
|
||||
export namespace TestItem {
|
||||
export function from(item: vscode.TestItem): ITestItem {
|
||||
export function from(item: vscode.TestItem, parentExtId?: string): ITestItem {
|
||||
return {
|
||||
extId: item.id ?? (parentExtId ? `${parentExtId}\0${item.label}` : item.label),
|
||||
label: item.label,
|
||||
location: item.location ? location.from(item.location) : undefined,
|
||||
debuggable: item.debuggable,
|
||||
debuggable: item.debuggable ?? false,
|
||||
description: item.description,
|
||||
runnable: item.runnable,
|
||||
runnable: item.runnable ?? true,
|
||||
state: TestState.from(item.state),
|
||||
};
|
||||
}
|
||||
|
||||
export function to(item: ITestItem): vscode.TestItem {
|
||||
export function toShallow(item: ITestItem): Omit<vscode.RequiredTestItem, 'children'> {
|
||||
return {
|
||||
id: item.extId,
|
||||
label: item.label,
|
||||
location: item.location && location.to({
|
||||
range: item.location.range,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
|
||||
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { addIdToOutput, CellEditType, ICellEditOperation, IDisplayOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { addIdToOutput, CellEditType, ICellEditOperation, IDisplayOutput, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
function es5ClassCompat(target: Function): any {
|
||||
@@ -638,7 +638,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
|
||||
// --- notebook
|
||||
|
||||
replaceNotebookMetadata(uri: URI, value: vscode.NotebookDocumentMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void {
|
||||
this._edits.push({ _type: FileEditType.Cell, metadata, uri, notebookMetadata: value });
|
||||
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.DocumentMetadata, metadata: { ...notebookDocumentMetadataDefaults, ...value } }, notebookMetadata: value });
|
||||
}
|
||||
|
||||
replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void {
|
||||
@@ -1373,6 +1373,23 @@ export enum SignatureHelpTriggerKind {
|
||||
ContentChange = 3,
|
||||
}
|
||||
|
||||
@es5ClassCompat
|
||||
export class InlineHint {
|
||||
text: string;
|
||||
range: Range;
|
||||
description?: string | vscode.MarkdownString;
|
||||
whitespaceBefore?: boolean;
|
||||
whitespaceAfter?: boolean;
|
||||
|
||||
constructor(text: string, range: Range, description?: string | vscode.MarkdownString, whitespaceBefore?: boolean, whitespaceAfter?: boolean) {
|
||||
this.text = text;
|
||||
this.range = range;
|
||||
this.description = description;
|
||||
this.whitespaceBefore = whitespaceBefore;
|
||||
this.whitespaceAfter = whitespaceAfter;
|
||||
}
|
||||
}
|
||||
|
||||
export enum CompletionTriggerKind {
|
||||
Invoke = 0,
|
||||
TriggerCharacter = 1,
|
||||
@@ -2298,9 +2315,6 @@ export class FunctionBreakpoint extends Breakpoint {
|
||||
|
||||
constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) {
|
||||
super(enabled, condition, hitCondition, logMessage);
|
||||
if (!functionName) {
|
||||
throw illegalArgument('functionName');
|
||||
}
|
||||
this.functionName = functionName;
|
||||
}
|
||||
}
|
||||
@@ -2858,7 +2872,8 @@ export enum NotebookCellStatusBarAlignment {
|
||||
export enum NotebookEditorRevealType {
|
||||
Default = 0,
|
||||
InCenter = 1,
|
||||
InCenterIfOutsideViewport = 2
|
||||
InCenterIfOutsideViewport = 2,
|
||||
AtTop = 3
|
||||
}
|
||||
|
||||
|
||||
@@ -2924,11 +2939,12 @@ export class LinkedEditingRanges {
|
||||
//#region Testing
|
||||
export enum TestRunState {
|
||||
Unset = 0,
|
||||
Running = 1,
|
||||
Passed = 2,
|
||||
Failed = 3,
|
||||
Skipped = 4,
|
||||
Errored = 5
|
||||
Queued = 1,
|
||||
Running = 2,
|
||||
Passed = 3,
|
||||
Failed = 4,
|
||||
Skipped = 5,
|
||||
Errored = 6
|
||||
}
|
||||
|
||||
export enum TestMessageSeverity {
|
||||
@@ -2963,15 +2979,15 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
|
||||
type AllowedUndefined = 'description' | 'location';
|
||||
|
||||
/**
|
||||
* Test item without any optional properties. Only some properties are
|
||||
* permitted to be undefined, but they must still exist.
|
||||
*/
|
||||
export type RequiredTestItem = {
|
||||
[K in keyof Required<vscode.TestItem>]: K extends AllowedUndefined ? vscode.TestItem[K] : Required<vscode.TestItem>[K]
|
||||
};
|
||||
export type RequiredTestItem = vscode.RequiredTestItem;
|
||||
|
||||
export type TestItem = vscode.TestItem;
|
||||
|
||||
//#endregion
|
||||
|
||||
export enum ExternalUriOpenerPriority {
|
||||
None = 0,
|
||||
Option = 1,
|
||||
Default = 2,
|
||||
Preferred = 3,
|
||||
}
|
||||
|
||||
74
lib/vscode/src/vs/workbench/api/common/extHostUriOpener.ts
Normal file
74
lib/vscode/src/vs/workbench/api/common/extHostUriOpener.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import type * as vscode from 'vscode';
|
||||
import { ExtHostUriOpenersShape, IMainContext, MainContext, MainThreadUriOpenersShape } from './extHost.protocol';
|
||||
|
||||
|
||||
export class ExtHostUriOpeners implements ExtHostUriOpenersShape {
|
||||
|
||||
private static readonly supportedSchemes = new Set<string>([Schemas.http, Schemas.https]);
|
||||
|
||||
private readonly _proxy: MainThreadUriOpenersShape;
|
||||
|
||||
private readonly _openers = new Map<string, vscode.ExternalUriOpener>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadUriOpeners);
|
||||
}
|
||||
|
||||
registerExternalUriOpener(
|
||||
extensionId: ExtensionIdentifier,
|
||||
id: string,
|
||||
opener: vscode.ExternalUriOpener,
|
||||
metadata: vscode.ExternalUriOpenerMetadata,
|
||||
): vscode.Disposable {
|
||||
if (this._openers.has(id)) {
|
||||
throw new Error(`Opener with id '${id}' already registered`);
|
||||
}
|
||||
|
||||
const invalidScheme = metadata.schemes.find(scheme => !ExtHostUriOpeners.supportedSchemes.has(scheme));
|
||||
if (invalidScheme) {
|
||||
throw new Error(`Scheme '${invalidScheme}' is not supported. Only http and https are currently supported.`);
|
||||
}
|
||||
|
||||
this._openers.set(id, opener);
|
||||
this._proxy.$registerUriOpener(id, metadata.schemes, extensionId, metadata.label);
|
||||
|
||||
return toDisposable(() => {
|
||||
this._openers.delete(id);
|
||||
this._proxy.$unregisterUriOpener(id);
|
||||
});
|
||||
}
|
||||
|
||||
async $canOpenUri(id: string, uriComponents: UriComponents, token: CancellationToken): Promise<modes.ExternalUriOpenerPriority> {
|
||||
const opener = this._openers.get(id);
|
||||
if (!opener) {
|
||||
throw new Error(`Unknown opener with id: ${id}`);
|
||||
}
|
||||
|
||||
const uri = URI.revive(uriComponents);
|
||||
return opener.canOpenExternalUri(uri, token);
|
||||
}
|
||||
|
||||
async $openUri(id: string, context: { resolvedUri: UriComponents, sourceUri: UriComponents }, token: CancellationToken): Promise<void> {
|
||||
const opener = this._openers.get(id);
|
||||
if (!opener) {
|
||||
throw new Error(`Unknown opener id: '${id}'`);
|
||||
}
|
||||
|
||||
return opener.openExternalUri(URI.revive(context.resolvedUri), {
|
||||
sourceUri: URI.revive(context.sourceUri)
|
||||
}, token);
|
||||
}
|
||||
}
|
||||
@@ -287,8 +287,8 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel
|
||||
await serializer.deserializeWebviewPanel(revivedPanel, state);
|
||||
}
|
||||
|
||||
public createNewWebviewPanel(webviewHandle: string, viewType: string, title: string, position: number, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, webview: ExtHostWebview) {
|
||||
const panel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
|
||||
public createNewWebviewPanel(webviewHandle: string, viewType: string, title: string, position: vscode.ViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, webview: ExtHostWebview) {
|
||||
const panel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, position, options, webview);
|
||||
this._webviewPanels.set(webviewHandle, panel);
|
||||
return panel;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { Counter } from 'vs/base/common/numbers';
|
||||
import { basename, basenameOrAuthority, dirname, isEqual, relativePath } from 'vs/base/common/resources';
|
||||
import { basename, basenameOrAuthority, dirname, ExtUri, relativePath } from 'vs/base/common/resources';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -37,27 +37,27 @@ export interface IExtHostWorkspaceProvider {
|
||||
resolveProxy(url: string): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
function isFolderEqual(folderA: URI, folderB: URI): boolean {
|
||||
return isEqual(folderA, folderB);
|
||||
function isFolderEqual(folderA: URI, folderB: URI, extHostFileSystemInfo: IExtHostFileSystemInfo): boolean {
|
||||
return new ExtUri(uri => ignorePathCasing(uri, extHostFileSystemInfo)).isEqual(folderA, folderB);
|
||||
}
|
||||
|
||||
function compareWorkspaceFolderByUri(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
return isFolderEqual(a.uri, b.uri) ? 0 : compare(a.uri.toString(), b.uri.toString());
|
||||
function compareWorkspaceFolderByUri(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder, extHostFileSystemInfo: IExtHostFileSystemInfo): number {
|
||||
return isFolderEqual(a.uri, b.uri, extHostFileSystemInfo) ? 0 : compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
function compareWorkspaceFolderByUriAndNameAndIndex(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
function compareWorkspaceFolderByUriAndNameAndIndex(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder, extHostFileSystemInfo: IExtHostFileSystemInfo): number {
|
||||
if (a.index !== b.index) {
|
||||
return a.index < b.index ? -1 : 1;
|
||||
}
|
||||
|
||||
return isFolderEqual(a.uri, b.uri) ? compare(a.name, b.name) : compare(a.uri.toString(), b.uri.toString());
|
||||
return isFolderEqual(a.uri, b.uri, extHostFileSystemInfo) ? compare(a.name, b.name) : compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
function delta(oldFolders: vscode.WorkspaceFolder[], newFolders: vscode.WorkspaceFolder[], compare: (a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder) => number): { removed: vscode.WorkspaceFolder[], added: vscode.WorkspaceFolder[] } {
|
||||
const oldSortedFolders = oldFolders.slice(0).sort(compare);
|
||||
const newSortedFolders = newFolders.slice(0).sort(compare);
|
||||
function delta(oldFolders: vscode.WorkspaceFolder[], newFolders: vscode.WorkspaceFolder[], compare: (a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder, extHostFileSystemInfo: IExtHostFileSystemInfo) => number, extHostFileSystemInfo: IExtHostFileSystemInfo): { removed: vscode.WorkspaceFolder[], added: vscode.WorkspaceFolder[] } {
|
||||
const oldSortedFolders = oldFolders.slice(0).sort((a, b) => compare(a, b, extHostFileSystemInfo));
|
||||
const newSortedFolders = newFolders.slice(0).sort((a, b) => compare(a, b, extHostFileSystemInfo));
|
||||
|
||||
return arrayDelta(oldSortedFolders, newSortedFolders, compare);
|
||||
return arrayDelta(oldSortedFolders, newSortedFolders, (a, b) => compare(a, b, extHostFileSystemInfo));
|
||||
}
|
||||
|
||||
function ignorePathCasing(uri: URI, extHostFileSystemInfo: IExtHostFileSystemInfo): boolean {
|
||||
@@ -87,7 +87,7 @@ class ExtHostWorkspaceImpl extends Workspace {
|
||||
if (previousConfirmedWorkspace) {
|
||||
folders.forEach((folderData, index) => {
|
||||
const folderUri = URI.revive(folderData.uri);
|
||||
const existingFolder = ExtHostWorkspaceImpl._findFolder(previousUnconfirmedWorkspace || previousConfirmedWorkspace, folderUri);
|
||||
const existingFolder = ExtHostWorkspaceImpl._findFolder(previousUnconfirmedWorkspace || previousConfirmedWorkspace, folderUri, extHostFileSystemInfo);
|
||||
|
||||
if (existingFolder) {
|
||||
existingFolder.name = folderData.name;
|
||||
@@ -106,15 +106,15 @@ class ExtHostWorkspaceImpl extends Workspace {
|
||||
newWorkspaceFolders.sort((f1, f2) => f1.index < f2.index ? -1 : 1);
|
||||
|
||||
const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders, configuration ? URI.revive(configuration) : null, !!isUntitled, uri => ignorePathCasing(uri, extHostFileSystemInfo));
|
||||
const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri);
|
||||
const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri, extHostFileSystemInfo);
|
||||
|
||||
return { workspace, added, removed };
|
||||
}
|
||||
|
||||
private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI): MutableWorkspaceFolder | undefined {
|
||||
private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI, extHostFileSystemInfo: IExtHostFileSystemInfo): MutableWorkspaceFolder | undefined {
|
||||
for (let i = 0; i < workspace.folders.length; i++) {
|
||||
const folder = workspace.workspaceFolders[i];
|
||||
if (isFolderEqual(folder.uri, folderUriToFind)) {
|
||||
if (isFolderEqual(folder.uri, folderUriToFind, extHostFileSystemInfo)) {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
@@ -254,7 +254,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||
const validatedDistinctWorkspaceFoldersToAdd: { uri: vscode.Uri, name?: string }[] = [];
|
||||
if (Array.isArray(workspaceFoldersToAdd)) {
|
||||
workspaceFoldersToAdd.forEach(folderToAdd => {
|
||||
if (URI.isUri(folderToAdd.uri) && !validatedDistinctWorkspaceFoldersToAdd.some(f => isFolderEqual(f.uri, folderToAdd.uri))) {
|
||||
if (URI.isUri(folderToAdd.uri) && !validatedDistinctWorkspaceFoldersToAdd.some(f => isFolderEqual(f.uri, folderToAdd.uri, this._extHostFileSystemInfo))) {
|
||||
validatedDistinctWorkspaceFoldersToAdd.push({ uri: folderToAdd.uri, name: folderToAdd.name || basenameOrAuthority(folderToAdd.uri) });
|
||||
}
|
||||
});
|
||||
@@ -283,13 +283,13 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||
|
||||
for (let i = 0; i < newWorkspaceFolders.length; i++) {
|
||||
const folder = newWorkspaceFolders[i];
|
||||
if (newWorkspaceFolders.some((otherFolder, index) => index !== i && isFolderEqual(folder.uri, otherFolder.uri))) {
|
||||
if (newWorkspaceFolders.some((otherFolder, index) => index !== i && isFolderEqual(folder.uri, otherFolder.uri, this._extHostFileSystemInfo))) {
|
||||
return false; // cannot add the same folder multiple times
|
||||
}
|
||||
}
|
||||
|
||||
newWorkspaceFolders.forEach((f, index) => f.index = index); // fix index
|
||||
const { added, removed } = delta(currentWorkspaceFolders, newWorkspaceFolders, compareWorkspaceFolderByUriAndNameAndIndex);
|
||||
const { added, removed } = delta(currentWorkspaceFolders, newWorkspaceFolders, compareWorkspaceFolderByUriAndNameAndIndex, this._extHostFileSystemInfo);
|
||||
if (added.length === 0 && removed.length === 0) {
|
||||
return false; // nothing actually changed
|
||||
}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
||||
export interface IFullSemanticTokensDto {
|
||||
id: number;
|
||||
type: 'full';
|
||||
data: Uint32Array;
|
||||
}
|
||||
|
||||
export interface IDeltaSemanticTokensDto {
|
||||
id: number;
|
||||
type: 'delta';
|
||||
deltas: { start: number; deleteCount: number; data?: Uint32Array; }[];
|
||||
}
|
||||
|
||||
export type ISemanticTokensDto = IFullSemanticTokensDto | IDeltaSemanticTokensDto;
|
||||
|
||||
const enum EncodedSemanticTokensType {
|
||||
Full = 1,
|
||||
Delta = 2
|
||||
}
|
||||
|
||||
function reverseEndianness(arr: Uint8Array): void {
|
||||
for (let i = 0, len = arr.length; i < len; i += 4) {
|
||||
// flip bytes 0<->3 and 1<->2
|
||||
const b0 = arr[i + 0];
|
||||
const b1 = arr[i + 1];
|
||||
const b2 = arr[i + 2];
|
||||
const b3 = arr[i + 3];
|
||||
arr[i + 0] = b3;
|
||||
arr[i + 1] = b2;
|
||||
arr[i + 2] = b1;
|
||||
arr[i + 3] = b0;
|
||||
}
|
||||
}
|
||||
|
||||
function toLittleEndianBuffer(arr: Uint32Array): VSBuffer {
|
||||
const uint8Arr = new Uint8Array(arr.buffer, arr.byteOffset, arr.length * 4);
|
||||
if (!platform.isLittleEndian()) {
|
||||
// the byte order must be changed
|
||||
reverseEndianness(uint8Arr);
|
||||
}
|
||||
return VSBuffer.wrap(uint8Arr);
|
||||
}
|
||||
|
||||
function fromLittleEndianBuffer(buff: VSBuffer): Uint32Array {
|
||||
const uint8Arr = buff.buffer;
|
||||
if (!platform.isLittleEndian()) {
|
||||
// the byte order must be changed
|
||||
reverseEndianness(uint8Arr);
|
||||
}
|
||||
if (uint8Arr.byteOffset % 4 === 0) {
|
||||
return new Uint32Array(uint8Arr.buffer, uint8Arr.byteOffset, uint8Arr.length / 4);
|
||||
} else {
|
||||
// unaligned memory access doesn't work on all platforms
|
||||
const data = new Uint8Array(uint8Arr.byteLength);
|
||||
data.set(uint8Arr);
|
||||
return new Uint32Array(data.buffer, data.byteOffset, data.length / 4);
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeSemanticTokensDto(semanticTokens: ISemanticTokensDto): VSBuffer {
|
||||
const dest = new Uint32Array(encodeSemanticTokensDtoSize(semanticTokens));
|
||||
let offset = 0;
|
||||
dest[offset++] = semanticTokens.id;
|
||||
if (semanticTokens.type === 'full') {
|
||||
dest[offset++] = EncodedSemanticTokensType.Full;
|
||||
dest[offset++] = semanticTokens.data.length;
|
||||
dest.set(semanticTokens.data, offset); offset += semanticTokens.data.length;
|
||||
} else {
|
||||
dest[offset++] = EncodedSemanticTokensType.Delta;
|
||||
dest[offset++] = semanticTokens.deltas.length;
|
||||
for (const delta of semanticTokens.deltas) {
|
||||
dest[offset++] = delta.start;
|
||||
dest[offset++] = delta.deleteCount;
|
||||
if (delta.data) {
|
||||
dest[offset++] = delta.data.length;
|
||||
dest.set(delta.data, offset); offset += delta.data.length;
|
||||
} else {
|
||||
dest[offset++] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return toLittleEndianBuffer(dest);
|
||||
}
|
||||
|
||||
function encodeSemanticTokensDtoSize(semanticTokens: ISemanticTokensDto): number {
|
||||
let result = 0;
|
||||
result += (
|
||||
+ 1 // id
|
||||
+ 1 // type
|
||||
);
|
||||
if (semanticTokens.type === 'full') {
|
||||
result += (
|
||||
+ 1 // data length
|
||||
+ semanticTokens.data.length
|
||||
);
|
||||
} else {
|
||||
result += (
|
||||
+ 1 // delta count
|
||||
);
|
||||
result += (
|
||||
+ 1 // start
|
||||
+ 1 // deleteCount
|
||||
+ 1 // data length
|
||||
) * semanticTokens.deltas.length;
|
||||
for (const delta of semanticTokens.deltas) {
|
||||
if (delta.data) {
|
||||
result += delta.data.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function decodeSemanticTokensDto(_buff: VSBuffer): ISemanticTokensDto {
|
||||
const src = fromLittleEndianBuffer(_buff);
|
||||
let offset = 0;
|
||||
const id = src[offset++];
|
||||
const type: EncodedSemanticTokensType = src[offset++];
|
||||
if (type === EncodedSemanticTokensType.Full) {
|
||||
const length = src[offset++];
|
||||
const data = src.subarray(offset, offset + length); offset += length;
|
||||
return {
|
||||
id: id,
|
||||
type: 'full',
|
||||
data: data
|
||||
};
|
||||
}
|
||||
const deltaCount = src[offset++];
|
||||
let deltas: { start: number; deleteCount: number; data?: Uint32Array; }[] = [];
|
||||
for (let i = 0; i < deltaCount; i++) {
|
||||
const start = src[offset++];
|
||||
const deleteCount = src[offset++];
|
||||
const length = src[offset++];
|
||||
let data: Uint32Array | undefined;
|
||||
if (length > 0) {
|
||||
data = src.subarray(offset, offset + length); offset += length;
|
||||
}
|
||||
deltas[i] = { start, deleteCount, data };
|
||||
}
|
||||
return {
|
||||
id: id,
|
||||
type: 'delta',
|
||||
deltas: deltas
|
||||
};
|
||||
}
|
||||
@@ -119,8 +119,7 @@ export function checkGlobFileExists(
|
||||
const queryBuilder = instantiationService.createInstance(QueryBuilder);
|
||||
const query = queryBuilder.file(folders.map(folder => toWorkspaceFolder(URI.revive(folder))), {
|
||||
_reason: 'checkExists',
|
||||
includePattern: includes.join(', '),
|
||||
expandPatterns: true,
|
||||
includePattern: includes,
|
||||
exists: true
|
||||
});
|
||||
|
||||
|
||||
@@ -41,7 +41,15 @@ export interface RunCommandPipeArgs {
|
||||
args: any[];
|
||||
}
|
||||
|
||||
export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs;
|
||||
export interface ExtensionManagementPipeArgs {
|
||||
type: 'extensionManagement';
|
||||
list?: { showVersions?: boolean, category?: string; };
|
||||
install?: string[];
|
||||
uninstall?: string[];
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs | ExtensionManagementPipeArgs;
|
||||
|
||||
export interface ICommandsExecuter {
|
||||
executeCommand<T>(id: string, ...args: any[]): Promise<T>;
|
||||
@@ -102,6 +110,10 @@ export class CLIServerBase {
|
||||
this.runCommand(data, res)
|
||||
.catch(this.logService.error);
|
||||
break;
|
||||
case 'extensionManagement':
|
||||
this.manageExtensions(data, res)
|
||||
.catch(this.logService.error);
|
||||
break;
|
||||
default:
|
||||
res.writeHead(404);
|
||||
res.write(`Unknown message type: ${data.type}`, err => {
|
||||
@@ -150,14 +162,34 @@ export class CLIServerBase {
|
||||
res.end();
|
||||
}
|
||||
|
||||
private openExternal(data: OpenExternalCommandPipeArgs, res: http.ServerResponse) {
|
||||
private async openExternal(data: OpenExternalCommandPipeArgs, res: http.ServerResponse) {
|
||||
for (const uri of data.uris) {
|
||||
this._commands.executeCommand('_workbench.openExternal', URI.parse(uri), { allowTunneling: true });
|
||||
await this._commands.executeCommand('_remoteCLI.openExternal', URI.parse(uri), { allowTunneling: true });
|
||||
}
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
}
|
||||
|
||||
private async manageExtensions(data: ExtensionManagementPipeArgs, res: http.ServerResponse) {
|
||||
console.log('server: manageExtensions');
|
||||
try {
|
||||
const toExtOrVSIX = (inputs: string[] | undefined) => inputs?.map(input => /\.vsix$/i.test(input) ? URI.parse(input) : input);
|
||||
const commandArgs = {
|
||||
list: data.list,
|
||||
install: toExtOrVSIX(data.install),
|
||||
uninstall: toExtOrVSIX(data.uninstall),
|
||||
force: data.force
|
||||
};
|
||||
const output = await this._commands.executeCommand('_remoteCLI.manageExtensions', commandArgs, { allowTunneling: true });
|
||||
res.writeHead(200);
|
||||
res.write(output);
|
||||
} catch (e) {
|
||||
res.writeHead(500);
|
||||
res.write(String(e));
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
|
||||
private async getStatus(data: StatusPipeArgs, res: http.ServerResponse) {
|
||||
try {
|
||||
const status = await this._commands.executeCommand('_issues.getSystemStatus');
|
||||
|
||||
@@ -104,7 +104,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
|
||||
cwdForPrepareCommand = args.cwd;
|
||||
}
|
||||
|
||||
terminal.show();
|
||||
terminal.show(true);
|
||||
|
||||
const shellProcessId = await terminal.processId;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
|
||||
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
||||
import { MainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
@@ -13,7 +14,7 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer
|
||||
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
|
||||
class NodeModuleRequireInterceptor extends RequireInterceptor {
|
||||
@@ -62,10 +63,12 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
// Module loading tricks
|
||||
const interceptor = this._instaService.createInstance(NodeModuleRequireInterceptor, extensionApiFactory, this._registry);
|
||||
await interceptor.install();
|
||||
performance.mark('code/extHost/didInitAPI');
|
||||
|
||||
// Do this when extension service exists, but extensions are not being activated yet.
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData);
|
||||
performance.mark('code/extHost/didInitProxyResolver');
|
||||
|
||||
// Use IPC messages to forward console-calls, note that the console is
|
||||
// already patched to use`process.send()`
|
||||
@@ -84,7 +87,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
return extensionDescription.main;
|
||||
}
|
||||
|
||||
protected _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
protected _loadCommonJSModule<T>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
if (module.scheme !== Schemas.file) {
|
||||
throw new Error(`Cannot load URI: '${module}', must be of file-scheme`);
|
||||
}
|
||||
@@ -93,10 +96,16 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
this._logService.info(`ExtensionService#loadCommonJSModule ${module.toString(true)}`);
|
||||
this._logService.flush();
|
||||
try {
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/willLoadExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
r = require.__$__nodeRequire<T>(module.fsPath);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
} finally {
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/didLoadExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
activationTimesBuilder.codeLoadingStop();
|
||||
}
|
||||
return Promise.resolve(r);
|
||||
|
||||
@@ -50,11 +50,12 @@ export class ExtHostTask extends ExtHostTaskBase {
|
||||
}
|
||||
|
||||
public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise<vscode.TaskExecution> {
|
||||
if (!task.execution) {
|
||||
const tTask = (task as types.Task);
|
||||
|
||||
if (!task.execution && (tTask._id === undefined)) {
|
||||
throw new Error('Tasks to execute must include an execution');
|
||||
}
|
||||
|
||||
const tTask = (task as types.Task);
|
||||
// We have a preserved ID. So the task didn't change.
|
||||
if (tTask._id !== undefined) {
|
||||
// Always get the task execution first to prevent timing issues when retrieving it later
|
||||
@@ -121,7 +122,7 @@ export class ExtHostTask extends ExtHostTaskBase {
|
||||
private async getVariableResolver(workspaceFolders: vscode.WorkspaceFolder[]): Promise<ExtHostVariableResolverService> {
|
||||
if (this._variableResolver === undefined) {
|
||||
const configProvider = await this._configurationService.getConfigProvider();
|
||||
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment);
|
||||
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment, this.workspaceService);
|
||||
}
|
||||
return this._variableResolver;
|
||||
}
|
||||
|
||||
@@ -17,13 +17,15 @@ import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/ext
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService';
|
||||
import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
import { detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
|
||||
import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
|
||||
@@ -32,6 +34,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
|
||||
// TODO: Pull this from main side
|
||||
private _isWorkspaceShellAllowed: boolean = false;
|
||||
private _defaultShell: string | undefined;
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
@@ -42,20 +45,26 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
@IExtHostInitDataService private _extHostInitDataService: IExtHostInitDataService
|
||||
) {
|
||||
super(true, extHostRpc);
|
||||
|
||||
// Getting the SystemShell is an async operation, however, the ExtHost terminal service is mostly synchronous
|
||||
// and the API `vscode.env.shell` is also synchronous. The default shell _should_ be set when extensions are
|
||||
// starting up but if not, we run getSystemShellSync below which gets a sane default.
|
||||
getSystemShell(platform.platform).then(s => this._defaultShell = s);
|
||||
|
||||
this._updateLastActiveWorkspace();
|
||||
this._updateVariableResolver();
|
||||
this._registerListeners();
|
||||
}
|
||||
|
||||
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
|
||||
const terminal = new ExtHostTerminal(this._proxy, { name, shellPath, shellArgs }, name);
|
||||
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), { name, shellPath, shellArgs }, name);
|
||||
this._terminals.push(terminal);
|
||||
terminal.create(shellPath, shellArgs);
|
||||
return terminal;
|
||||
}
|
||||
|
||||
public createTerminalFromOptions(options: vscode.TerminalOptions, isFeatureTerminal?: boolean): vscode.Terminal {
|
||||
const terminal = new ExtHostTerminal(this._proxy, options, options.name);
|
||||
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
|
||||
this._terminals.push(terminal);
|
||||
terminal.create(
|
||||
withNullAsUndefined(options.shellPath),
|
||||
@@ -76,10 +85,11 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
.inspect<string | string[]>(key.substr(key.lastIndexOf('.') + 1));
|
||||
return this._apiInspectConfigToPlain<string | string[]>(setting);
|
||||
};
|
||||
|
||||
return terminalEnvironment.getDefaultShell(
|
||||
fetchSetting,
|
||||
this._isWorkspaceShellAllowed,
|
||||
getSystemShell(platform.platform),
|
||||
this._defaultShell ?? getSystemShellSync(platform.platform),
|
||||
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
|
||||
process.env.windir,
|
||||
terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver),
|
||||
@@ -139,7 +149,8 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
||||
executable: shellLaunchConfigDto.executable,
|
||||
args: shellLaunchConfigDto.args,
|
||||
cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd),
|
||||
env: shellLaunchConfigDto.env
|
||||
env: shellLaunchConfigDto.env,
|
||||
flowControl: shellLaunchConfigDto.flowControl
|
||||
};
|
||||
|
||||
// Merge in shell and args from settings
|
||||
|
||||
@@ -12,12 +12,16 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { exec } from 'child_process';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import * as fs from 'fs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { promisify } from 'util';
|
||||
import { MovingAverage } from 'vs/base/common/numbers';
|
||||
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
class ExtensionTunnel implements vscode.Tunnel {
|
||||
private _onDispose: Emitter<void> = new Emitter();
|
||||
@@ -26,14 +30,106 @@ class ExtensionTunnel implements vscode.Tunnel {
|
||||
constructor(
|
||||
public readonly remoteAddress: { port: number, host: string },
|
||||
public readonly localAddress: { port: number, host: string } | string,
|
||||
private readonly _dispose: () => void) { }
|
||||
private readonly _dispose: () => Promise<void>) { }
|
||||
|
||||
dispose(): void {
|
||||
dispose(): Promise<void> {
|
||||
this._onDispose.fire();
|
||||
this._dispose();
|
||||
return this._dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export function getSockets(stdout: string): { pid: number, socket: number }[] {
|
||||
const lines = stdout.trim().split('\n');
|
||||
const mapped: { pid: number, socket: number }[] = [];
|
||||
lines.forEach(line => {
|
||||
const match = /\/proc\/(\d+)\/fd\/\d+ -> socket:\[(\d+)\]/.exec(line)!;
|
||||
if (match && match.length >= 3) {
|
||||
mapped.push({
|
||||
pid: parseInt(match[1], 10),
|
||||
socket: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
});
|
||||
return mapped;
|
||||
}
|
||||
|
||||
export function loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] {
|
||||
const table = ([] as Record<string, string>[]).concat(...stdouts.map(loadConnectionTable));
|
||||
return [
|
||||
...new Map(
|
||||
table.filter(row => row.st === '0A')
|
||||
.map(row => {
|
||||
const address = row.local_address.split(':');
|
||||
return {
|
||||
socket: parseInt(row.inode, 10),
|
||||
ip: parseIpAddress(address[0]),
|
||||
port: parseInt(address[1], 16)
|
||||
};
|
||||
}).map(port => [port.ip + ':' + port.port, port])
|
||||
).values()
|
||||
];
|
||||
}
|
||||
|
||||
export function parseIpAddress(hex: string): string {
|
||||
let result = '';
|
||||
if (hex.length === 8) {
|
||||
for (let i = hex.length - 2; i >= 0; i -= 2) {
|
||||
result += parseInt(hex.substr(i, 2), 16);
|
||||
if (i !== 0) {
|
||||
result += '.';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = hex.length - 4; i >= 0; i -= 4) {
|
||||
result += parseInt(hex.substr(i, 4), 16).toString(16);
|
||||
if (i !== 0) {
|
||||
result += ':';
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function loadConnectionTable(stdout: string): Record<string, string>[] {
|
||||
const lines = stdout.trim().split('\n');
|
||||
const names = lines.shift()!.trim().split(/\s+/)
|
||||
.filter(name => name !== 'rx_queue' && name !== 'tm->when');
|
||||
const table = lines.map(line => line.trim().split(/\s+/).reduce((obj, value, i) => {
|
||||
obj[names[i] || i] = value;
|
||||
return obj;
|
||||
}, {} as Record<string, string>));
|
||||
return table;
|
||||
}
|
||||
|
||||
function knownExcludeCmdline(command: string): boolean {
|
||||
return !!command.match(/.*\.vscode-server-[a-zA-Z]+\/bin.*/)
|
||||
|| (command.indexOf('out/vs/server/main.js') !== -1)
|
||||
|| (command.indexOf('_productName=VSCode') !== -1);
|
||||
}
|
||||
|
||||
export async function findPorts(tcp: string, tcp6: string, procSockets: string, processes: { pid: number, cwd: string, cmd: string }[]): Promise<CandidatePort[]> {
|
||||
const connections: { socket: number, ip: string, port: number }[] = loadListeningPorts(tcp, tcp6);
|
||||
const sockets = getSockets(procSockets);
|
||||
|
||||
const socketMap = sockets.reduce((m, socket) => {
|
||||
m[socket.socket] = socket;
|
||||
return m;
|
||||
}, {} as Record<string, typeof sockets[0]>);
|
||||
const processMap = processes.reduce((m, process) => {
|
||||
m[process.pid] = process;
|
||||
return m;
|
||||
}, {} as Record<string, typeof processes[0]>);
|
||||
|
||||
const ports: CandidatePort[] = [];
|
||||
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
|
||||
const command = processMap[socketMap[socket].pid].cmd;
|
||||
if (!knownExcludeCmdline(command)) {
|
||||
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd, pid: socketMap[socket].pid });
|
||||
}
|
||||
});
|
||||
return ports;
|
||||
}
|
||||
|
||||
export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService {
|
||||
readonly _serviceBrand: undefined;
|
||||
private readonly _proxy: MainThreadTunnelServiceShape;
|
||||
@@ -42,15 +138,17 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
private _extensionTunnels: Map<string, Map<number, { tunnel: vscode.Tunnel, disposeListener: IDisposable }>> = new Map();
|
||||
private _onDidChangeTunnels: Emitter<void> = new Emitter<void>();
|
||||
onDidChangeTunnels: vscode.Event<void> = this._onDidChangeTunnels.event;
|
||||
private _candidateFindingEnabled: boolean = false;
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService);
|
||||
if (initData.remote.isRemote && initData.remote.authority) {
|
||||
this.registerCandidateFinder();
|
||||
if (isLinux && initData.remote.isRemote && initData.remote.authority) {
|
||||
this._proxy.$setCandidateFinder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,18 +168,30 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
return this._proxy.$getTunnels();
|
||||
}
|
||||
|
||||
registerCandidateFinder(): void {
|
||||
// Every two seconds, scan to see if the candidate ports have changed;
|
||||
if (isLinux) {
|
||||
let oldPorts: { host: string, port: number, detail: string }[] | undefined = undefined;
|
||||
setInterval(async () => {
|
||||
const newPorts = await this.findCandidatePorts();
|
||||
if (!oldPorts || (JSON.stringify(oldPorts) !== JSON.stringify(newPorts))) {
|
||||
oldPorts = newPorts;
|
||||
this._proxy.$onFoundNewCandidates(oldPorts.filter(async (candidate) => await this._showCandidatePort(candidate.host, candidate.port, candidate.detail)));
|
||||
return;
|
||||
}
|
||||
}, 2000);
|
||||
private calculateDelay(movingAverage: number) {
|
||||
// Some local testing indicated that the moving average might be between 50-100 ms.
|
||||
return Math.max(movingAverage * 20, 2000);
|
||||
}
|
||||
|
||||
async $registerCandidateFinder(enable: boolean): Promise<void> {
|
||||
if (enable && this._candidateFindingEnabled) {
|
||||
// already enabled
|
||||
return;
|
||||
}
|
||||
this._candidateFindingEnabled = enable;
|
||||
// Regularly scan to see if the candidate ports have changed.
|
||||
let movingAverage = new MovingAverage();
|
||||
let oldPorts: { host: string, port: number, detail: string }[] | undefined = undefined;
|
||||
while (this._candidateFindingEnabled) {
|
||||
const startTime = new Date().getTime();
|
||||
const newPorts = await this.findCandidatePorts();
|
||||
const timeTaken = new Date().getTime() - startTime;
|
||||
movingAverage.update(timeTaken);
|
||||
if (!oldPorts || (JSON.stringify(oldPorts) !== JSON.stringify(newPorts))) {
|
||||
oldPorts = newPorts;
|
||||
await this._proxy.$onFoundNewCandidates(oldPorts);
|
||||
}
|
||||
await (new Promise<void>(resolve => setTimeout(() => resolve(), this.calculateDelay(movingAverage.value))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,15 +199,18 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
if (provider) {
|
||||
if (provider.showCandidatePort) {
|
||||
this._showCandidatePort = provider.showCandidatePort;
|
||||
await this._proxy.$setCandidateFilter();
|
||||
}
|
||||
if (provider.tunnelFactory) {
|
||||
this._forwardPortProvider = provider.tunnelFactory;
|
||||
await this._proxy.$setTunnelProvider();
|
||||
await this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? {
|
||||
elevation: false,
|
||||
public: false
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._forwardPortProvider = undefined;
|
||||
}
|
||||
await this._proxy.$tunnelServiceReady();
|
||||
return toDisposable(() => {
|
||||
this._forwardPortProvider = undefined;
|
||||
});
|
||||
@@ -110,7 +223,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
if (silent) {
|
||||
hostMap.get(remote.port)!.disposeListener.dispose();
|
||||
}
|
||||
hostMap.get(remote.port)!.tunnel.dispose();
|
||||
await hostMap.get(remote.port)!.tunnel.dispose();
|
||||
hostMap.delete(remote.port);
|
||||
}
|
||||
}
|
||||
@@ -120,31 +233,42 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
this._onDidChangeTunnels.fire();
|
||||
}
|
||||
|
||||
$forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto> | undefined {
|
||||
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> {
|
||||
if (this._forwardPortProvider) {
|
||||
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
|
||||
if (providedPort !== undefined) {
|
||||
return asPromise(() => providedPort).then(tunnel => {
|
||||
try {
|
||||
this.logService.trace('$forwardPort: Getting tunnel from provider.');
|
||||
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
|
||||
this.logService.trace('$forwardPort: Got tunnel promise from provider.');
|
||||
if (providedPort !== undefined) {
|
||||
const tunnel = await providedPort;
|
||||
this.logService.trace('$forwardPort: Successfully awaited tunnel from provider.');
|
||||
if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) {
|
||||
this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map());
|
||||
}
|
||||
const disposeListener = this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress)));
|
||||
this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener });
|
||||
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
|
||||
});
|
||||
return TunnelDto.fromApiTunnel(tunnel);
|
||||
} else {
|
||||
this.logService.trace('$forwardPort: Tunnel is undefined');
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.trace('$forwardPort: tunnel provider error');
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async $applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]> {
|
||||
const filter = await Promise.all(candidates.map(candidate => this._showCandidatePort(candidate.host, candidate.port, candidate.detail)));
|
||||
return candidates.filter((candidate, index) => filter[index]);
|
||||
}
|
||||
|
||||
async findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> {
|
||||
const ports: { host: string, port: number, detail: string }[] = [];
|
||||
async findCandidatePorts(): Promise<CandidatePort[]> {
|
||||
let tcp: string = '';
|
||||
let tcp6: string = '';
|
||||
try {
|
||||
tcp = fs.readFileSync('/proc/net/tcp', 'utf8');
|
||||
tcp6 = fs.readFileSync('/proc/net/tcp6', 'utf8');
|
||||
tcp = await pfs.readFile('/proc/net/tcp', 'utf8');
|
||||
tcp6 = await pfs.readFile('/proc/net/tcp6', 'utf8');
|
||||
} catch (e) {
|
||||
// File reading error. No additional handling needed.
|
||||
}
|
||||
@@ -154,105 +278,24 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
|
||||
});
|
||||
}));
|
||||
|
||||
const procChildren = fs.readdirSync('/proc');
|
||||
const processes: { pid: number, cwd: string, cmd: string }[] = [];
|
||||
const procChildren = await pfs.readdir('/proc');
|
||||
const processes: {
|
||||
pid: number, cwd: string, cmd: string
|
||||
}[] = [];
|
||||
for (let childName of procChildren) {
|
||||
try {
|
||||
const pid: number = Number(childName);
|
||||
const childUri = resources.joinPath(URI.file('/proc'), childName);
|
||||
const childStat = fs.statSync(childUri.fsPath);
|
||||
const childStat = await pfs.stat(childUri.fsPath);
|
||||
if (childStat.isDirectory() && !isNaN(pid)) {
|
||||
const cwd = fs.readlinkSync(resources.joinPath(childUri, 'cwd').fsPath);
|
||||
const cmd = fs.readFileSync(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8');
|
||||
const cwd = await promisify(fs.readlink)(resources.joinPath(childUri, 'cwd').fsPath);
|
||||
const cmd = await pfs.readFile(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8');
|
||||
processes.push({ pid, cwd, cmd });
|
||||
}
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
const connections: { socket: number, ip: string, port: number }[] = this.loadListeningPorts(tcp, tcp6);
|
||||
const sockets = this.getSockets(procSockets);
|
||||
|
||||
const socketMap = sockets.reduce((m, socket) => {
|
||||
m[socket.socket] = socket;
|
||||
return m;
|
||||
}, {} as Record<string, typeof sockets[0]>);
|
||||
const processMap = processes.reduce((m, process) => {
|
||||
m[process.pid] = process;
|
||||
return m;
|
||||
}, {} as Record<string, typeof processes[0]>);
|
||||
|
||||
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
|
||||
const command = processMap[socketMap[socket].pid].cmd;
|
||||
if (!command.match(/.*\.vscode-server-[a-zA-Z]+\/bin.*/) && (command.indexOf('out/vs/server/main.js') === -1)) {
|
||||
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd });
|
||||
}
|
||||
});
|
||||
|
||||
return ports;
|
||||
}
|
||||
|
||||
private getSockets(stdout: string): { pid: number, socket: number }[] {
|
||||
const lines = stdout.trim().split('\n');
|
||||
const mapped: { pid: number, socket: number }[] = [];
|
||||
lines.forEach(line => {
|
||||
const match = /\/proc\/(\d+)\/fd\/\d+ -> socket:\[(\d+)\]/.exec(line)!;
|
||||
if (match && match.length >= 3) {
|
||||
mapped.push({
|
||||
pid: parseInt(match[1], 10),
|
||||
socket: parseInt(match[2], 10)
|
||||
});
|
||||
}
|
||||
});
|
||||
return mapped;
|
||||
}
|
||||
|
||||
private loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] {
|
||||
const table = ([] as Record<string, string>[]).concat(...stdouts.map(this.loadConnectionTable));
|
||||
return [
|
||||
...new Map(
|
||||
table.filter(row => row.st === '0A')
|
||||
.map(row => {
|
||||
const address = row.local_address.split(':');
|
||||
return {
|
||||
socket: parseInt(row.inode, 10),
|
||||
ip: this.parseIpAddress(address[0]),
|
||||
port: parseInt(address[1], 16)
|
||||
};
|
||||
}).map(port => [port.ip + ':' + port.port, port])
|
||||
).values()
|
||||
];
|
||||
}
|
||||
|
||||
private parseIpAddress(hex: string): string {
|
||||
let result = '';
|
||||
if (hex.length === 8) {
|
||||
for (let i = hex.length - 2; i >= 0; i -= 2) {
|
||||
result += parseInt(hex.substr(i, 2), 16);
|
||||
if (i !== 0) {
|
||||
result += '.';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = hex.length - 4; i >= 0; i -= 4) {
|
||||
result += parseInt(hex.substr(i, 4), 16).toString(16);
|
||||
if (i !== 0) {
|
||||
result += ':';
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private loadConnectionTable(stdout: string): Record<string, string>[] {
|
||||
const lines = stdout.trim().split('\n');
|
||||
const names = lines.shift()!.trim().split(/\s+/)
|
||||
.filter(name => name !== 'rx_queue' && name !== 'tm->when');
|
||||
const table = lines.map(line => line.trim().split(/\s+/).reduce((obj, value, i) => {
|
||||
obj[names[i] || i] = value;
|
||||
return obj;
|
||||
}, {} as Record<string, string>));
|
||||
return table;
|
||||
return findPorts(tcp, tcp6, procSockets, processes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,38 @@ import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHost
|
||||
import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
namespace TrustedFunction {
|
||||
|
||||
// workaround a chrome issue not allowing to create new functions
|
||||
// see https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
|
||||
const ttpTrustedFunction = self.trustedTypes?.createPolicy('TrustedFunctionWorkaround', {
|
||||
createScript: (_, ...args: string[]) => {
|
||||
args.forEach((arg) => {
|
||||
if (!self.trustedTypes?.isScript(arg)) {
|
||||
throw new Error('TrustedScripts only, please');
|
||||
}
|
||||
});
|
||||
// NOTE: This is insecure without parsing the arguments and body,
|
||||
// Malicious inputs can escape the function body and execute immediately!
|
||||
const fnArgs = args.slice(0, -1).join(',');
|
||||
const fnBody = args.pop()!.toString();
|
||||
const body = `(function anonymous(${fnArgs}) {\n${fnBody}\n})`;
|
||||
return body;
|
||||
}
|
||||
});
|
||||
|
||||
export function create(...args: string[]): Function {
|
||||
if (!ttpTrustedFunction) {
|
||||
return new Function(...args);
|
||||
}
|
||||
return self.eval(ttpTrustedFunction.createScript('', ...args) as unknown as string);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkerRequireInterceptor extends RequireInterceptor {
|
||||
|
||||
_installInterceptor() { }
|
||||
@@ -35,6 +63,8 @@ class WorkerRequireInterceptor extends RequireInterceptor {
|
||||
export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
readonly extensionRuntime = ExtensionRuntime.Webworker;
|
||||
|
||||
private static _ttpExtensionScripts = self.trustedTypes?.createPolicy('ExtensionScripts', { createScript: source => source });
|
||||
|
||||
private _fakeModules?: WorkerRequireInterceptor;
|
||||
|
||||
protected async _beforeAlmostReadyToRunExtensions(): Promise<void> {
|
||||
@@ -42,6 +72,8 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
const apiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors);
|
||||
this._fakeModules = this._instaService.createInstance(WorkerRequireInterceptor, apiFactory, this._registry);
|
||||
await this._fakeModules.install();
|
||||
performance.mark('code/extHost/didInitAPI');
|
||||
|
||||
await this._waitForDebuggerAttachment();
|
||||
}
|
||||
|
||||
@@ -49,10 +81,16 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
return extensionDescription.browser;
|
||||
}
|
||||
|
||||
protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
protected async _loadCommonJSModule<T>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
|
||||
module = module.with({ path: ensureSuffix(module.path, '.js') });
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/willFetchExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
const response = await fetch(module.toString(true));
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/didFetchExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(response.statusText);
|
||||
@@ -63,7 +101,25 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
// Here we append #vscode-extension to serve as a marker, such that source maps
|
||||
// can be adjusted for the extra wrapping function.
|
||||
const sourceURL = `${module.toString(true)}#vscode-extension`;
|
||||
const initFn = new Function('module', 'exports', 'require', `${source}\n//# sourceURL=${sourceURL}`);
|
||||
const fullSource = `${source}\n//# sourceURL=${sourceURL}`;
|
||||
let initFn: Function;
|
||||
try {
|
||||
initFn = TrustedFunction.create(
|
||||
ExtHostExtensionService._ttpExtensionScripts?.createScript('module') as unknown as string ?? 'module',
|
||||
ExtHostExtensionService._ttpExtensionScripts?.createScript('exports') as unknown as string ?? 'exports',
|
||||
ExtHostExtensionService._ttpExtensionScripts?.createScript('require') as unknown as string ?? 'require',
|
||||
ExtHostExtensionService._ttpExtensionScripts?.createScript(fullSource) as unknown as string ?? fullSource
|
||||
);
|
||||
} catch (err) {
|
||||
if (extensionId) {
|
||||
console.error(`Loading code for extension ${extensionId.value} failed: ${err.message}`);
|
||||
} else {
|
||||
console.error(`Loading code failed: ${err.message}`);
|
||||
}
|
||||
console.error(`${module.toString(true)}${typeof err.line === 'number' ? ` line ${err.line}` : ''}${typeof err.column === 'number' ? ` column ${err.column}` : ''}`);
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
// define commonjs globals: `module`, `exports`, and `require`
|
||||
const _exports = {};
|
||||
@@ -78,9 +134,15 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
||||
|
||||
try {
|
||||
activationTimesBuilder.codeLoadingStart();
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/willLoadExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
initFn(_module, _exports, _require);
|
||||
return <T>(_module.exports !== _exports ? _module.exports : _exports);
|
||||
} finally {
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/didLoadExtensionCode/${extensionId.value}`);
|
||||
}
|
||||
activationTimesBuilder.codeLoadingStop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,25 +8,21 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor, MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions, CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService } 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, 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';
|
||||
import { isWindows, isLinux, isWeb } from 'vs/base/common/platform';
|
||||
import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
|
||||
import { IViewDescriptorService, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor } from 'vs/workbench/common/views';
|
||||
import { IViewDescriptorService, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor, ViewContainerLocationToString } from 'vs/workbench/common/views';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
|
||||
|
||||
@@ -125,54 +121,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
order: 3
|
||||
});
|
||||
|
||||
// --- Toggle Editor Layout
|
||||
|
||||
export class ToggleEditorLayoutAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.toggleEditorGroupLayout';
|
||||
static readonly LABEL = nls.localize('flipLayout', "Toggle Vertical/Horizontal Editor Layout");
|
||||
|
||||
private readonly toDispose = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.class = Codicon.editorLayout.classNames;
|
||||
this.updateEnablement();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.add(this.editorGroupService.onDidAddGroup(() => this.updateEnablement()));
|
||||
this.toDispose.add(this.editorGroupService.onDidRemoveGroup(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
private updateEnablement(): void {
|
||||
this.enabled = this.editorGroupService.count > 1;
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const newOrientation = (this.editorGroupService.orientation === GroupOrientation.VERTICAL) ? GroupOrientation.HORIZONTAL : GroupOrientation.VERTICAL;
|
||||
this.editorGroupService.setGroupOrientation(newOrientation);
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleEditorLayoutAction, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Vertical/Horizontal Editor Layout', CATEGORIES.View.value);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: 'z_flip',
|
||||
command: {
|
||||
id: ToggleEditorLayoutAction.ID,
|
||||
title: nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
// --- Toggle Sidebar Position
|
||||
|
||||
export class ToggleSidebarPositionAction extends Action {
|
||||
@@ -203,7 +151,64 @@ export class ToggleSidebarPositionAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSidebarPositionAction), 'View: Toggle Side Bar Position', CATEGORIES.View.value);
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: { value: nls.localize('toggleSidebarPosition', "Toggle Side Bar Position"), original: 'Toggle Side Bar Position' },
|
||||
category: CATEGORIES.View,
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor) {
|
||||
accessor.get(IInstantiationService).createInstance(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL).run();
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItems([{
|
||||
id: MenuId.ViewContainerTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: nls.localize('move sidebar right', "Move Side Bar Right")
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 1
|
||||
}
|
||||
}, {
|
||||
id: MenuId.ViewTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: nls.localize('move sidebar right', "Move Side Bar Right")
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 1
|
||||
}
|
||||
}, {
|
||||
id: MenuId.ViewContainerTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: nls.localize('move sidebar left', "Move Side Bar Left")
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 1
|
||||
}
|
||||
}, {
|
||||
id: MenuId.ViewTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: nls.localize('move sidebar left', "Move Side Bar Left")
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 1
|
||||
}
|
||||
}]);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '3_workbench_layout_move',
|
||||
@@ -256,27 +261,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
order: 5
|
||||
});
|
||||
|
||||
export class ToggleSidebarVisibilityAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.toggleSidebarVisibility';
|
||||
static readonly LABEL = nls.localize('toggleSidebar', "Toggle Side Bar Visibility");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const hideSidebar = this.layoutService.isVisible(Parts.SIDEBAR_PART);
|
||||
this.layoutService.setSideBarHidden(hideSidebar);
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSidebarVisibilityAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', CATEGORIES.View.value);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
group: '2_appearance',
|
||||
title: nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"),
|
||||
@@ -284,10 +268,53 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
order: 1
|
||||
});
|
||||
|
||||
export const TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID = 'workbench.action.toggleSidebarVisibility';
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
|
||||
title: { value: nls.localize('toggleSidebar', "Toggle Side Bar Visibility"), original: 'Toggle Side Bar Visibility' },
|
||||
category: CATEGORIES.View,
|
||||
f1: true,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_B
|
||||
}
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor) {
|
||||
const layoutService = accessor.get(IWorkbenchLayoutService);
|
||||
layoutService.setSideBarHidden(layoutService.isVisible(Parts.SIDEBAR_PART));
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItems([{
|
||||
id: MenuId.ViewContainerTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
|
||||
title: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
|
||||
},
|
||||
when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 2
|
||||
}
|
||||
}, {
|
||||
id: MenuId.ViewTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
|
||||
title: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
|
||||
},
|
||||
when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 2
|
||||
}
|
||||
}]);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
command: {
|
||||
id: ToggleSidebarVisibilityAction.ID,
|
||||
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
|
||||
title: nls.localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Side Bar"),
|
||||
toggled: SideBarVisibleContext
|
||||
},
|
||||
@@ -413,32 +440,16 @@ export class ToggleMenuBarAction extends Action {
|
||||
static readonly ID = 'workbench.action.toggleMenuBar';
|
||||
static readonly LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar");
|
||||
|
||||
private static readonly menuBarVisibilityKey = 'window.menuBarVisibility';
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<void> {
|
||||
let currentVisibilityValue = getMenuBarVisibility(this.configurationService);
|
||||
if (typeof currentVisibilityValue !== 'string') {
|
||||
currentVisibilityValue = 'default';
|
||||
}
|
||||
|
||||
let newVisibilityValue: string;
|
||||
if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') {
|
||||
newVisibilityValue = 'toggle';
|
||||
} else if (currentVisibilityValue === 'compact') {
|
||||
newVisibilityValue = 'hidden';
|
||||
} else {
|
||||
newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default';
|
||||
}
|
||||
|
||||
return this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER);
|
||||
async run(): Promise<void> {
|
||||
this.layoutService.toggleMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,19 +490,18 @@ export class ResetViewLocationsAction extends Action {
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ResetViewLocationsAction), 'View: Reset View Locations', CATEGORIES.View.value);
|
||||
|
||||
// --- Toggle View with Command
|
||||
export abstract class ToggleViewAction extends Action {
|
||||
export class ToggleViewAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private readonly viewId: string,
|
||||
protected viewsService: IViewsService,
|
||||
protected viewDescriptorService: IViewDescriptorService,
|
||||
protected contextKeyService: IContextKeyService,
|
||||
private layoutService: IWorkbenchLayoutService,
|
||||
cssClass?: string
|
||||
@IViewsService protected viewsService: IViewsService,
|
||||
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService protected contextKeyService: IContextKeyService,
|
||||
@IWorkbenchLayoutService private layoutService: IWorkbenchLayoutService,
|
||||
) {
|
||||
super(id, label, cssClass);
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
||||
@@ -17,7 +17,6 @@ import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/c
|
||||
import { Direction } from 'vs/base/browser/ui/grid/grid';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { isAncestor } from 'vs/base/browser/dom';
|
||||
|
||||
abstract class BaseNavigationAction extends Action {
|
||||
|
||||
@@ -215,7 +214,8 @@ function findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Part
|
||||
}
|
||||
|
||||
function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorService: IEditorService, next: boolean): void {
|
||||
const currentlyFocusedPart = isActiveElementInNotebookEditor(editorService) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.EDITOR_PART) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART :
|
||||
const editorFocused = editorService.activeEditorPane?.hasFocus();
|
||||
const currentlyFocusedPart = editorFocused ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART :
|
||||
layoutService.hasFocus(Parts.STATUSBAR_PART) ? Parts.STATUSBAR_PART : layoutService.hasFocus(Parts.SIDEBAR_PART) ? Parts.SIDEBAR_PART : layoutService.hasFocus(Parts.PANEL_PART) ? Parts.PANEL_PART : undefined;
|
||||
let partToFocus = Parts.EDITOR_PART;
|
||||
if (currentlyFocusedPart) {
|
||||
@@ -225,17 +225,6 @@ function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorS
|
||||
layoutService.focusPart(partToFocus);
|
||||
}
|
||||
|
||||
function isActiveElementInNotebookEditor(editorService: IEditorService): boolean {
|
||||
const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined;
|
||||
if (activeEditorPane?.isNotebookEditor) {
|
||||
const control = editorService.activeEditorPane?.getControl() as { getDomNode(): HTMLElement; getOverflowContainerDomNode(): HTMLElement; };
|
||||
const activeElement = document.activeElement;
|
||||
return isAncestor(activeElement, control.getDomNode()) || isAncestor(activeElement, control.getOverflowContainerDomNode());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export class FocusNextPart extends Action {
|
||||
static readonly ID = 'workbench.action.focusNextPart';
|
||||
static readonly LABEL = nls.localize('focusNextPart', "Focus Next Part");
|
||||
|
||||
@@ -76,23 +76,26 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo
|
||||
|
||||
// Context menu support in input/textarea
|
||||
this.layoutService.container.addEventListener('contextmenu', e => this.onContextMenu(e));
|
||||
|
||||
}
|
||||
|
||||
private onContextMenu(e: MouseEvent): void {
|
||||
if (e.target instanceof HTMLElement) {
|
||||
const target = <HTMLElement>e.target;
|
||||
if (target.nodeName && (target.nodeName.toLowerCase() === 'input' || target.nodeName.toLowerCase() === 'textarea')) {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e,
|
||||
getActions: () => this.textInputActions,
|
||||
getActionsContext: () => target,
|
||||
onHide: () => target.focus() // fixes https://github.com/microsoft/vscode/issues/52948
|
||||
});
|
||||
}
|
||||
if (e.defaultPrevented) {
|
||||
return; // make sure to not show these actions by accident if component indicated to prevent
|
||||
}
|
||||
|
||||
const target = e.target;
|
||||
if (!(target instanceof HTMLElement) || (target.nodeName.toLowerCase() !== 'input' && target.nodeName.toLowerCase() !== 'textarea')) {
|
||||
return; // only for inputs or textareas
|
||||
}
|
||||
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e,
|
||||
getActions: () => this.textInputActions,
|
||||
getActionsContext: () => target,
|
||||
onHide: () => target.focus() // fixes https://github.com/microsoft/vscode/issues/52948
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import { ResourceMap } from 'vs/base/common/map';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { isHTMLElement } from 'vs/base/browser/dom';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export const inRecentFilesPickerContextKey = 'inRecentFilesPicker';
|
||||
|
||||
@@ -49,12 +49,17 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
tooltip: nls.localize('remove', "Remove from Recently Opened")
|
||||
};
|
||||
|
||||
private readonly dirtyRecentlyOpened: IQuickInputButton = {
|
||||
private readonly dirtyRecentlyOpenedFolder: IQuickInputButton = {
|
||||
iconClass: 'dirty-workspace ' + Codicon.closeDirty.classNames,
|
||||
tooltip: nls.localize('dirtyRecentlyOpened', "Workspace With Dirty Files"),
|
||||
tooltip: nls.localize('dirtyRecentlyOpenedFolder', "Folder With Unsaved Files"),
|
||||
alwaysVisible: true
|
||||
};
|
||||
|
||||
private readonly dirtyRecentlyOpenedWorkspace: IQuickInputButton = {
|
||||
...this.dirtyRecentlyOpenedFolder,
|
||||
tooltip: nls.localize('dirtyRecentlyOpenedWorkspace', "Workspace With Unsaved Files"),
|
||||
};
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@@ -77,7 +82,9 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
const recentlyOpened = await this.workspacesService.getRecentlyOpened();
|
||||
const dirtyWorkspacesAndFolders = await this.workspacesService.getDirtyWorkspaces();
|
||||
|
||||
// Identify all folders and workspaces with dirty files
|
||||
let hasWorkspaces = false;
|
||||
|
||||
// Identify all folders and workspaces with unsaved files
|
||||
const dirtyFolders = new ResourceMap<boolean>();
|
||||
const dirtyWorkspaces = new ResourceMap<IWorkspaceIdentifier>();
|
||||
for (const dirtyWorkspace of dirtyWorkspacesAndFolders) {
|
||||
@@ -85,6 +92,7 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
dirtyFolders.set(dirtyWorkspace, true);
|
||||
} else {
|
||||
dirtyWorkspaces.set(dirtyWorkspace.configPath, dirtyWorkspace);
|
||||
hasWorkspaces = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +104,7 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
recentFolders.set(recent.folderUri, true);
|
||||
} else {
|
||||
recentWorkspaces.set(recent.workspace.configPath, recent.workspace);
|
||||
hasWorkspaces = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +133,7 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
|
||||
let keyMods: IKeyMods | undefined;
|
||||
|
||||
const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('workspaces', "workspaces") };
|
||||
const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: hasWorkspaces ? nls.localize('workspacesAndFolders', "folders & workspaces") : nls.localize('folders', "folders") };
|
||||
const fileSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('files', "files") };
|
||||
const picks = [workspaceSeparator, ...workspacePicks, fileSeparator, ...filePicks];
|
||||
|
||||
@@ -143,13 +152,14 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
context.removeItem();
|
||||
}
|
||||
|
||||
// Dirty Workspace
|
||||
else if (context.button === this.dirtyRecentlyOpened) {
|
||||
// Dirty Folder/Workspace
|
||||
else if (context.button === this.dirtyRecentlyOpenedFolder || context.button === this.dirtyRecentlyOpenedWorkspace) {
|
||||
const isDirtyWorkspace = context.button === this.dirtyRecentlyOpenedWorkspace;
|
||||
const result = await this.dialogService.confirm({
|
||||
type: 'question',
|
||||
title: nls.localize('dirtyWorkspace', "Workspace with Dirty Files"),
|
||||
message: nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the dirty files?"),
|
||||
detail: nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with dirty files cannot be removed until all dirty files have been saved or reverted.")
|
||||
title: isDirtyWorkspace ? nls.localize('dirtyWorkspace', "Workspace with Unsaved Files") : nls.localize('dirtyFolder', "Folder with Unsaved Files"),
|
||||
message: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the unsaved files?") : nls.localize('dirtyFolderConfirm', "Do you want to open the folder to review the unsaved files?"),
|
||||
detail: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with unsaved files cannot be removed until all unsaved files have been saved or reverted.") : nls.localize('dirtyFolderConfirmDetail', "Folders with unsaved files cannot be removed until all unsaved files have been saved or reverted.")
|
||||
});
|
||||
|
||||
if (result.confirmed) {
|
||||
@@ -170,6 +180,7 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
let iconClasses: string[];
|
||||
let fullLabel: string | undefined;
|
||||
let resource: URI | undefined;
|
||||
let isWorkspace = false;
|
||||
|
||||
// Folder
|
||||
if (isRecentFolder(recent)) {
|
||||
@@ -185,6 +196,7 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.ROOT_FOLDER);
|
||||
openable = { workspaceUri: resource };
|
||||
fullLabel = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true });
|
||||
isWorkspace = true;
|
||||
}
|
||||
|
||||
// File
|
||||
@@ -200,9 +212,9 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
return {
|
||||
iconClasses,
|
||||
label: name,
|
||||
ariaLabel: isDirty ? nls.localize('recentDirtyAriaLabel', "{0}, dirty workspace", name) : name,
|
||||
ariaLabel: isDirty ? isWorkspace ? nls.localize('recentDirtyWorkspaceAriaLabel', "{0}, workspace with unsaved changes", name) : nls.localize('recentDirtyFolderAriaLabel', "{0}, folder with unsaved changes", name) : name,
|
||||
description: parentPath,
|
||||
buttons: isDirty ? [this.dirtyRecentlyOpened] : [this.removeFromRecentlyOpened],
|
||||
buttons: isDirty ? [isWorkspace ? this.dirtyRecentlyOpenedWorkspace : this.dirtyRecentlyOpenedFolder] : [this.removeFromRecentlyOpened],
|
||||
openable,
|
||||
resource
|
||||
};
|
||||
@@ -405,7 +417,7 @@ CommandsRegistry.registerCommand('workbench.action.toggleConfirmBeforeClose', ac
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const setting = configurationService.inspect<'always' | 'keyboardOnly' | 'never'>('window.confirmBeforeClose').userValue;
|
||||
|
||||
return configurationService.updateValue('window.confirmBeforeClose', setting === 'never' ? 'keyboardOnly' : 'never', ConfigurationTarget.USER);
|
||||
return configurationService.updateValue('window.confirmBeforeClose', setting === 'never' ? 'keyboardOnly' : 'never');
|
||||
});
|
||||
|
||||
// --- Menu Registration
|
||||
|
||||
@@ -114,7 +114,7 @@ export class CloseWorkspaceAction extends Action {
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
|
||||
this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close."));
|
||||
this.notificationService.info(nls.localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ export class SaveWorkspaceAsAction extends Action {
|
||||
export class DuplicateWorkspaceInNewWindowAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow';
|
||||
static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window");
|
||||
static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate As Workspace in New Window");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -259,7 +259,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(AddRootFolderAction),
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalRemoveRootFolderAction), 'Workspaces: Remove Folder from Workspace...', workspacesCategory);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseWorkspaceAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory, EmptyWorkspaceSupportContext);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(SaveWorkspaceAsAction), 'Workspaces: Save Workspace As...', workspacesCategory, EmptyWorkspaceSupportContext);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateWorkspaceInNewWindowAction), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateWorkspaceInNewWindowAction), 'Workspaces: Duplicate As Workspace in New Window', workspacesCategory);
|
||||
|
||||
// --- Menu Registration
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
|
||||
import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -15,11 +15,121 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { Disposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
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';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
|
||||
|
||||
export interface IRangeHighlightDecoration {
|
||||
resource: URI;
|
||||
range: IRange;
|
||||
isWholeLine?: boolean;
|
||||
}
|
||||
|
||||
export class RangeHighlightDecorations extends Disposable {
|
||||
|
||||
private readonly _onHighlightRemoved = this._register(new Emitter<void>());
|
||||
readonly onHighlightRemoved = this._onHighlightRemoved.event;
|
||||
|
||||
private rangeHighlightDecorationId: string | null = null;
|
||||
private editor: ICodeEditor | null = null;
|
||||
private readonly editorDisposables = this._register(new DisposableStore());
|
||||
|
||||
constructor(@IEditorService private readonly editorService: IEditorService) {
|
||||
super();
|
||||
}
|
||||
|
||||
removeHighlightRange() {
|
||||
if (this.editor && this.editor.getModel() && this.rangeHighlightDecorationId) {
|
||||
this.editor.deltaDecorations([this.rangeHighlightDecorationId], []);
|
||||
this._onHighlightRemoved.fire();
|
||||
}
|
||||
|
||||
this.rangeHighlightDecorationId = null;
|
||||
}
|
||||
|
||||
highlightRange(range: IRangeHighlightDecoration, editor?: any) {
|
||||
editor = editor ?? this.getEditor(range);
|
||||
if (isCodeEditor(editor)) {
|
||||
this.doHighlightRange(editor, range);
|
||||
} else if (isCompositeEditor(editor) && isCodeEditor(editor.activeCodeEditor)) {
|
||||
this.doHighlightRange(editor.activeCodeEditor, range);
|
||||
}
|
||||
}
|
||||
|
||||
private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) {
|
||||
this.removeHighlightRange();
|
||||
|
||||
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
|
||||
this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine));
|
||||
});
|
||||
|
||||
this.setEditor(editor);
|
||||
}
|
||||
|
||||
private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined {
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
const resource = activeEditor && activeEditor.resource;
|
||||
if (resource && isEqual(resource, resourceRange.resource)) {
|
||||
return this.editorService.activeTextEditorControl as ICodeEditor;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private setEditor(editor: ICodeEditor) {
|
||||
if (this.editor !== editor) {
|
||||
this.editorDisposables.clear();
|
||||
this.editor = editor;
|
||||
this.editorDisposables.add(this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
|
||||
if (
|
||||
e.reason === CursorChangeReason.NotSet
|
||||
|| e.reason === CursorChangeReason.Explicit
|
||||
|| e.reason === CursorChangeReason.Undo
|
||||
|| e.reason === CursorChangeReason.Redo
|
||||
) {
|
||||
this.removeHighlightRange();
|
||||
}
|
||||
}));
|
||||
this.editorDisposables.add(this.editor.onDidChangeModel(() => { this.removeHighlightRange(); }));
|
||||
this.editorDisposables.add(this.editor.onDidDispose(() => {
|
||||
this.removeHighlightRange();
|
||||
this.editor = null;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly _WHOLE_LINE_RANGE_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
});
|
||||
|
||||
private static readonly _RANGE_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight'
|
||||
});
|
||||
|
||||
private createRangeHighlightDecoration(isWholeLine: boolean = true): ModelDecorationOptions {
|
||||
return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
if (this.editor && this.editor.getModel()) {
|
||||
this.removeHighlightRange();
|
||||
this.editor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class FloatingClickWidget extends Widget implements IOverlayWidget {
|
||||
|
||||
@@ -56,15 +56,26 @@ export abstract class Composite extends Component implements IComposite {
|
||||
return this._onDidBlur.event;
|
||||
}
|
||||
|
||||
private _hasFocus = false;
|
||||
hasFocus(): boolean {
|
||||
return this._hasFocus;
|
||||
}
|
||||
|
||||
private registerFocusTrackEvents(): { onDidFocus: Emitter<void>, onDidBlur: Emitter<void> } {
|
||||
const container = assertIsDefined(this.getContainer());
|
||||
const focusTracker = this._register(trackFocus(container));
|
||||
|
||||
const onDidFocus = this._onDidFocus = this._register(new Emitter<void>());
|
||||
this._register(focusTracker.onDidFocus(() => onDidFocus.fire()));
|
||||
this._register(focusTracker.onDidFocus(() => {
|
||||
this._hasFocus = true;
|
||||
onDidFocus.fire();
|
||||
}));
|
||||
|
||||
const onDidBlur = this._onDidBlur = this._register(new Emitter<void>());
|
||||
this._register(focusTracker.onDidBlur(() => onDidBlur.fire()));
|
||||
this._register(focusTracker.onDidBlur(() => {
|
||||
this._hasFocus = false;
|
||||
onDidBlur.fire();
|
||||
}));
|
||||
|
||||
return { onDidFocus, onDidBlur };
|
||||
}
|
||||
@@ -234,7 +245,6 @@ export abstract class CompositeDescriptor<T extends Composite> {
|
||||
readonly cssClass?: string,
|
||||
readonly order?: number,
|
||||
readonly requestedIndex?: number,
|
||||
readonly keybindingId?: string,
|
||||
) { }
|
||||
|
||||
instantiate(instantiationService: IInstantiationService): T {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/
|
||||
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
|
||||
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { PanelPositionContext } from 'vs/workbench/common/panel';
|
||||
import { PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/panel';
|
||||
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { isNative } from 'vs/base/common/platform';
|
||||
@@ -64,6 +64,8 @@ export class WorkbenchContextKeysHandler extends Disposable {
|
||||
private sideBarVisibleContext: IContextKey<boolean>;
|
||||
private editorAreaVisibleContext: IContextKey<boolean>;
|
||||
private panelPositionContext: IContextKey<string>;
|
||||
private panelVisibleContext: IContextKey<boolean>;
|
||||
private panelMaximizedContext: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@@ -146,9 +148,13 @@ export class WorkbenchContextKeysHandler extends Disposable {
|
||||
// Sidebar
|
||||
this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService);
|
||||
|
||||
// Panel Position
|
||||
// Panel
|
||||
this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService);
|
||||
this.panelPositionContext.set(positionToString(this.layoutService.getPanelPosition()));
|
||||
this.panelVisibleContext = PanelVisibleContext.bindTo(this.contextKeyService);
|
||||
this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART));
|
||||
this.panelMaximizedContext = PanelMaximizedContext.bindTo(this.contextKeyService);
|
||||
this.panelMaximizedContext.set(this.layoutService.isPanelMaximized());
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
@@ -182,7 +188,11 @@ export class WorkbenchContextKeysHandler extends Disposable {
|
||||
this._register(this.viewletService.onDidViewletClose(() => this.updateSideBarContextKeys()));
|
||||
this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys()));
|
||||
|
||||
this._register(this.layoutService.onPartVisibilityChange(() => this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART))));
|
||||
this._register(this.layoutService.onPartVisibilityChange(() => {
|
||||
this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART));
|
||||
this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART));
|
||||
this.panelMaximizedContext.set(this.layoutService.isPanelMaximized());
|
||||
}));
|
||||
|
||||
this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.dirtyWorkingCopiesContext.set(workingCopy.isDirty() || this.workingCopyService.hasDirty)));
|
||||
}
|
||||
|
||||
@@ -737,7 +737,7 @@ export class CompositeDragAndDropObserver extends Disposable {
|
||||
if (callbacks.onDragEnd) {
|
||||
this._onDragEnd.event(e => {
|
||||
callbacks.onDragEnd!(e);
|
||||
});
|
||||
}, this, disposableStore);
|
||||
}
|
||||
return this._register(disposableStore);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { insert } from 'vs/base/common/arrays';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IEditorDescriptor {
|
||||
|
||||
getId(): string;
|
||||
getName(): string;
|
||||
|
||||
|
||||
@@ -100,6 +100,10 @@ export const DEFAULT_LABELS_CONTAINER: IResourceLabelsContainer = {
|
||||
};
|
||||
|
||||
export class ResourceLabels extends Disposable {
|
||||
|
||||
private _onDidChangeDecorations = this._register(new Emitter<void>());
|
||||
readonly onDidChangeDecorations = this._onDidChangeDecorations.event;
|
||||
|
||||
private widgets: ResourceLabelWidget[] = [];
|
||||
private labels: IResourceLabel[] = [];
|
||||
|
||||
@@ -148,7 +152,18 @@ export class ResourceLabels extends Disposable {
|
||||
}));
|
||||
|
||||
// notify when file decoration changes
|
||||
this._register(this.decorationsService.onDidChangeDecorations(e => this.widgets.forEach(widget => widget.notifyFileDecorationsChanges(e))));
|
||||
this._register(this.decorationsService.onDidChangeDecorations(e => {
|
||||
let notifyDidChangeDecorations = false;
|
||||
this.widgets.forEach(widget => {
|
||||
if (widget.notifyFileDecorationsChanges(e)) {
|
||||
notifyDidChangeDecorations = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (notifyDidChangeDecorations) {
|
||||
this._onDidChangeDecorations.fire();
|
||||
}
|
||||
}));
|
||||
|
||||
// notify when theme changes
|
||||
this._register(this.themeService.onDidColorThemeChange(() => this.widgets.forEach(widget => widget.notifyThemeChange())));
|
||||
@@ -311,19 +326,21 @@ class ResourceLabelWidget extends IconLabel {
|
||||
}
|
||||
}
|
||||
|
||||
notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): void {
|
||||
notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): boolean {
|
||||
if (!this.options) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const resource = toResource(this.label);
|
||||
if (!resource) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.options.fileDecorations && e.affectsResource(resource)) {
|
||||
this.render(false);
|
||||
return this.render(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
notifyExtensionsRegistered(): void {
|
||||
@@ -465,7 +482,7 @@ class ResourceLabelWidget extends IconLabel {
|
||||
this.setLabel('');
|
||||
}
|
||||
|
||||
private render(clearIconCache: boolean): void {
|
||||
private render(clearIconCache: boolean): boolean {
|
||||
if (this.isHidden) {
|
||||
if (!this.needsRedraw) {
|
||||
this.needsRedraw = clearIconCache ? Redraw.Full : Redraw.Basic;
|
||||
@@ -475,7 +492,7 @@ class ResourceLabelWidget extends IconLabel {
|
||||
this.needsRedraw = Redraw.Full;
|
||||
}
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.label) {
|
||||
@@ -492,7 +509,7 @@ class ResourceLabelWidget extends IconLabel {
|
||||
}
|
||||
|
||||
if (!this.label) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.renderDisposables.clear();
|
||||
@@ -558,6 +575,8 @@ class ResourceLabelWidget extends IconLabel {
|
||||
this.setLabel(label || '', this.label.description, iconLabelOptions);
|
||||
|
||||
this._onDidRender.fire();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
@@ -78,7 +78,9 @@ enum Storage {
|
||||
|
||||
GRID_LAYOUT = 'workbench.grid.layout',
|
||||
GRID_WIDTH = 'workbench.grid.width',
|
||||
GRID_HEIGHT = 'workbench.grid.height'
|
||||
GRID_HEIGHT = 'workbench.grid.height',
|
||||
|
||||
MENU_VISIBILITY = 'window.menuBarVisibility'
|
||||
}
|
||||
|
||||
enum Classes {
|
||||
@@ -321,9 +323,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) {
|
||||
// Propagate to grid
|
||||
this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART));
|
||||
|
||||
this.layout();
|
||||
}
|
||||
|
||||
// Move layout call to any time the menubar
|
||||
// is toggled to update consumers of offset
|
||||
// see issue #115267
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +627,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
return this._openedDefaultEditors;
|
||||
}
|
||||
|
||||
private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[] } | undefined {
|
||||
private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } | undefined {
|
||||
const defaultLayout = this.environmentService.options?.defaultLayout;
|
||||
if (defaultLayout?.editors?.length && this.storageService.isNew(StorageScope.WORKSPACE)) {
|
||||
this._openedDefaultEditors = true;
|
||||
@@ -652,7 +657,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
// Restore editors
|
||||
restorePromises.push((async () => {
|
||||
mark('willRestoreEditors');
|
||||
mark('code/willRestoreEditors');
|
||||
|
||||
// first ensure the editor part is restored
|
||||
await this.editorGroupService.whenRestored;
|
||||
@@ -669,17 +674,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
await this.editorService.openEditors(editors);
|
||||
}
|
||||
|
||||
mark('didRestoreEditors');
|
||||
mark('code/didRestoreEditors');
|
||||
})());
|
||||
|
||||
// Restore default views
|
||||
const restoreDefaultViewsPromise = (async () => {
|
||||
if (this.state.views.defaults?.length) {
|
||||
mark('willOpenDefaultViews');
|
||||
mark('code/willOpenDefaultViews');
|
||||
|
||||
let locationsRestored: { id: string; order: number }[] = [];
|
||||
let locationsRestored: { id: string; order: number; }[] = [];
|
||||
|
||||
const tryOpenView = (view: { id: string; order: number }): boolean => {
|
||||
const tryOpenView = (view: { id: string; order: number; }): boolean => {
|
||||
const location = this.viewDescriptorService.getViewLocationById(view.id);
|
||||
if (location !== null) {
|
||||
const container = this.viewDescriptorService.getViewContainerByViewId(view.id);
|
||||
@@ -733,7 +738,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
this.state.panel.panelToRestore = locationsRestored[ViewContainerLocation.Panel].id;
|
||||
}
|
||||
|
||||
mark('didOpenDefaultViews');
|
||||
mark('code/didOpenDefaultViews');
|
||||
}
|
||||
})();
|
||||
restorePromises.push(restoreDefaultViewsPromise);
|
||||
@@ -748,14 +753,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
return;
|
||||
}
|
||||
|
||||
mark('willRestoreViewlet');
|
||||
mark('code/willRestoreViewlet');
|
||||
|
||||
const viewlet = await this.viewletService.openViewlet(this.state.sideBar.viewletToRestore);
|
||||
if (!viewlet) {
|
||||
await this.viewletService.openViewlet(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); // fallback to default viewlet as needed
|
||||
}
|
||||
|
||||
mark('didRestoreViewlet');
|
||||
mark('code/didRestoreViewlet');
|
||||
})());
|
||||
|
||||
// Restore Panel
|
||||
@@ -768,14 +773,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
return;
|
||||
}
|
||||
|
||||
mark('willRestorePanel');
|
||||
mark('code/willRestorePanel');
|
||||
|
||||
const panel = await this.panelService.openPanel(this.state.panel.panelToRestore!);
|
||||
if (!panel) {
|
||||
await this.panelService.openPanel(Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId()); // fallback to default panel as needed
|
||||
}
|
||||
|
||||
mark('didRestorePanel');
|
||||
mark('code/didRestorePanel');
|
||||
})());
|
||||
|
||||
// Restore Zen Mode
|
||||
@@ -1128,7 +1133,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
[Parts.STATUSBAR_PART]: this.statusBarPartView
|
||||
};
|
||||
|
||||
const fromJSON = ({ type }: { type: Parts }) => viewMap[type];
|
||||
const fromJSON = ({ type }: { type: Parts; }) => viewMap[type];
|
||||
const workbenchGrid = SerializableGrid.deserialize(
|
||||
this.createGridDescriptor(),
|
||||
{ fromJSON },
|
||||
@@ -1410,7 +1415,29 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
// If panel part becomes visible, show last active panel or default panel
|
||||
else if (!hidden && !this.panelService.getActivePanel()) {
|
||||
const panelToOpen = this.panelService.getLastActivePanelId();
|
||||
let panelToOpen: string | undefined = this.panelService.getLastActivePanelId();
|
||||
const hasViews = (id: string): boolean => {
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerById(id);
|
||||
if (!viewContainer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
if (!viewContainerModel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return viewContainerModel.activeViewDescriptors.length >= 1;
|
||||
};
|
||||
|
||||
// verify that the panel we try to open has views before we default to it
|
||||
// otherwise fall back to any view that has views still refs #111463
|
||||
if (!panelToOpen || !hasViews(panelToOpen)) {
|
||||
panelToOpen = this.viewDescriptorService
|
||||
.getViewContainersByLocation(ViewContainerLocation.Panel)
|
||||
.find(viewContainer => hasViews(viewContainer.id))?.id;
|
||||
}
|
||||
|
||||
if (panelToOpen) {
|
||||
const focus = !skipLayout;
|
||||
this.panelService.openPanel(panelToOpen, focus);
|
||||
@@ -1529,6 +1556,24 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
return this.state.menuBar.visibility;
|
||||
}
|
||||
|
||||
toggleMenuBar(): void {
|
||||
let currentVisibilityValue = getMenuBarVisibility(this.configurationService);
|
||||
if (typeof currentVisibilityValue !== 'string') {
|
||||
currentVisibilityValue = 'default';
|
||||
}
|
||||
|
||||
let newVisibilityValue: string;
|
||||
if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') {
|
||||
newVisibilityValue = 'toggle';
|
||||
} else if (currentVisibilityValue === 'compact') {
|
||||
newVisibilityValue = 'hidden';
|
||||
} else {
|
||||
newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default';
|
||||
}
|
||||
|
||||
this.configurationService.updateValue(Storage.MENU_VISIBILITY, newVisibilityValue);
|
||||
}
|
||||
|
||||
getPanelPosition(): Position {
|
||||
return this.state.panel.position;
|
||||
}
|
||||
|
||||
99
lib/vscode/src/vs/workbench/browser/menuActions.ts
Normal file
99
lib/vscode/src/vs/workbench/browser/menuActions.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { MenuId, IMenuService, IMenu, SubmenuItemAction, IMenuActionOptions } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
|
||||
class MenuActions extends Disposable {
|
||||
|
||||
private readonly menu: IMenu;
|
||||
|
||||
private _primaryActions: IAction[] = [];
|
||||
get primaryActions() { return this._primaryActions; }
|
||||
|
||||
private _secondaryActions: IAction[] = [];
|
||||
get secondaryActions() { return this._secondaryActions; }
|
||||
|
||||
private readonly _onDidChange = this._register(new Emitter<void>());
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private disposables = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
menuId: MenuId,
|
||||
private readonly options: IMenuActionOptions | undefined,
|
||||
private readonly menuService: IMenuService,
|
||||
private readonly contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
this.menu = this._register(menuService.createMenu(menuId, contextKeyService));
|
||||
this._register(this.menu.onDidChange(() => this.updateActions()));
|
||||
this.updateActions();
|
||||
}
|
||||
|
||||
private updateActions(): void {
|
||||
this.disposables.clear();
|
||||
this._primaryActions = [];
|
||||
this._secondaryActions = [];
|
||||
this.disposables.add(createAndFillInActionBarActions(this.menu, this.options, { primary: this._primaryActions, secondary: this._secondaryActions }));
|
||||
this.disposables.add(this.updateSubmenus([...this._primaryActions, ...this._secondaryActions], {}));
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
private updateSubmenus(actions: readonly IAction[], submenus: { [id: number]: IMenu }): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
for (const action of actions) {
|
||||
if (action instanceof SubmenuItemAction && !submenus[action.item.submenu.id]) {
|
||||
const menu = submenus[action.item.submenu.id] = disposables.add(this.menuService.createMenu(action.item.submenu, this.contextKeyService));
|
||||
disposables.add(menu.onDidChange(() => this.updateActions()));
|
||||
disposables.add(this.updateSubmenus(action.actions, submenus));
|
||||
}
|
||||
}
|
||||
return disposables;
|
||||
}
|
||||
}
|
||||
|
||||
export class CompositeMenuActions extends Disposable {
|
||||
|
||||
private readonly menuActions: MenuActions;
|
||||
private readonly contextMenuActionsDisposable = this._register(new MutableDisposable());
|
||||
|
||||
private _onDidChange = this._register(new Emitter<void>());
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
constructor(
|
||||
menuId: MenuId,
|
||||
private readonly contextMenuId: MenuId | undefined,
|
||||
private readonly options: IMenuActionOptions | undefined,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
) {
|
||||
super();
|
||||
this.menuActions = this._register(new MenuActions(menuId, this.options, menuService, contextKeyService));
|
||||
this._register(this.menuActions.onDidChange(() => this._onDidChange.fire()));
|
||||
}
|
||||
|
||||
getPrimaryActions(): IAction[] {
|
||||
return this.menuActions.primaryActions;
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.menuActions.secondaryActions;
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
if (this.contextMenuId) {
|
||||
const menu = this.menuService.createMenu(this.contextMenuId, this.contextKeyService);
|
||||
this.contextMenuActionsDisposable.value = createAndFillInActionBarActions(menu, this.options, { primary: [], secondary: actions });
|
||||
menu.dispose();
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
|
||||
import { ViewPaneContainer } from './parts/views/viewPaneContainer';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { IAction, IActionViewItem, Separator } from 'vs/base/common/actions';
|
||||
import { ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
|
||||
export class PaneComposite extends Composite implements IPaneComposite {
|
||||
|
||||
private menuActions: ViewContainerMenuActions;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
protected readonly viewPaneContainer: ViewPaneContainer,
|
||||
@@ -35,8 +31,6 @@ export class PaneComposite extends Composite implements IPaneComposite {
|
||||
@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()));
|
||||
}
|
||||
|
||||
@@ -71,22 +65,36 @@ export class PaneComposite extends Composite implements IPaneComposite {
|
||||
|
||||
getContextMenuActions(): ReadonlyArray<IAction> {
|
||||
const result = [];
|
||||
result.push(...this.menuActions.getContextMenuActions());
|
||||
result.push(...this.viewPaneContainer.getContextMenuActions2());
|
||||
|
||||
if (result.length) {
|
||||
const otherActions = this.viewPaneContainer.getContextMenuActions();
|
||||
|
||||
if (otherActions.length) {
|
||||
result.push(new Separator());
|
||||
result.push(...otherActions);
|
||||
}
|
||||
|
||||
result.push(...this.viewPaneContainer.getContextMenuActions());
|
||||
return result;
|
||||
}
|
||||
|
||||
getActions(): ReadonlyArray<IAction> {
|
||||
return this.viewPaneContainer.getActions();
|
||||
const result = [];
|
||||
result.push(...this.viewPaneContainer.getActions2());
|
||||
result.push(...this.viewPaneContainer.getActions());
|
||||
return result;
|
||||
}
|
||||
|
||||
getSecondaryActions(): ReadonlyArray<IAction> {
|
||||
return this.viewPaneContainer.getSecondaryActions();
|
||||
const menuActions = this.viewPaneContainer.getSecondaryActions2();
|
||||
const viewPaneContainerActions = this.viewPaneContainer.getSecondaryActions();
|
||||
if (menuActions.length && viewPaneContainerActions.length) {
|
||||
return [
|
||||
...menuActions,
|
||||
new Separator(),
|
||||
...viewPaneContainerActions
|
||||
];
|
||||
}
|
||||
return menuActions.length ? menuActions : viewPaneContainerActions;
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
|
||||
@@ -6,23 +6,74 @@
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IPanel } from 'vs/workbench/common/panel';
|
||||
import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConstructorSignature0, BrandedService, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { PaneComposite } from 'vs/workbench/browser/panecomposite';
|
||||
import { IAction, Separator } from 'vs/base/common/actions';
|
||||
import { CompositeMenuActions } from 'vs/workbench/browser/menuActions';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export abstract class Panel extends PaneComposite implements IPanel { }
|
||||
export abstract class Panel extends PaneComposite implements IPanel {
|
||||
|
||||
private readonly panelActions: CompositeMenuActions;
|
||||
|
||||
constructor(id: string,
|
||||
viewPaneContainer: ViewPaneContainer,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
) {
|
||||
super(id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
|
||||
this.panelActions = this._register(this.instantiationService.createInstance(CompositeMenuActions, MenuId.PanelTitle, undefined, undefined));
|
||||
this._register(this.panelActions.onDidChange(() => this.updateTitleArea()));
|
||||
}
|
||||
|
||||
getActions(): ReadonlyArray<IAction> {
|
||||
return [...super.getActions(), ...this.panelActions.getPrimaryActions()];
|
||||
}
|
||||
|
||||
getSecondaryActions(): ReadonlyArray<IAction> {
|
||||
return this.mergeSecondaryActions(super.getSecondaryActions(), this.panelActions.getSecondaryActions());
|
||||
}
|
||||
|
||||
getContextMenuActions(): ReadonlyArray<IAction> {
|
||||
return this.mergeSecondaryActions(super.getContextMenuActions(), this.panelActions.getContextMenuActions());
|
||||
}
|
||||
|
||||
private mergeSecondaryActions(actions: ReadonlyArray<IAction>, panelActions: IAction[]): ReadonlyArray<IAction> {
|
||||
if (panelActions.length && actions.length) {
|
||||
return [
|
||||
...actions,
|
||||
new Separator(),
|
||||
...panelActions,
|
||||
];
|
||||
}
|
||||
return panelActions.length ? panelActions : actions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A panel descriptor is a leightweight descriptor of a panel in the workbench.
|
||||
*/
|
||||
export class PanelDescriptor extends CompositeDescriptor<Panel> {
|
||||
|
||||
static create<Services extends BrandedService[]>(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number, _commandId?: string): PanelDescriptor {
|
||||
return new PanelDescriptor(ctor as IConstructorSignature0<Panel>, id, name, cssClass, order, requestedIndex, _commandId);
|
||||
static create<Services extends BrandedService[]>(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number): PanelDescriptor {
|
||||
return new PanelDescriptor(ctor as IConstructorSignature0<Panel>, id, name, cssClass, order, requestedIndex);
|
||||
}
|
||||
|
||||
private constructor(ctor: IConstructorSignature0<Panel>, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number, _commandId?: string) {
|
||||
super(ctor, id, name, cssClass, order, requestedIndex, _commandId);
|
||||
private constructor(ctor: IConstructorSignature0<Panel>, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number) {
|
||||
super(ctor, id, name, cssClass, order, requestedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ class PartLayout {
|
||||
if (this.options && this.options.hasTitle) {
|
||||
titleSize = new Dimension(width, Math.min(height, PartLayout.TITLE_HEIGHT));
|
||||
} else {
|
||||
titleSize = new Dimension(0, 0);
|
||||
titleSize = Dimension.None;
|
||||
}
|
||||
|
||||
let contentWidth = width;
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/activityaction';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { EventType, addDisposableListener, EventHelper } from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
|
||||
import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
|
||||
import { Action, IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IMenuService, MenuId, IMenu, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { IActivity } from 'vs/workbench/common/activity';
|
||||
@@ -29,12 +29,12 @@ import { isMacintosh, isWeb } from 'vs/base/common/platform';
|
||||
import { getCurrentAuthenticationSessionInfo, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { AuthenticationSession } from 'vs/editor/common/modes';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { getTitleBarStyle } from 'vs/platform/windows/common/windows';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export class ViewContainerActivityAction extends ActivityAction {
|
||||
|
||||
@@ -97,10 +97,10 @@ export class ViewContainerActivityAction extends ActivityAction {
|
||||
|
||||
private logAction(action: string) {
|
||||
type ActivityBarActionClassification = {
|
||||
viewletId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
viewletId: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; };
|
||||
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; };
|
||||
};
|
||||
this.telemetryService.publicLog2<{ viewletId: String, action: String }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action });
|
||||
this.telemetryService.publicLog2<{ viewletId: String, action: String; }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
|
||||
constructor(
|
||||
private readonly menuId: MenuId,
|
||||
action: ActivityAction,
|
||||
private contextMenuActionsProvider: () => IAction[],
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IMenuService protected readonly menuService: IMenuService,
|
||||
@@ -126,30 +127,35 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
|
||||
// Context menus are triggered on mouse down so that an item can be picked
|
||||
// and executed with releasing the mouse over it
|
||||
|
||||
this._register(DOM.addDisposableListener(this.container, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
this._register(addDisposableListener(this.container, EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
EventHelper.stop(e, true);
|
||||
this.showContextMenu(e);
|
||||
}));
|
||||
|
||||
this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
this._register(addDisposableListener(this.container, EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
EventHelper.stop(e, true);
|
||||
this.showContextMenu();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(DOM.addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
this._register(addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => {
|
||||
EventHelper.stop(e, true);
|
||||
this.showContextMenu();
|
||||
}));
|
||||
}
|
||||
|
||||
protected async showContextMenu(e?: MouseEvent): Promise<void> {
|
||||
private async showContextMenu(e?: MouseEvent): Promise<void> {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService));
|
||||
const actions = await this.resolveActions(menu, disposables);
|
||||
let actions: IAction[];
|
||||
if (e?.button !== 2) {
|
||||
const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService));
|
||||
actions = await this.resolveMainMenuActions(menu, disposables);
|
||||
} else {
|
||||
actions = await this.resolveContextMenuActions(disposables);
|
||||
}
|
||||
|
||||
const isUsingCustomMenu = isWeb || (getTitleBarStyle(this.configurationService) !== 'native' && !isMacintosh); // see #40262
|
||||
const position = this.configurationService.getValue('workbench.sideBar.location');
|
||||
@@ -163,13 +169,17 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
|
||||
});
|
||||
}
|
||||
|
||||
protected async resolveActions(menu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
protected async resolveMainMenuActions(menu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
const actions: IAction[] = [];
|
||||
|
||||
disposables.add(createAndFillInActionBarActions(menu, undefined, { primary: [], secondary: actions }));
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
|
||||
return this.contextMenuActionsProvider();
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
@@ -179,6 +189,7 @@ export class HomeActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
constructor(
|
||||
private readonly goHomeHref: string,
|
||||
action: ActivityAction,
|
||||
contextMenuActionsProvider: () => IAction[],
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@@ -188,27 +199,32 @@ export class HomeActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IStorageService private readonly storageService: IStorageService
|
||||
) {
|
||||
super(MenuId.MenubarHomeMenu, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
super(MenuId.MenubarHomeMenu, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
}
|
||||
|
||||
protected async resolveActions(homeMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
protected async resolveMainMenuActions(homeMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
const actions = [];
|
||||
|
||||
// Go Home
|
||||
actions.push(disposables.add(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true, async () => window.location.href = this.goHomeHref)));
|
||||
actions.push(disposables.add(new Separator()));
|
||||
actions.push(toAction({ id: 'goHome', label: localize('goHome', "Go Home"), run: () => window.location.href = this.goHomeHref }));
|
||||
|
||||
// Contributed
|
||||
const contributedActions = await super.resolveActions(homeMenu, disposables);
|
||||
actions.push(...contributedActions);
|
||||
|
||||
// Hide
|
||||
if (contributedActions.length > 0) {
|
||||
const contributedActions = await super.resolveMainMenuActions(homeMenu, disposables);
|
||||
if (contributedActions.length) {
|
||||
actions.push(disposables.add(new Separator()));
|
||||
actions.push(...contributedActions);
|
||||
}
|
||||
actions.push(disposables.add(new Action('hide', nls.localize('hide', "Hide"), undefined, true, async () => {
|
||||
this.storageService.store(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, false, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
})));
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
|
||||
const actions = await super.resolveContextMenuActions(disposables);
|
||||
|
||||
actions.unshift(...[
|
||||
toAction({ id: 'hideHomeButton', label: localize('hideHomeButton', "Hide Home Button"), run: () => this.storageService.store(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, false, StorageScope.GLOBAL, StorageTarget.USER) }),
|
||||
new Separator()
|
||||
]);
|
||||
|
||||
return actions;
|
||||
}
|
||||
@@ -220,6 +236,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
contextMenuActionsProvider: () => IAction[],
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@@ -227,15 +244,15 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IStorageService private readonly storageService: IStorageService
|
||||
) {
|
||||
super(MenuId.AccountsContext, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
super(MenuId.AccountsContext, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
}
|
||||
|
||||
protected async resolveActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
await super.resolveActions(accountsMenu, disposables);
|
||||
protected async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
await super.resolveMainMenuActions(accountsMenu, disposables);
|
||||
|
||||
const otherCommands = accountsMenu.getActions();
|
||||
const providers = this.authenticationService.getProviderIds();
|
||||
@@ -243,7 +260,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
try {
|
||||
const sessions = await this.authenticationService.getSessions(providerId);
|
||||
|
||||
const groupedSessions: { [label: string]: AuthenticationSession[] } = {};
|
||||
const groupedSessions: { [label: string]: AuthenticationSession[]; } = {};
|
||||
sessions.forEach(session => {
|
||||
if (groupedSessions[session.account.label]) {
|
||||
groupedSessions[session.account.label].push(session);
|
||||
@@ -266,11 +283,11 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
|
||||
if (sessionInfo.sessions) {
|
||||
Object.keys(sessionInfo.sessions).forEach(accountName => {
|
||||
const manageExtensionsAction = disposables.add(new Action(`configureSessions${accountName}`, nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, () => {
|
||||
const manageExtensionsAction = disposables.add(new Action(`configureSessions${accountName}`, localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, () => {
|
||||
return this.authenticationService.manageTrustedExtensionsForAccount(sessionInfo.providerId, accountName);
|
||||
}));
|
||||
|
||||
const signOutAction = disposables.add(new Action('signOut', nls.localize('signOut', "Sign Out"), '', true, () => {
|
||||
const signOutAction = disposables.add(new Action('signOut', localize('signOut', "Sign Out"), '', true, () => {
|
||||
return this.authenticationService.signOutOfAccount(sessionInfo.providerId, accountName);
|
||||
}));
|
||||
|
||||
@@ -285,7 +302,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
menus.push(providerSubMenu);
|
||||
});
|
||||
} else {
|
||||
const providerUnavailableAction = disposables.add(new Action('providerUnavailable', nls.localize('authProviderUnavailable', '{0} is currently unavailable', providerDisplayName)));
|
||||
const providerUnavailableAction = disposables.add(new Action('providerUnavailable', localize('authProviderUnavailable', '{0} is currently unavailable', providerDisplayName)));
|
||||
menus.push(providerUnavailableAction);
|
||||
}
|
||||
});
|
||||
@@ -302,22 +319,26 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
}
|
||||
});
|
||||
|
||||
if (menus.length) {
|
||||
menus.push(disposables.add(new Separator()));
|
||||
}
|
||||
|
||||
menus.push(disposables.add(new Action('hide', nls.localize('hide', "Hide"), undefined, true, async () => {
|
||||
this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
})));
|
||||
|
||||
return menus;
|
||||
}
|
||||
|
||||
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
|
||||
const actions = await super.resolveContextMenuActions(disposables);
|
||||
|
||||
actions.unshift(...[
|
||||
toAction({ id: 'hideAccounts', label: localize('hideAccounts', "Hide Accounts"), run: () => this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER) }),
|
||||
new Separator()
|
||||
]);
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
contextMenuActionsProvider: () => IAction[],
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@@ -326,7 +347,7 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
|
||||
) {
|
||||
super(MenuId.GlobalActivity, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
super(MenuId.GlobalActivity, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +400,7 @@ registerAction2(
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.previousSideBarView',
|
||||
title: { value: nls.localize('previousSideBarView', "Previous Side Bar View"), original: 'Previous Side Bar View' },
|
||||
title: { value: localize('previousSideBarView', "Previous Side Bar View"), original: 'Previous Side Bar View' },
|
||||
category: CATEGORIES.View,
|
||||
f1: true
|
||||
}, -1);
|
||||
@@ -392,7 +413,7 @@ registerAction2(
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.nextSideBarView',
|
||||
title: { value: nls.localize('nextSideBarView', "Next Side Bar View"), original: 'Next Side Bar View' },
|
||||
title: { value: localize('nextSideBarView', "Next Side Bar View"), original: 'Next Side Bar View' },
|
||||
category: CATEGORIES.View,
|
||||
f1: true
|
||||
}, 1);
|
||||
@@ -400,7 +421,7 @@ registerAction2(
|
||||
}
|
||||
);
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const activityBarBackgroundColor = theme.getColor(ACTIVITY_BAR_BACKGROUND);
|
||||
if (activityBarBackgroundColor) {
|
||||
collector.addRule(`
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/activitybarpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIVITY_ID } from 'vs/workbench/common/activity';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
@@ -13,7 +13,7 @@ import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activ
|
||||
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions';
|
||||
import { ToggleActivityBarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions';
|
||||
import { IThemeService, IColorTheme, ThemeIcon } 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_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';
|
||||
@@ -23,33 +23,34 @@ import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { IViewDescriptorService, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views';
|
||||
import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views';
|
||||
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { assertIsDefined, isString } from 'vs/base/common/types';
|
||||
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { isNative, isWeb } from 'vs/base/common/platform';
|
||||
import { Before2D } from 'vs/workbench/browser/dnd';
|
||||
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
|
||||
import { Action, Separator } from 'vs/base/common/actions';
|
||||
import { IAction, Separator, toAction } from 'vs/base/common/actions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { StringSHA1 } from 'vs/base/common/hash';
|
||||
|
||||
interface IPlaceholderViewContainer {
|
||||
readonly id: string;
|
||||
readonly name?: string;
|
||||
readonly iconUrl?: UriComponents;
|
||||
readonly themeIcon?: ThemeIcon;
|
||||
readonly views?: { when?: string }[];
|
||||
readonly isBuiltin?: boolean;
|
||||
readonly views?: { when?: string; }[];
|
||||
}
|
||||
|
||||
interface IPinnedViewContainer {
|
||||
@@ -66,12 +67,10 @@ interface ICachedViewContainer {
|
||||
readonly pinned: boolean;
|
||||
readonly order?: number;
|
||||
visible: boolean;
|
||||
views?: { when?: string }[];
|
||||
isBuiltin?: boolean;
|
||||
views?: { when?: string; }[];
|
||||
}
|
||||
|
||||
const settingsViewBarIcon = registerIcon('settings-view-bar-icon', Codicon.settingsGear, nls.localize('settingsViewBarIcon', 'Settings icon in the view bar.'));
|
||||
const accountsViewBarIcon = registerIcon('accounts-view-bar-icon', Codicon.account, nls.localize('accountsViewBarIcon', 'Accounts icon in the view bar.'));
|
||||
|
||||
export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
@@ -81,6 +80,9 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
private static readonly ACTION_HEIGHT = 48;
|
||||
private static readonly ACCOUNTS_ACTION_INDEX = 0;
|
||||
|
||||
private static readonly GEAR_ICON = registerIcon('settings-view-bar-icon', Codicon.settingsGear, localize('settingsViewBarIcon', "Settings icon in the view bar."));
|
||||
private static readonly ACCOUNTS_ICON = registerIcon('accounts-view-bar-icon', Codicon.account, localize('accountsViewBarIcon', "Accounts icon in the view bar."));
|
||||
|
||||
//#region IView
|
||||
|
||||
readonly minimumWidth: number = 48;
|
||||
@@ -110,12 +112,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private readonly accountsActivity: ICompositeActivity[] = [];
|
||||
|
||||
private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction }>();
|
||||
private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction; }>();
|
||||
private readonly viewContainerDisposables = new Map<string, IDisposable>();
|
||||
|
||||
private readonly keyboardNavigationDisposables = this._register(new DisposableStore());
|
||||
|
||||
private readonly location = ViewContainerLocation.Sidebar;
|
||||
private hasExtensionsRegistered: boolean = false;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@@ -132,14 +135,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
|
||||
|
||||
for (const cachedViewContainer of this.cachedViewContainers) {
|
||||
if (
|
||||
environmentService.remoteAuthority || // In remote window, hide activity bar entries until registered
|
||||
this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer)
|
||||
) {
|
||||
cachedViewContainer.visible = false;
|
||||
}
|
||||
cachedViewContainer.visible = !this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer);
|
||||
}
|
||||
|
||||
this.compositeBar = this.createCompositeBar();
|
||||
|
||||
this.onDidRegisterViewContainers(this.getViewContainers());
|
||||
@@ -161,53 +158,66 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
icon: true,
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
preventLoopNavigation: true,
|
||||
openComposite: (compositeId: string) => this.viewsService.openViewContainer(compositeId, true),
|
||||
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
|
||||
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
|
||||
getOnCompositeClickAction: (compositeId: string) => new Action(compositeId, '', '', true, () => this.viewsService.isViewContainerVisible(compositeId) ? Promise.resolve(this.viewsService.closeViewContainer(compositeId)) : this.viewsService.openViewContainer(compositeId)),
|
||||
getContextMenuActions: () => {
|
||||
const actions = [];
|
||||
openComposite: compositeId => this.viewsService.openViewContainer(compositeId, true),
|
||||
getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,
|
||||
getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction,
|
||||
getOnCompositeClickAction: compositeId => toAction({ id: compositeId, label: '', run: async () => this.viewsService.isViewContainerVisible(compositeId) ? this.viewsService.closeViewContainer(compositeId) : this.viewsService.openViewContainer(compositeId) }),
|
||||
fillExtraContextMenuActions: actions => {
|
||||
|
||||
// Home
|
||||
const topActions: IAction[] = [];
|
||||
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; }
|
||||
));
|
||||
topActions.push({
|
||||
id: 'toggleHomeBarAction',
|
||||
label: localize('homeButton', "Home Button"),
|
||||
class: undefined,
|
||||
tooltip: localize('homeButton', "Home Button"),
|
||||
checked: this.homeBarVisibilityPreference,
|
||||
enabled: true,
|
||||
run: async () => this.homeBarVisibilityPreference = !this.homeBarVisibilityPreference,
|
||||
dispose: () => { }
|
||||
});
|
||||
}
|
||||
|
||||
// Menu
|
||||
const menuBarVisibility = getMenuBarVisibility(this.configurationService);
|
||||
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")));
|
||||
topActions.push({
|
||||
id: 'toggleMenuVisibility',
|
||||
label: localize('menu', "Menu"),
|
||||
class: undefined,
|
||||
tooltip: localize('menu', "Menu"),
|
||||
checked: menuBarVisibility === 'compact',
|
||||
enabled: true,
|
||||
run: async () => this.layoutService.toggleMenuBar(),
|
||||
dispose: () => { }
|
||||
});
|
||||
}
|
||||
|
||||
if (topActions.length) {
|
||||
actions.unshift(...topActions, new Separator());
|
||||
}
|
||||
|
||||
// Accounts
|
||||
actions.push(new Action(
|
||||
'toggleAccountsVisibility',
|
||||
this.accountsVisibilityPreference ? nls.localize('hideAccounts', "Hide Accounts") : nls.localize('showAccounts', "Show Accounts"),
|
||||
undefined,
|
||||
true,
|
||||
async () => { this.accountsVisibilityPreference = !this.accountsVisibilityPreference; }
|
||||
));
|
||||
actions.push(new Separator());
|
||||
actions.push({
|
||||
id: 'toggleAccountsVisibility',
|
||||
label: localize('accounts', "Accounts"),
|
||||
class: undefined,
|
||||
tooltip: localize('accounts', "Accounts"),
|
||||
checked: this.accountsVisibilityPreference,
|
||||
enabled: true,
|
||||
run: async () => this.accountsVisibilityPreference = !this.accountsVisibilityPreference,
|
||||
dispose: () => { }
|
||||
});
|
||||
|
||||
actions.push(new Separator());
|
||||
|
||||
// Toggle Sidebar
|
||||
actions.push(this.instantiationService.createInstance(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.layoutService)));
|
||||
|
||||
// Toggle Activity Bar
|
||||
actions.push(new Action(
|
||||
ToggleActivityBarVisibilityAction.ID,
|
||||
nls.localize('hideActivitBar', "Hide Activity Bar"),
|
||||
undefined,
|
||||
true,
|
||||
async () => { this.instantiationService.invokeFunction(accessor => new ToggleActivityBarVisibilityAction().run(accessor)); }
|
||||
));
|
||||
|
||||
return actions;
|
||||
actions.push(toAction({ id: ToggleActivityBarVisibilityAction.ID, label: localize('hideActivitBar', "Hide Activity Bar"), run: async () => this.instantiationService.invokeFunction(accessor => new ToggleActivityBarVisibilityAction().run(accessor)) }));
|
||||
},
|
||||
getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId),
|
||||
getDefaultCompositeId: () => this.viewDescriptorService.getDefaultViewContainer(this.location)!.id,
|
||||
@@ -223,24 +233,20 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}));
|
||||
}
|
||||
|
||||
private getContextMenuActionsForComposite(compositeId: string): Action[] {
|
||||
const actions = [];
|
||||
private getContextMenuActionsForComposite(compositeId: string): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!;
|
||||
const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!;
|
||||
if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) {
|
||||
actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => {
|
||||
this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation);
|
||||
}));
|
||||
actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation) }));
|
||||
} else {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
if (viewContainerModel.allViewDescriptors.length === 1) {
|
||||
const viewToReset = viewContainerModel.allViewDescriptors[0];
|
||||
const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!;
|
||||
if (defaultContainer !== viewContainer) {
|
||||
actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => {
|
||||
this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer);
|
||||
}));
|
||||
actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,7 +284,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}));
|
||||
}
|
||||
|
||||
private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>) {
|
||||
private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>) {
|
||||
removed.filter(({ location }) => location === ViewContainerLocation.Sidebar).forEach(({ container }) => this.onDidDeregisterViewContainer(container));
|
||||
this.onDidRegisterViewContainers(added.filter(({ location }) => location === ViewContainerLocation.Sidebar).map(({ container }) => container));
|
||||
}
|
||||
@@ -310,7 +316,22 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}
|
||||
|
||||
private onDidRegisterExtensions(): void {
|
||||
this.removeNotExistingComposites();
|
||||
this.hasExtensionsRegistered = true;
|
||||
|
||||
// show/hide/remove composites
|
||||
for (const { id } of this.cachedViewContainers) {
|
||||
const viewContainer = this.getViewContainer(id);
|
||||
if (viewContainer) {
|
||||
this.showOrHideViewContainer(viewContainer);
|
||||
} else {
|
||||
if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) {
|
||||
this.removeComposite(id);
|
||||
} else {
|
||||
this.hideComposite(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.saveCachedViewContainers();
|
||||
}
|
||||
|
||||
@@ -322,7 +343,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
this.compositeBar.addComposite(viewContainer);
|
||||
this.compositeBar.activateComposite(viewContainer.id);
|
||||
|
||||
if (viewContainer.hideIfEmpty) {
|
||||
if (this.shouldBeHidden(viewContainer)) {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
if (viewContainerModel.activeViewDescriptors.length === 0) {
|
||||
// Update the composite bar by hiding
|
||||
@@ -460,7 +481,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
// Home action bar
|
||||
const homeIndicator = this.environmentService.options?.homeIndicator;
|
||||
if (homeIndicator) {
|
||||
// TODO @sbatten remove the fake setting and associated code
|
||||
if (homeIndicator && this.configurationService.getValue<boolean>('window.showHomeIndicator')) {
|
||||
let codicon = iconRegistry.get(homeIndicator.icon);
|
||||
if (!codicon) {
|
||||
codicon = Codicon.code;
|
||||
@@ -556,14 +578,14 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private createHomeBar(href: string, icon: Codicon): void {
|
||||
this.homeBarContainer = document.createElement('div');
|
||||
this.homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home"));
|
||||
this.homeBarContainer.setAttribute('aria-label', localize('homeIndicator', "Home"));
|
||||
this.homeBarContainer.setAttribute('role', 'toolbar');
|
||||
this.homeBarContainer.classList.add('home-bar');
|
||||
|
||||
this.homeBar = this._register(new ActionBar(this.homeBarContainer, {
|
||||
actionViewItemProvider: action => this.instantiationService.createInstance(HomeActivityActionViewItem, href, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)),
|
||||
actionViewItemProvider: action => this.instantiationService.createInstance(HomeActivityActionViewItem, href, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme)),
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('home', "Home"),
|
||||
ariaLabel: localize('home', "Home"),
|
||||
animated: false,
|
||||
preventLoopNavigation: true,
|
||||
ignoreOrientationForPreviousAndNextKey: true
|
||||
@@ -575,7 +597,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
this.homeBar.push(this._register(new ActivityAction({
|
||||
id: 'workbench.actions.home',
|
||||
name: nls.localize('home', "Home"),
|
||||
name: localize('home', "Home"),
|
||||
cssClass: icon.classNames
|
||||
})));
|
||||
|
||||
@@ -587,17 +609,17 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
this.globalActivityActionBar = this._register(new ActionBar(container, {
|
||||
actionViewItemProvider: action => {
|
||||
if (action.id === 'workbench.actions.manage') {
|
||||
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme));
|
||||
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme));
|
||||
}
|
||||
|
||||
if (action.id === 'workbench.actions.accounts') {
|
||||
return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme));
|
||||
return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme));
|
||||
}
|
||||
|
||||
throw new Error(`No view item for action '${action.id}'`);
|
||||
},
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('manage', "Manage"),
|
||||
ariaLabel: localize('manage', "Manage"),
|
||||
animated: false,
|
||||
preventLoopNavigation: true,
|
||||
ignoreOrientationForPreviousAndNextKey: true
|
||||
@@ -605,15 +627,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
this.globalActivityAction = this._register(new ActivityAction({
|
||||
id: 'workbench.actions.manage',
|
||||
name: nls.localize('manage', "Manage"),
|
||||
cssClass: ThemeIcon.asClassName(settingsViewBarIcon)
|
||||
name: localize('manage', "Manage"),
|
||||
cssClass: ThemeIcon.asClassName(ActivitybarPart.GEAR_ICON)
|
||||
}));
|
||||
|
||||
if (this.accountsVisibilityPreference) {
|
||||
this.accountsActivityAction = this._register(new ActivityAction({
|
||||
id: 'workbench.actions.accounts',
|
||||
name: nls.localize('accounts', "Accounts"),
|
||||
cssClass: ThemeIcon.asClassName(accountsViewBarIcon)
|
||||
name: localize('accounts', "Accounts"),
|
||||
cssClass: ThemeIcon.asClassName(ActivitybarPart.ACCOUNTS_ICON)
|
||||
}));
|
||||
|
||||
this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX });
|
||||
@@ -630,7 +652,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
} else {
|
||||
this.accountsActivityAction = this._register(new ActivityAction({
|
||||
id: 'workbench.actions.accounts',
|
||||
name: nls.localize('accounts', "Accounts"),
|
||||
name: localize('accounts', "Accounts"),
|
||||
cssClass: Codicon.account.classNames
|
||||
}));
|
||||
this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX });
|
||||
@@ -640,7 +662,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
this.updateGlobalActivity(ACCOUNTS_ACTIVITY_ID);
|
||||
}
|
||||
|
||||
private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction } {
|
||||
private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction; } {
|
||||
let compositeActions = this.compositeActions.get(compositeId);
|
||||
if (!compositeActions) {
|
||||
const viewContainer = this.getViewContainer(compositeId);
|
||||
@@ -666,32 +688,27 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private onDidRegisterViewContainers(viewContainers: ReadonlyArray<ViewContainer>): void {
|
||||
for (const viewContainer of viewContainers) {
|
||||
this.compositeBar.addComposite(viewContainer);
|
||||
|
||||
// Pin it by default if it is new
|
||||
const cachedViewContainer = this.cachedViewContainers.filter(({ id }) => id === viewContainer.id)[0];
|
||||
const visibleViewContainer = this.viewsService.getVisibleViewContainer(this.location);
|
||||
const isActive = visibleViewContainer?.id === viewContainer.id;
|
||||
|
||||
if (isActive || !this.shouldBeHidden(viewContainer.id, cachedViewContainer)) {
|
||||
this.compositeBar.addComposite(viewContainer);
|
||||
|
||||
// Pin it by default if it is new
|
||||
if (!cachedViewContainer) {
|
||||
this.compositeBar.pin(viewContainer.id);
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
this.compositeBar.activateComposite(viewContainer.id);
|
||||
}
|
||||
if (!cachedViewContainer) {
|
||||
this.compositeBar.pin(viewContainer.id);
|
||||
}
|
||||
|
||||
// Active
|
||||
const visibleViewContainer = this.viewsService.getVisibleViewContainer(this.location);
|
||||
if (visibleViewContainer?.id === viewContainer.id) {
|
||||
this.compositeBar.activateComposite(viewContainer.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const viewContainer of viewContainers) {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
this.updateActivity(viewContainer, viewContainerModel);
|
||||
this.onDidChangeActiveViews(viewContainer, viewContainerModel);
|
||||
this.showOrHideViewContainer(viewContainer);
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewContainer, viewContainerModel)));
|
||||
disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewContainer, viewContainerModel)));
|
||||
disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.showOrHideViewContainer(viewContainer)));
|
||||
|
||||
this.viewContainerDisposables.set(viewContainer.id, disposables);
|
||||
}
|
||||
@@ -728,12 +745,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
let iconUrl: URI | undefined = undefined;
|
||||
if (URI.isUri(icon)) {
|
||||
iconUrl = icon;
|
||||
cssClass = `activity-${id.replace(/\./g, '-')}`;
|
||||
const cssUrl = asCSSUrl(icon);
|
||||
const hash = new StringSHA1();
|
||||
hash.update(cssUrl);
|
||||
cssClass = `activity-${id.replace(/\./g, '-')}-${hash.digest()}`;
|
||||
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`;
|
||||
createCSSRule(iconClass, `
|
||||
mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
|
||||
mask: ${cssUrl} no-repeat 50% 50%;
|
||||
mask-size: 24px;
|
||||
-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
|
||||
-webkit-mask: ${cssUrl} no-repeat 50% 50%;
|
||||
-webkit-mask-size: 24px;
|
||||
`);
|
||||
} else if (ThemeIcon.isThemeIcon(icon)) {
|
||||
@@ -743,36 +763,43 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
return { id, name, cssClass, iconUrl, keybindingId };
|
||||
}
|
||||
|
||||
private onDidChangeActiveViews(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
|
||||
if (viewContainerModel.activeViewDescriptors.length) {
|
||||
this.compositeBar.addComposite(viewContainer);
|
||||
} else if (viewContainer.hideIfEmpty) {
|
||||
private showOrHideViewContainer(viewContainer: ViewContainer): void {
|
||||
if (this.shouldBeHidden(viewContainer)) {
|
||||
this.hideComposite(viewContainer.id);
|
||||
} else {
|
||||
this.compositeBar.addComposite(viewContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private shouldBeHidden(viewContainerId: string, cachedViewContainer?: ICachedViewContainer): boolean {
|
||||
const viewContainer = this.getViewContainer(viewContainerId);
|
||||
if (!viewContainer || !viewContainer.hideIfEmpty) {
|
||||
return false;
|
||||
}
|
||||
private shouldBeHidden(viewContainerOrId: string | ViewContainer, cachedViewContainer?: ICachedViewContainer): boolean {
|
||||
const viewContainer = isString(viewContainerOrId) ? this.getViewContainer(viewContainerOrId) : viewContainerOrId;
|
||||
const viewContainerId = isString(viewContainerOrId) ? viewContainerOrId : viewContainerOrId.id;
|
||||
|
||||
return cachedViewContainer?.views && cachedViewContainer.views.length
|
||||
? cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)))
|
||||
: viewContainerId === TEST_VIEW_CONTAINER_ID /* Hide Test view container for the first time or it had no views registered before */;
|
||||
}
|
||||
|
||||
private removeNotExistingComposites(): void {
|
||||
const viewContainers = this.getViewContainers();
|
||||
for (const { id } of this.cachedViewContainers) {
|
||||
if (viewContainers.every(viewContainer => viewContainer.id !== id)) {
|
||||
if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) {
|
||||
this.removeComposite(id);
|
||||
} else {
|
||||
this.hideComposite(id);
|
||||
if (viewContainer) {
|
||||
if (viewContainer.hideIfEmpty) {
|
||||
if (this.viewDescriptorService.getViewContainerModel(viewContainer).activeViewDescriptors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check cache only if extensions are not yet registered and current window is not native (desktop) remote connection window
|
||||
if (!this.hasExtensionsRegistered && !(this.environmentService.remoteAuthority && isNative)) {
|
||||
cachedViewContainer = cachedViewContainer || this.cachedViewContainers.find(({ id }) => id === viewContainerId);
|
||||
|
||||
// Show builtin ViewContainer if not registered yet
|
||||
if (!viewContainer && cachedViewContainer?.isBuiltin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cachedViewContainer?.views?.length) {
|
||||
return cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private hideComposite(compositeId: string): void {
|
||||
@@ -864,7 +891,6 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private getViewContainer(id: string): ViewContainer | undefined {
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerById(id);
|
||||
|
||||
return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined;
|
||||
}
|
||||
|
||||
@@ -918,22 +944,22 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
const viewContainer = this.getViewContainer(compositeItem.id);
|
||||
if (viewContainer) {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
const views: { when: string | undefined }[] = [];
|
||||
const views: { when: string | undefined; }[] = [];
|
||||
for (const { when } of viewContainerModel.allViewDescriptors) {
|
||||
views.push({ when: when ? when.serialize() : undefined });
|
||||
}
|
||||
const cacheIcon = URI.isUri(viewContainerModel.icon) ? viewContainerModel.icon.scheme === Schemas.file : true;
|
||||
state.push({
|
||||
id: compositeItem.id,
|
||||
name: viewContainerModel.title,
|
||||
icon: cacheIcon ? viewContainerModel.icon : undefined,
|
||||
icon: URI.isUri(viewContainerModel.icon) && this.environmentService.remoteAuthority && isNative ? undefined : viewContainerModel.icon, /* Donot cache uri icons in desktop with remote connection */
|
||||
views,
|
||||
pinned: compositeItem.pinned,
|
||||
order: compositeItem.order,
|
||||
visible: compositeItem.visible
|
||||
visible: compositeItem.visible,
|
||||
isBuiltin: !viewContainer.extensionId
|
||||
});
|
||||
} else {
|
||||
state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false });
|
||||
state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false, isBuiltin: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -948,9 +974,10 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
const cachedViewContainer = this._cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0];
|
||||
if (cachedViewContainer) {
|
||||
cachedViewContainer.name = placeholderViewContainer.name;
|
||||
cachedViewContainer.icon = placeholderViewContainer.themeIcon ? ThemeIcon.revive(placeholderViewContainer.themeIcon) :
|
||||
cachedViewContainer.icon = placeholderViewContainer.themeIcon ? placeholderViewContainer.themeIcon :
|
||||
placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;
|
||||
cachedViewContainer.views = placeholderViewContainer.views;
|
||||
cachedViewContainer.isBuiltin = placeholderViewContainer.isBuiltin;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -966,11 +993,12 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
order
|
||||
})));
|
||||
|
||||
this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views }) => (<IPlaceholderViewContainer>{
|
||||
this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views, isBuiltin }) => (<IPlaceholderViewContainer>{
|
||||
id,
|
||||
iconUrl: URI.isUri(icon) ? icon : undefined,
|
||||
themeIcon: ThemeIcon.isThemeIcon(icon) ? icon : undefined,
|
||||
name,
|
||||
isBuiltin,
|
||||
views
|
||||
})));
|
||||
}
|
||||
@@ -1067,7 +1095,7 @@ class FocusActivityBarAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.focusActivityBar',
|
||||
title: { value: nls.localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' },
|
||||
title: { value: localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' },
|
||||
category: CATEGORIES.View,
|
||||
f1: true
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action, IAction, Separator } from 'vs/base/common/actions';
|
||||
import { IAction, toAction } from 'vs/base/common/actions';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -149,10 +149,10 @@ export interface ICompositeBarOptions {
|
||||
readonly preventLoopNavigation?: boolean;
|
||||
|
||||
getActivityAction: (compositeId: string) => ActivityAction;
|
||||
getCompositePinnedAction: (compositeId: string) => Action;
|
||||
getOnCompositeClickAction: (compositeId: string) => Action;
|
||||
getContextMenuActions: () => Action[];
|
||||
getContextMenuActionsForComposite: (compositeId: string) => Action[];
|
||||
getCompositePinnedAction: (compositeId: string) => IAction;
|
||||
getOnCompositeClickAction: (compositeId: string) => IAction;
|
||||
fillExtraContextMenuActions: (actions: IAction[]) => void;
|
||||
getContextMenuActionsForComposite: (compositeId: string) => IAction[];
|
||||
openComposite: (compositeId: string) => Promise<IComposite | null>;
|
||||
getDefaultCompositeId: () => string;
|
||||
hidePart: () => void;
|
||||
@@ -208,15 +208,15 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
create(parent: HTMLElement): HTMLElement {
|
||||
const actionBarDiv = parent.appendChild($('.composite-bar'));
|
||||
this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, {
|
||||
actionViewItemProvider: (action: IAction) => {
|
||||
actionViewItemProvider: action => {
|
||||
if (action instanceof CompositeOverflowActivityAction) {
|
||||
return this.compositeOverflowActionViewItem;
|
||||
}
|
||||
const item = this.model.findItem(action.id);
|
||||
return item && this.instantiationService.createInstance(
|
||||
CompositeActionViewItem, action as ActivityAction, item.pinnedAction,
|
||||
(compositeId: string) => this.options.getContextMenuActionsForComposite(compositeId),
|
||||
() => this.getContextMenuActions() as Action[],
|
||||
compositeId => this.options.getContextMenuActionsForComposite(compositeId),
|
||||
() => this.getContextMenuActions(),
|
||||
this.options.colors,
|
||||
this.options.icon,
|
||||
this.options.dndHandler,
|
||||
@@ -319,7 +319,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
addComposite({ id, name, order, requestedIndex }: { id: string; name: string, order?: number, requestedIndex?: number }): void {
|
||||
addComposite({ id, name, order, requestedIndex }: { id: string; name: string, order?: number, requestedIndex?: number; }): void {
|
||||
// Add to the model
|
||||
if (this.model.add(id, name, order, requestedIndex)) {
|
||||
this.computeSizes([this.model.findItem(id)]);
|
||||
@@ -596,7 +596,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
this.compositeOverflowAction,
|
||||
() => this.getOverflowingComposites(),
|
||||
() => this.model.activeItem ? this.model.activeItem.id : undefined,
|
||||
(compositeId: string) => {
|
||||
compositeId => {
|
||||
const item = this.model.findItem(compositeId);
|
||||
return item?.activity[0]?.badge;
|
||||
},
|
||||
@@ -610,7 +610,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
private getOverflowingComposites(): { id: string, name?: string }[] {
|
||||
private getOverflowingComposites(): { id: string, name?: string; }[] {
|
||||
let overflowingIds = this.model.visibleItems.filter(item => item.pinned).map(item => item.id);
|
||||
|
||||
// Show the active composite even if it is not pinned
|
||||
@@ -631,9 +631,9 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
});
|
||||
}
|
||||
|
||||
private getContextMenuActions(): IAction[] {
|
||||
getContextMenuActions(): IAction[] {
|
||||
const actions: IAction[] = this.model.visibleItems
|
||||
.map(({ id, name, activityAction }) => (<IAction>{
|
||||
.map(({ id, name, activityAction }) => (toAction({
|
||||
id,
|
||||
label: this.getAction(id).label || name || id,
|
||||
checked: this.isPinned(id),
|
||||
@@ -645,19 +645,17 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
this.pin(id, true);
|
||||
}
|
||||
}
|
||||
}));
|
||||
const otherActions = this.options.getContextMenuActions();
|
||||
if (otherActions.length) {
|
||||
actions.push(new Separator());
|
||||
actions.push(...otherActions);
|
||||
}
|
||||
})));
|
||||
|
||||
this.options.fillExtraContextMenuActions(actions);
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
interface ICompositeBarModelItem extends ICompositeBarItem {
|
||||
activityAction: ActivityAction;
|
||||
pinnedAction: Action;
|
||||
pinnedAction: IAction;
|
||||
activity: ICompositeActivity[];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action, Separator } from 'vs/base/common/actions';
|
||||
import { Action, IAction, Separator } from 'vs/base/common/actions';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -374,14 +374,14 @@ export class CompositeOverflowActivityAction extends ActivityAction {
|
||||
}
|
||||
|
||||
export class CompositeOverflowActivityActionViewItem extends ActivityActionViewItem {
|
||||
private actions: Action[] = [];
|
||||
private actions: IAction[] = [];
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
private getOverflowingComposites: () => { id: string, name?: string }[],
|
||||
private getActiveCompositeId: () => string | undefined,
|
||||
private getBadge: (compositeId: string) => IBadge,
|
||||
private getCompositeOpenAction: (compositeId: string) => Action,
|
||||
private getCompositeOpenAction: (compositeId: string) => IAction,
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@@ -404,7 +404,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI
|
||||
});
|
||||
}
|
||||
|
||||
private getActions(): Action[] {
|
||||
private getActions(): IAction[] {
|
||||
return this.getOverflowingComposites().map(composite => {
|
||||
const action = this.getCompositeOpenAction(composite.id);
|
||||
action.checked = this.getActiveCompositeId() === action.id;
|
||||
@@ -457,9 +457,9 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
|
||||
constructor(
|
||||
private compositeActivityAction: ActivityAction,
|
||||
private toggleCompositePinnedAction: Action,
|
||||
private compositeContextMenuActionsProvider: (compositeId: string) => ReadonlyArray<Action>,
|
||||
private contextMenuActionsProvider: () => ReadonlyArray<Action>,
|
||||
private toggleCompositePinnedAction: IAction,
|
||||
private compositeContextMenuActionsProvider: (compositeId: string) => IAction[],
|
||||
private contextMenuActionsProvider: () => IAction[],
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
icon: boolean,
|
||||
private dndHandler: ICompositeDragAndDrop,
|
||||
@@ -500,9 +500,14 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
return this.compositeActivity;
|
||||
}
|
||||
|
||||
private getActivtyName(): string {
|
||||
private getActivtyName(skipKeybinding = false): string {
|
||||
let name = this.compositeActivityAction.activity.name;
|
||||
if (skipKeybinding) {
|
||||
return name;
|
||||
}
|
||||
|
||||
const keybinding = this.compositeActivityAction.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeActivityAction.activity.keybindingId) : null;
|
||||
return keybinding ? nls.localize('titleKeybinding', "{0} ({1})", this.compositeActivityAction.activity.name, keybinding.getLabel()) : this.compositeActivityAction.activity.name;
|
||||
return keybinding ? nls.localize('titleKeybinding', "{0} ({1})", name, keybinding.getLabel()) : name;
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
@@ -601,7 +606,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
}
|
||||
|
||||
private showContextMenu(container: HTMLElement): void {
|
||||
const actions: Action[] = [this.toggleCompositePinnedAction];
|
||||
const actions: IAction[] = [this.toggleCompositePinnedAction];
|
||||
|
||||
const compositeContextMenuActions = this.compositeContextMenuActionsProvider(this.activity.id);
|
||||
if (compositeContextMenuActions.length) {
|
||||
@@ -615,10 +620,10 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
|
||||
const isPinned = this.compositeBar.isPinned(this.activity.id);
|
||||
if (isPinned) {
|
||||
this.toggleCompositePinnedAction.label = nls.localize('hide', "Hide");
|
||||
this.toggleCompositePinnedAction.label = nls.localize('hide', "Hide '{0}'", this.getActivtyName(true));
|
||||
this.toggleCompositePinnedAction.checked = false;
|
||||
} else {
|
||||
this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep");
|
||||
this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep '{0}'", this.getActivtyName(true));
|
||||
}
|
||||
|
||||
const otherActions = this.contextMenuActionsProvider();
|
||||
|
||||
@@ -327,10 +327,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
const primaryActions: IAction[] = composite?.getActions().slice(0) || [];
|
||||
const secondaryActions: IAction[] = composite?.getSecondaryActions().slice(0) || [];
|
||||
|
||||
// From Part
|
||||
primaryActions.push(...this.getActions());
|
||||
secondaryActions.push(...this.getSecondaryActions());
|
||||
|
||||
// Update context
|
||||
const toolBar = assertIsDefined(this.toolBar);
|
||||
toolBar.context = this.actionsContextProvider();
|
||||
@@ -471,14 +467,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return compositeItem ? compositeItem.progress : undefined;
|
||||
}
|
||||
|
||||
protected getActions(): ReadonlyArray<IAction> {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected getSecondaryActions(): ReadonlyArray<IAction> {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {
|
||||
return AnchorAlignment.RIGHT;
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution {
|
||||
private impl: IDialogHandler;
|
||||
private readonly model: IDialogsModel;
|
||||
private readonly impl: IDialogHandler;
|
||||
|
||||
private model: IDialogsModel;
|
||||
private currentDialog: IDialogViewItem | undefined;
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -6,19 +6,13 @@
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
|
||||
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
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 { combinedDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
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';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { SymbolKinds } from 'vs/editor/common/modes';
|
||||
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
@@ -28,35 +22,92 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
|
||||
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
|
||||
import { IListService, WorkbenchDataTree, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ColorIdentifier, ColorFunction } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { ResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { BreadcrumbsPicker, createBreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
|
||||
import { BreadcrumbsModel, FileElement, OutlineElement2 } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { BreadcrumbsFilePicker, BreadcrumbsOutlinePicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
|
||||
import { IEditorPartOptions, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||
import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { onDidChangeZoomLevel } from 'vs/base/browser/browser';
|
||||
import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IOutline } from 'vs/workbench/services/outline/browser/outline';
|
||||
|
||||
class Item extends BreadcrumbsItem {
|
||||
class OutlineItem extends BreadcrumbsItem {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
readonly element: BreadcrumbElement,
|
||||
readonly model: BreadcrumbsModel,
|
||||
readonly element: OutlineElement2,
|
||||
readonly options: IBreadcrumbsControlOptions
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
equals(other: BreadcrumbsItem): boolean {
|
||||
if (!(other instanceof OutlineItem)) {
|
||||
return false;
|
||||
}
|
||||
return this.element === other.element &&
|
||||
this.options.showFileIcons === other.options.showFileIcons &&
|
||||
this.options.showSymbolIcons === other.options.showSymbolIcons;
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
const { element, outline } = this.element;
|
||||
|
||||
if (element === outline) {
|
||||
const element = dom.$('span', undefined, '…');
|
||||
container.appendChild(element);
|
||||
return;
|
||||
}
|
||||
|
||||
const templateId = outline.config.delegate.getTemplateId(element);
|
||||
const renderer = outline.config.renderers.find(renderer => renderer.templateId === templateId);
|
||||
if (!renderer) {
|
||||
container.innerText = '<<NO RENDERER>>';
|
||||
return;
|
||||
}
|
||||
|
||||
const template = renderer.renderTemplate(container);
|
||||
renderer.renderElement(<ITreeNode<any, any>>{
|
||||
element,
|
||||
children: [],
|
||||
depth: 0,
|
||||
visibleChildrenCount: 0,
|
||||
visibleChildIndex: 0,
|
||||
collapsible: false,
|
||||
collapsed: false,
|
||||
visible: true,
|
||||
filterData: undefined
|
||||
}, 0, template, undefined);
|
||||
|
||||
this._disposables.add(toDisposable(() => { renderer.disposeTemplate(template); }));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FileItem extends BreadcrumbsItem {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
readonly model: BreadcrumbsModel,
|
||||
readonly element: FileElement,
|
||||
readonly options: IBreadcrumbsControlOptions,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
) {
|
||||
@@ -68,59 +119,26 @@ class Item extends BreadcrumbsItem {
|
||||
}
|
||||
|
||||
equals(other: BreadcrumbsItem): boolean {
|
||||
if (!(other instanceof Item)) {
|
||||
if (!(other instanceof FileItem)) {
|
||||
return false;
|
||||
}
|
||||
if (this.element instanceof FileElement && other.element instanceof FileElement) {
|
||||
return (extUri.isEqual(this.element.uri, other.element.uri) &&
|
||||
this.options.showFileIcons === other.options.showFileIcons &&
|
||||
this.options.showSymbolIcons === other.options.showSymbolIcons);
|
||||
}
|
||||
if (this.element instanceof TreeElement && other.element instanceof TreeElement) {
|
||||
return this.element.id === other.element.id;
|
||||
}
|
||||
return false;
|
||||
return (extUri.isEqual(this.element.uri, other.element.uri) &&
|
||||
this.options.showFileIcons === other.options.showFileIcons &&
|
||||
this.options.showSymbolIcons === other.options.showSymbolIcons);
|
||||
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
if (this.element instanceof FileElement) {
|
||||
// file/folder
|
||||
let label = this._instantiationService.createInstance(ResourceLabel, container, {});
|
||||
label.element.setFile(this.element.uri, {
|
||||
hidePath: true,
|
||||
hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons,
|
||||
fileKind: this.element.kind,
|
||||
fileDecorations: { colors: this.options.showDecorationColors, badges: false },
|
||||
});
|
||||
container.classList.add(FileKind[this.element.kind].toLowerCase());
|
||||
this._disposables.add(label);
|
||||
|
||||
} else if (this.element instanceof OutlineModel) {
|
||||
// has outline element but not in one
|
||||
let label = document.createElement('div');
|
||||
label.innerText = '\u2026';
|
||||
label.className = 'hint-more';
|
||||
container.appendChild(label);
|
||||
|
||||
} else if (this.element instanceof OutlineGroup) {
|
||||
// provider
|
||||
let label = new IconLabel(container);
|
||||
label.setLabel(this.element.label);
|
||||
this._disposables.add(label);
|
||||
|
||||
} else if (this.element instanceof OutlineElement) {
|
||||
// symbol
|
||||
if (this.options.showSymbolIcons) {
|
||||
let icon = document.createElement('div');
|
||||
icon.className = SymbolKinds.toCssClassName(this.element.symbol.kind);
|
||||
container.appendChild(icon);
|
||||
container.classList.add('shows-symbol-icon');
|
||||
}
|
||||
let label = new IconLabel(container);
|
||||
let title = this.element.symbol.name.replace(/\r|\n|\r\n/g, '\u23CE');
|
||||
label.setLabel(title);
|
||||
this._disposables.add(label);
|
||||
}
|
||||
// file/folder
|
||||
let label = this._instantiationService.createInstance(ResourceLabel, container, {});
|
||||
label.element.setFile(this.element.uri, {
|
||||
hidePath: true,
|
||||
hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons,
|
||||
fileKind: this.element.kind,
|
||||
fileDecorations: { colors: this.options.showDecorationColors, badges: false },
|
||||
});
|
||||
container.classList.add(FileKind[this.element.kind].toLowerCase());
|
||||
this._disposables.add(label);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,26 +188,23 @@ export class BreadcrumbsControl {
|
||||
private readonly _editorGroup: IEditorGroupView,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IContextViewService private readonly _contextViewService: IContextViewService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@IQuickInputService private readonly _quickInputService: IQuickInputService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IBreadcrumbsService breadcrumbsService: IBreadcrumbsService,
|
||||
) {
|
||||
this.domNode = document.createElement('div');
|
||||
this.domNode.classList.add('breadcrumbs-control');
|
||||
dom.append(container, this.domNode);
|
||||
|
||||
this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService);
|
||||
this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(_configurationService);
|
||||
this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(_configurationService);
|
||||
this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(configurationService);
|
||||
this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(configurationService);
|
||||
this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(configurationService);
|
||||
|
||||
const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default';
|
||||
this._widget = new BreadcrumbsWidget(this.domNode, BreadcrumbsControl.SCROLLBAR_SIZES[sizing]);
|
||||
@@ -256,14 +271,11 @@ export class BreadcrumbsControl {
|
||||
this._ckBreadcrumbsVisible.set(true);
|
||||
this._ckBreadcrumbsPossible.set(true);
|
||||
|
||||
const editor = this._getActiveCodeEditor();
|
||||
const model = new EditorBreadcrumbsModel(
|
||||
const model = this._instantiationService.createInstance(BreadcrumbsModel,
|
||||
fileInfoUri ?? uri,
|
||||
uri, editor,
|
||||
this._configurationService,
|
||||
this._textResourceConfigurationService,
|
||||
this._workspaceService
|
||||
this._editorGroup.activeEditorPane
|
||||
);
|
||||
|
||||
this.domNode.classList.toggle('relative-path', model.isRelative());
|
||||
this.domNode.classList.toggle('backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\');
|
||||
|
||||
@@ -274,7 +286,7 @@ export class BreadcrumbsControl {
|
||||
showFileIcons: this._options.showFileIcons && showIcons,
|
||||
showSymbolIcons: this._options.showSymbolIcons && showIcons
|
||||
};
|
||||
const items = model.getElements().map(element => new Item(element, options, this._instantiationService));
|
||||
const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._instantiationService) : new OutlineItem(model, element, options));
|
||||
this._widget.setItems(items);
|
||||
this._widget.reveal(items[items.length - 1]);
|
||||
};
|
||||
@@ -298,7 +310,7 @@ export class BreadcrumbsControl {
|
||||
this._breadcrumbsDisposables.add({
|
||||
dispose: () => {
|
||||
if (this._breadcrumbsPickerShowing) {
|
||||
this._contextViewService.hideContextView(this);
|
||||
this._contextViewService.hideContextView({ source: this });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -306,20 +318,6 @@ export class BreadcrumbsControl {
|
||||
return true;
|
||||
}
|
||||
|
||||
private _getActiveCodeEditor(): ICodeEditor | undefined {
|
||||
if (!this._editorGroup.activeEditorPane) {
|
||||
return undefined;
|
||||
}
|
||||
let control = this._editorGroup.activeEditorPane.getControl();
|
||||
let editor: ICodeEditor | undefined;
|
||||
if (isCodeEditor(control)) {
|
||||
editor = control as ICodeEditor;
|
||||
} else if (isDiffEditor(control)) {
|
||||
editor = control.getModifiedEditor();
|
||||
}
|
||||
return editor;
|
||||
}
|
||||
|
||||
private _onFocusEvent(event: IBreadcrumbsItemEvent): void {
|
||||
if (event.item && this._breadcrumbsPickerShowing) {
|
||||
this._breadcrumbsPickerIgnoreOnceItem = undefined;
|
||||
@@ -339,13 +337,12 @@ export class BreadcrumbsControl {
|
||||
return;
|
||||
}
|
||||
|
||||
const { element } = event.item as Item;
|
||||
const { element } = event.item as FileItem | OutlineItem;
|
||||
this._editorGroup.focus();
|
||||
|
||||
type BreadcrumbSelectClassification = {
|
||||
type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
this._telemetryService.publicLog2<{ type: string }, BreadcrumbSelectClassification>('breadcrumbs/select', { type: element instanceof TreeElement ? 'symbol' : 'file' });
|
||||
type BreadcrumbSelect = { type: string };
|
||||
type BreadcrumbSelectClassification = { type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; };
|
||||
this._telemetryService.publicLog2<BreadcrumbSelect, BreadcrumbSelectClassification>('breadcrumbs/select', { type: event.item instanceof OutlineItem ? 'symbol' : 'file' });
|
||||
|
||||
const group = this._getEditorGroup(event.payload);
|
||||
if (group !== undefined) {
|
||||
@@ -360,64 +357,31 @@ export class BreadcrumbsControl {
|
||||
// using quick pick
|
||||
this._widget.setFocused(undefined);
|
||||
this._widget.setSelection(undefined);
|
||||
this._quickInputService.quickAccess.show(element instanceof TreeElement ? '@' : '');
|
||||
this._quickInputService.quickAccess.show(element instanceof OutlineElement2 ? '@' : '');
|
||||
return;
|
||||
}
|
||||
|
||||
// show picker
|
||||
let picker: BreadcrumbsPicker;
|
||||
let pickerAnchor: { x: number; y: number };
|
||||
let editor = this._getActiveCodeEditor();
|
||||
let editorDecorations: string[] = [];
|
||||
let editorViewState: ICodeEditorViewState | undefined;
|
||||
|
||||
interface IHideData { didPick?: boolean, source?: BreadcrumbsControl }
|
||||
|
||||
this._contextViewService.showContextView({
|
||||
render: (parent: HTMLElement) => {
|
||||
picker = createBreadcrumbsPicker(this._instantiationService, parent, element);
|
||||
let selectListener = picker.onDidPickElement(data => {
|
||||
if (data.target) {
|
||||
editorViewState = undefined;
|
||||
}
|
||||
this._contextViewService.hideContextView(this);
|
||||
if (event.item instanceof FileItem) {
|
||||
picker = this._instantiationService.createInstance(BreadcrumbsFilePicker, parent, event.item.model.resource);
|
||||
} else if (event.item instanceof OutlineItem) {
|
||||
picker = this._instantiationService.createInstance(BreadcrumbsOutlinePicker, parent, event.item.model.resource);
|
||||
}
|
||||
|
||||
const group = (picker.useAltAsMultipleSelectionModifier && (data.browserEvent as MouseEvent).metaKey) || (!picker.useAltAsMultipleSelectionModifier && (data.browserEvent as MouseEvent).altKey)
|
||||
? SIDE_GROUP
|
||||
: ACTIVE_GROUP;
|
||||
|
||||
this._revealInEditor(event, data.target, group, (data.browserEvent as MouseEvent).button === 1);
|
||||
/* __GDPR__
|
||||
"breadcrumbs/open" : {
|
||||
"type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('breadcrumbs/open', { type: !data ? 'nothing' : data.target instanceof TreeElement ? 'symbol' : 'file' });
|
||||
});
|
||||
let focusListener = picker.onDidFocusElement(data => {
|
||||
if (!editor || !(data.target instanceof OutlineElement)) {
|
||||
return;
|
||||
}
|
||||
if (!editorViewState) {
|
||||
editorViewState = withNullAsUndefined(editor.saveViewState());
|
||||
}
|
||||
const { symbol } = data.target;
|
||||
editor.revealRangeInCenterIfOutsideViewport(symbol.range, ScrollType.Smooth);
|
||||
editorDecorations = editor.deltaDecorations(editorDecorations, [{
|
||||
range: symbol.range,
|
||||
options: {
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
}
|
||||
}]);
|
||||
});
|
||||
|
||||
let zoomListener = onDidChangeZoomLevel(() => {
|
||||
this._contextViewService.hideContextView(this);
|
||||
});
|
||||
let selectListener = picker.onWillPickElement(() => this._contextViewService.hideContextView({ source: this, didPick: true }));
|
||||
let zoomListener = onDidChangeZoomLevel(() => this._contextViewService.hideContextView({ source: this }));
|
||||
|
||||
let focusTracker = dom.trackFocus(parent);
|
||||
let blurListener = focusTracker.onDidBlur(() => {
|
||||
this._breadcrumbsPickerIgnoreOnceItem = this._widget.isDOMFocused() ? event.item : undefined;
|
||||
this._contextViewService.hideContextView(this);
|
||||
this._contextViewService.hideContextView({ source: this });
|
||||
});
|
||||
|
||||
this._breadcrumbsPickerShowing = true;
|
||||
@@ -426,7 +390,6 @@ export class BreadcrumbsControl {
|
||||
return combinedDisposable(
|
||||
picker,
|
||||
selectListener,
|
||||
focusListener,
|
||||
zoomListener,
|
||||
focusTracker,
|
||||
blurListener
|
||||
@@ -465,19 +428,17 @@ export class BreadcrumbsControl {
|
||||
}
|
||||
return pickerAnchor;
|
||||
},
|
||||
onHide: (data) => {
|
||||
if (editor) {
|
||||
editor.deltaDecorations(editorDecorations, []);
|
||||
if (editorViewState) {
|
||||
editor.restoreViewState(editorViewState);
|
||||
}
|
||||
onHide: (data?: IHideData) => {
|
||||
if (!data?.didPick) {
|
||||
picker.restoreViewState();
|
||||
}
|
||||
this._breadcrumbsPickerShowing = false;
|
||||
this._updateCkBreadcrumbsActive();
|
||||
if (data === this) {
|
||||
if (data?.source === this) {
|
||||
this._widget.setFocused(undefined);
|
||||
this._widget.setSelection(undefined);
|
||||
}
|
||||
picker.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -487,11 +448,11 @@ export class BreadcrumbsControl {
|
||||
this._ckBreadcrumbsActive.set(value);
|
||||
}
|
||||
|
||||
private _revealInEditor(event: IBreadcrumbsItemEvent, element: BreadcrumbElement, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void {
|
||||
private async _revealInEditor(event: IBreadcrumbsItemEvent, element: FileElement | OutlineElement2, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): Promise<void> {
|
||||
|
||||
if (element instanceof FileElement) {
|
||||
if (element.kind === FileKind.FILE) {
|
||||
// open file in any editor
|
||||
this._editorService.openEditor({ resource: element.uri, options: { pinned } }, group);
|
||||
await this._editorService.openEditor({ resource: element.uri, options: { pinned } }, group);
|
||||
} else {
|
||||
// show next picker
|
||||
let items = this._widget.getItems();
|
||||
@@ -499,20 +460,8 @@ export class BreadcrumbsControl {
|
||||
this._widget.setFocused(items[idx + 1]);
|
||||
this._widget.setSelection(items[idx + 1], BreadcrumbsControl.Payload_Pick);
|
||||
}
|
||||
|
||||
} else if (element instanceof OutlineElement) {
|
||||
// open symbol in code editor
|
||||
const model = OutlineModel.get(element);
|
||||
if (model) {
|
||||
this._codeEditorService.openCodeEditor({
|
||||
resource: model.uri,
|
||||
options: {
|
||||
selection: Range.collapseToStart(element.symbol.selectionRange),
|
||||
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport,
|
||||
pinned
|
||||
}
|
||||
}, withUndefinedAsNull(this._getActiveCodeEditor()), group === SIDE_GROUP);
|
||||
}
|
||||
} else {
|
||||
element.outline.reveal(element, { pinned }, group === SIDE_GROUP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,29 +693,29 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
handler(accessor) {
|
||||
const editors = accessor.get(IEditorService);
|
||||
const lists = accessor.get(IListService);
|
||||
const element = lists.lastFocusedList ? <OutlineElement | IFileStat>lists.lastFocusedList.getFocus()[0] : undefined;
|
||||
if (element instanceof OutlineElement) {
|
||||
const outlineElement = OutlineModel.get(element);
|
||||
if (!outlineElement) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// open symbol in editor
|
||||
return editors.openEditor({
|
||||
resource: outlineElement.uri,
|
||||
options: { selection: Range.collapseToStart(element.symbol.selectionRange), pinned: true }
|
||||
}, SIDE_GROUP);
|
||||
const tree = lists.lastFocusedList;
|
||||
if (!(tree instanceof WorkbenchDataTree)) {
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (element && URI.isUri(element.resource)) {
|
||||
// open file in editor
|
||||
const element = <IFileStat | unknown>tree.getFocus()[0];
|
||||
|
||||
if (URI.isUri((<IFileStat>element)?.resource)) {
|
||||
// IFileStat: open file in editor
|
||||
return editors.openEditor({
|
||||
resource: element.resource,
|
||||
resource: (<IFileStat>element).resource,
|
||||
options: { pinned: true }
|
||||
}, SIDE_GROUP);
|
||||
}
|
||||
|
||||
} else {
|
||||
// ignore
|
||||
return undefined;
|
||||
// IOutline: check if this the outline and iff so reveal element
|
||||
const input = tree.getInput();
|
||||
if (input && typeof (<IOutline<any>>input).outlineKind === 'string') {
|
||||
return (<IOutline<any>>input).reveal(element, {
|
||||
pinned: true,
|
||||
preserveFocus: false
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,27 +3,20 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isEqual, dirname } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IOutline, IOutlineService, OutlineTarget } from 'vs/workbench/services/outline/browser/outline';
|
||||
import { IEditorPane } from 'vs/workbench/common/editor';
|
||||
|
||||
export class FileElement {
|
||||
constructor(
|
||||
@@ -32,11 +25,16 @@ export class FileElement {
|
||||
) { }
|
||||
}
|
||||
|
||||
export type BreadcrumbElement = FileElement | OutlineModel | OutlineGroup | OutlineElement;
|
||||
|
||||
type FileInfo = { path: FileElement[], folder?: IWorkspaceFolder };
|
||||
|
||||
export class EditorBreadcrumbsModel {
|
||||
export class OutlineElement2 {
|
||||
constructor(
|
||||
readonly element: IOutline<any> | any,
|
||||
readonly outline: IOutline<any>
|
||||
) { }
|
||||
}
|
||||
|
||||
export class BreadcrumbsModel {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
private readonly _fileInfo: FileInfo;
|
||||
@@ -45,28 +43,31 @@ export class EditorBreadcrumbsModel {
|
||||
private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
|
||||
private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
|
||||
|
||||
private _outlineElements: Array<OutlineModel | OutlineGroup | OutlineElement> = [];
|
||||
private _outlineDisposables = new DisposableStore();
|
||||
private readonly _currentOutline = new MutableDisposable<IOutline<any>>();
|
||||
private readonly _outlineDisposables = new DisposableStore();
|
||||
|
||||
private readonly _onDidUpdate = new Emitter<this>();
|
||||
readonly onDidUpdate: Event<this> = this._onDidUpdate.event;
|
||||
|
||||
constructor(
|
||||
fileInfoUri: URI,
|
||||
private readonly _uri: URI,
|
||||
private readonly _editor: ICodeEditor | undefined,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService,
|
||||
@IWorkspaceContextService workspaceService: IWorkspaceContextService,
|
||||
readonly resource: URI,
|
||||
editor: IEditorPane | undefined,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
@IOutlineService private readonly _outlineService: IOutlineService,
|
||||
) {
|
||||
this._cfgEnabled = BreadcrumbsConfig.IsEnabled.bindTo(_configurationService);
|
||||
this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(_configurationService);
|
||||
this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(_configurationService);
|
||||
this._cfgEnabled = BreadcrumbsConfig.IsEnabled.bindTo(configurationService);
|
||||
this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService);
|
||||
this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService);
|
||||
|
||||
this._disposables.add(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this)));
|
||||
this._disposables.add(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this)));
|
||||
this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(fileInfoUri, workspaceService);
|
||||
this._bindToEditor();
|
||||
this._fileInfo = this._initFilePathInfo(resource);
|
||||
|
||||
if (editor) {
|
||||
this._bindToEditor(editor);
|
||||
this._disposables.add(_outlineService.onDidChange(() => this._bindToEditor(editor)));
|
||||
}
|
||||
this._onDidUpdate.fire(this);
|
||||
}
|
||||
|
||||
@@ -74,6 +75,7 @@ export class EditorBreadcrumbsModel {
|
||||
this._cfgEnabled.dispose();
|
||||
this._cfgFilePath.dispose();
|
||||
this._cfgSymbolPath.dispose();
|
||||
this._currentOutline.dispose();
|
||||
this._outlineDisposables.dispose();
|
||||
this._disposables.dispose();
|
||||
this._onDidUpdate.dispose();
|
||||
@@ -83,8 +85,8 @@ export class EditorBreadcrumbsModel {
|
||||
return Boolean(this._fileInfo.folder);
|
||||
}
|
||||
|
||||
getElements(): ReadonlyArray<BreadcrumbElement> {
|
||||
let result: BreadcrumbElement[] = [];
|
||||
getElements(): ReadonlyArray<FileElement | OutlineElement2> {
|
||||
let result: (FileElement | OutlineElement2)[] = [];
|
||||
|
||||
// file path elements
|
||||
if (this._cfgFilePath.getValue() === 'on') {
|
||||
@@ -93,17 +95,27 @@ export class EditorBreadcrumbsModel {
|
||||
result = result.concat(this._fileInfo.path.slice(-1));
|
||||
}
|
||||
|
||||
// symbol path elements
|
||||
if (this._cfgSymbolPath.getValue() === 'on') {
|
||||
result = result.concat(this._outlineElements);
|
||||
} else if (this._cfgSymbolPath.getValue() === 'last' && this._outlineElements.length > 0) {
|
||||
result = result.concat(this._outlineElements.slice(-1));
|
||||
if (this._cfgSymbolPath.getValue() === 'off') {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!this._currentOutline.value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const breadcrumbsElements = this._currentOutline.value.config.breadcrumbsDataSource.getBreadcrumbElements();
|
||||
for (let i = this._cfgSymbolPath.getValue() === 'last' && breadcrumbsElements.length > 0 ? breadcrumbsElements.length - 1 : 0; i < breadcrumbsElements.length; i++) {
|
||||
result.push(new OutlineElement2(breadcrumbsElements[i], this._currentOutline.value));
|
||||
}
|
||||
|
||||
if (breadcrumbsElements.length === 0 && !this._currentOutline.value.isEmpty) {
|
||||
result.push(new OutlineElement2(this._currentOutline.value, this._currentOutline.value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _initFilePathInfo(uri: URI, workspaceService: IWorkspaceContextService): FileInfo {
|
||||
private _initFilePathInfo(uri: URI): FileInfo {
|
||||
|
||||
if (uri.scheme === Schemas.untitled) {
|
||||
return {
|
||||
@@ -113,7 +125,7 @@ export class EditorBreadcrumbsModel {
|
||||
}
|
||||
|
||||
let info: FileInfo = {
|
||||
folder: withNullAsUndefined(workspaceService.getWorkspaceFolder(uri)),
|
||||
folder: withNullAsUndefined(this._workspaceService.getWorkspaceFolder(uri)),
|
||||
path: []
|
||||
};
|
||||
|
||||
@@ -130,181 +142,33 @@ export class EditorBreadcrumbsModel {
|
||||
}
|
||||
}
|
||||
|
||||
if (info.folder && workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
if (info.folder && this._workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
info.path.unshift(new FileElement(info.folder.uri, FileKind.ROOT_FOLDER));
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private _bindToEditor(): void {
|
||||
if (!this._editor) {
|
||||
return;
|
||||
}
|
||||
// update as language, model, providers changes
|
||||
this._disposables.add(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline()));
|
||||
this._disposables.add(this._editor.onDidChangeModel(_ => this._updateOutline()));
|
||||
this._disposables.add(this._editor.onDidChangeModelLanguage(_ => this._updateOutline()));
|
||||
|
||||
// update when config changes (re-render)
|
||||
this._disposables.add(this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (!this._cfgEnabled.getValue()) {
|
||||
// breadcrumbs might be disabled (also via a setting/config) and that is
|
||||
// something we must check before proceeding.
|
||||
return;
|
||||
}
|
||||
if (e.affectsConfiguration('breadcrumbs')) {
|
||||
this._updateOutline(true);
|
||||
return;
|
||||
}
|
||||
if (this._editor && this._editor.getModel()) {
|
||||
const editorModel = this._editor.getModel() as ITextModel;
|
||||
const languageName = editorModel.getLanguageIdentifier().language;
|
||||
|
||||
// Checking for changes in the current language override config.
|
||||
// We can't be more specific than this because the ConfigurationChangeEvent(e) only includes the first part of the root path
|
||||
if (e.affectsConfiguration(`[${languageName}]`)) {
|
||||
this._updateOutline(true);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// update soon'ish as model content change
|
||||
const updateSoon = new TimeoutTimer();
|
||||
this._disposables.add(updateSoon);
|
||||
this._disposables.add(this._editor.onDidChangeModelContent(_ => {
|
||||
const timeout = OutlineModel.getRequestDelay(this._editor!.getModel());
|
||||
updateSoon.cancelAndSet(() => this._updateOutline(true), timeout);
|
||||
}));
|
||||
this._updateOutline();
|
||||
|
||||
// stop when editor dies
|
||||
this._disposables.add(this._editor.onDidDispose(() => this._outlineDisposables.clear()));
|
||||
}
|
||||
|
||||
private _updateOutline(didChangeContent?: boolean): void {
|
||||
|
||||
private _bindToEditor(editor: IEditorPane): void {
|
||||
const newCts = new CancellationTokenSource();
|
||||
this._currentOutline.clear();
|
||||
this._outlineDisposables.clear();
|
||||
if (!didChangeContent) {
|
||||
this._updateOutlineElements([]);
|
||||
}
|
||||
this._outlineDisposables.add(toDisposable(() => newCts.dispose(true)));
|
||||
|
||||
const editor = this._editor!;
|
||||
|
||||
const buffer = editor.getModel();
|
||||
if (!buffer || !DocumentSymbolProviderRegistry.has(buffer) || !isEqual(buffer.uri, this._uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const source = new CancellationTokenSource();
|
||||
const versionIdThen = buffer.getVersionId();
|
||||
const timeout = new TimeoutTimer();
|
||||
|
||||
this._outlineDisposables.add({
|
||||
dispose: () => {
|
||||
source.dispose(true);
|
||||
timeout.dispose();
|
||||
this._outlineService.createOutline(editor, OutlineTarget.Breadcrumbs, newCts.token).then(outline => {
|
||||
if (newCts.token.isCancellationRequested) {
|
||||
// cancelled: dispose new outline and reset
|
||||
outline?.dispose();
|
||||
outline = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
OutlineModel.create(buffer, source.token).then(model => {
|
||||
if (source.token.isCancellationRequested) {
|
||||
// cancelled -> do nothing
|
||||
return;
|
||||
this._currentOutline.value = outline;
|
||||
this._onDidUpdate.fire(this);
|
||||
if (outline) {
|
||||
this._outlineDisposables.add(outline.onDidChange(() => this._onDidUpdate.fire(this)));
|
||||
}
|
||||
if (TreeElement.empty(model)) {
|
||||
// empty -> no outline elements
|
||||
this._updateOutlineElements([]);
|
||||
|
||||
} else {
|
||||
// copy the model
|
||||
model = model.adopt();
|
||||
|
||||
this._updateOutlineElements(this._getOutlineElements(model, editor.getPosition()));
|
||||
this._outlineDisposables.add(editor.onDidChangeCursorPosition(_ => {
|
||||
timeout.cancelAndSet(() => {
|
||||
if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && editor.getModel()) {
|
||||
this._updateOutlineElements(this._getOutlineElements(model, editor.getPosition()));
|
||||
}
|
||||
}, 150);
|
||||
}));
|
||||
}
|
||||
}).catch(err => {
|
||||
this._updateOutlineElements([]);
|
||||
this._onDidUpdate.fire(this);
|
||||
onUnexpectedError(err);
|
||||
});
|
||||
}
|
||||
|
||||
private _getOutlineElements(model: OutlineModel, position: IPosition | null): Array<OutlineModel | OutlineGroup | OutlineElement> {
|
||||
if (!model || !position) {
|
||||
return [];
|
||||
}
|
||||
let item: OutlineGroup | OutlineElement | undefined = model.getItemEnclosingPosition(position);
|
||||
if (!item) {
|
||||
return this._getOutlineElementsRoot(model);
|
||||
}
|
||||
let chain: Array<OutlineGroup | OutlineElement> = [];
|
||||
while (item) {
|
||||
chain.push(item);
|
||||
let parent: any = item.parent;
|
||||
if (parent instanceof OutlineModel) {
|
||||
break;
|
||||
}
|
||||
if (parent instanceof OutlineGroup && parent.parent && parent.parent.children.size === 1) {
|
||||
break;
|
||||
}
|
||||
item = parent;
|
||||
}
|
||||
let result: Array<OutlineGroup | OutlineElement> = [];
|
||||
for (let i = chain.length - 1; i >= 0; i--) {
|
||||
let element = chain[i];
|
||||
if (this._isFiltered(element)) {
|
||||
break;
|
||||
}
|
||||
result.push(element);
|
||||
}
|
||||
if (result.length === 0) {
|
||||
return this._getOutlineElementsRoot(model);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _getOutlineElementsRoot(model: OutlineModel): (OutlineModel | OutlineGroup | OutlineElement)[] {
|
||||
for (const child of model.children.values()) {
|
||||
if (!this._isFiltered(child)) {
|
||||
return [model];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private _isFiltered(element: TreeElement): boolean {
|
||||
if (element instanceof OutlineElement) {
|
||||
const key = `breadcrumbs.${OutlineFilter.kindToConfigName[element.symbol.kind]}`;
|
||||
let uri: URI | undefined;
|
||||
if (this._editor && this._editor.getModel()) {
|
||||
const model = this._editor.getModel() as ITextModel;
|
||||
uri = model.uri;
|
||||
}
|
||||
return !this._textResourceConfigurationService.getValue<boolean>(uri, key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _updateOutlineElements(elements: Array<OutlineModel | OutlineGroup | OutlineElement>): void {
|
||||
if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) {
|
||||
this._outlineElements = elements;
|
||||
this._onDidUpdate.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static _outlineElementEquals(a: OutlineModel | OutlineGroup | OutlineElement, b: OutlineModel | OutlineGroup | OutlineElement): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
} else if (!a || !b) {
|
||||
return false;
|
||||
} else {
|
||||
return a.id === b.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,34 +8,30 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
|
||||
import * as glob from 'vs/base/common/glob';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, DisposableStore, MutableDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { posix } from 'vs/base/common/path';
|
||||
import { basename, dirname, isEqual } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!./media/breadcrumbscontrol';
|
||||
import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { WorkbenchDataTree, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { breadcrumbsPickerBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { isWorkspace, isWorkspaceFolder, IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { ResourceLabels, IResourceLabel, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { OutlineElement2, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
|
||||
import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineFilter, OutlineAccessibilityProvider } from 'vs/editor/contrib/documentSymbols/outlineTree';
|
||||
import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { IFileIconTheme, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker {
|
||||
return element instanceof FileElement
|
||||
? instantiationService.createInstance(BreadcrumbsFilePicker, parent)
|
||||
: instantiationService.createInstance(BreadcrumbsOutlinePicker, parent);
|
||||
}
|
||||
import { IOutline, IOutlineComparator } from 'vs/workbench/services/outline/browser/outline';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
|
||||
interface ILayoutInfo {
|
||||
maxHeight: number;
|
||||
@@ -62,17 +58,18 @@ export abstract class BreadcrumbsPicker {
|
||||
protected _fakeEvent = new UIEvent('fakeEvent');
|
||||
protected _layoutInfo!: ILayoutInfo;
|
||||
|
||||
private readonly _onDidPickElement = new Emitter<SelectEvent>();
|
||||
readonly onDidPickElement: Event<SelectEvent> = this._onDidPickElement.event;
|
||||
protected readonly _onWillPickElement = new Emitter<void>();
|
||||
readonly onWillPickElement: Event<void> = this._onWillPickElement.event;
|
||||
|
||||
private readonly _onDidFocusElement = new Emitter<SelectEvent>();
|
||||
readonly onDidFocusElement: Event<SelectEvent> = this._onDidFocusElement.event;
|
||||
private readonly _previewDispoables = new MutableDisposable();
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
protected resource: URI,
|
||||
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
|
||||
@IThemeService protected readonly _themeService: IThemeService,
|
||||
@IConfigurationService protected readonly _configurationService: IConfigurationService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
) {
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons';
|
||||
@@ -81,12 +78,13 @@ export abstract class BreadcrumbsPicker {
|
||||
|
||||
dispose(): void {
|
||||
this._disposables.dispose();
|
||||
this._onDidPickElement.dispose();
|
||||
this._onDidFocusElement.dispose();
|
||||
this._tree.dispose();
|
||||
this._previewDispoables.dispose();
|
||||
this._onWillPickElement.dispose();
|
||||
this._domNode.remove();
|
||||
setTimeout(() => this._tree.dispose(), 0); // tree cannot be disposed while being opened...
|
||||
}
|
||||
|
||||
show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): void {
|
||||
async show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): Promise<void> {
|
||||
|
||||
const theme = this._themeService.getColorTheme();
|
||||
const color = theme.getColor(breadcrumbsPickerBackground);
|
||||
@@ -103,33 +101,33 @@ export abstract class BreadcrumbsPicker {
|
||||
this._domNode.appendChild(this._treeContainer);
|
||||
|
||||
this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 };
|
||||
this._tree = this._createTree(this._treeContainer);
|
||||
this._tree = this._createTree(this._treeContainer, input);
|
||||
|
||||
this._disposables.add(this._tree.onDidChangeSelection(e => {
|
||||
if (e.browserEvent !== this._fakeEvent) {
|
||||
const target = this._getTargetFromEvent(e.elements[0]);
|
||||
if (target) {
|
||||
setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click
|
||||
this._onDidPickElement.fire({ target, browserEvent: e.browserEvent || new UIEvent('fake') });
|
||||
}, 0);
|
||||
}
|
||||
this._disposables.add(this._tree.onDidOpen(async e => {
|
||||
const { element, editorOptions, sideBySide } = e;
|
||||
const didReveal = await this._revealElement(element, { ...editorOptions, preserveFocus: false }, sideBySide);
|
||||
if (!didReveal) {
|
||||
return;
|
||||
}
|
||||
// send telemetry
|
||||
interface OpenEvent { type: string }
|
||||
interface OpenEventGDPR { type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' } }
|
||||
this._telemetryService.publicLog2<OpenEvent, OpenEventGDPR>('breadcrumbs/open', { type: element instanceof OutlineElement2 ? 'symbol' : 'file' });
|
||||
}));
|
||||
this._disposables.add(this._tree.onDidChangeFocus(e => {
|
||||
const target = this._getTargetFromEvent(e.elements[0]);
|
||||
if (target) {
|
||||
this._onDidFocusElement.fire({ target, browserEvent: e.browserEvent || new UIEvent('fake') });
|
||||
}
|
||||
this._previewDispoables.value = this._previewElement(e.elements[0]);
|
||||
}));
|
||||
this._disposables.add(this._tree.onDidChangeContentHeight(() => {
|
||||
this._layout();
|
||||
}));
|
||||
|
||||
this._domNode.focus();
|
||||
|
||||
this._setInput(input).then(() => {
|
||||
try {
|
||||
await this._setInput(input);
|
||||
this._layout();
|
||||
}).catch(onUnexpectedError);
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
}
|
||||
|
||||
protected _layout(): void {
|
||||
@@ -146,16 +144,15 @@ export abstract class BreadcrumbsPicker {
|
||||
this._treeContainer.style.height = `${treeHeight}px`;
|
||||
this._treeContainer.style.width = `${this._layoutInfo.width}px`;
|
||||
this._tree.layout(treeHeight, this._layoutInfo.width);
|
||||
|
||||
}
|
||||
|
||||
get useAltAsMultipleSelectionModifier() {
|
||||
return this._tree.useAltAsMultipleSelectionModifier;
|
||||
}
|
||||
restoreViewState(): void { }
|
||||
|
||||
protected abstract _setInput(element: FileElement | OutlineElement2): Promise<void>;
|
||||
protected abstract _createTree(container: HTMLElement, input: any): Tree<any, any>;
|
||||
protected abstract _previewElement(element: any): IDisposable;
|
||||
protected abstract _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise<boolean>;
|
||||
|
||||
protected abstract _setInput(element: BreadcrumbElement): Promise<void>;
|
||||
protected abstract _createTree(container: HTMLElement): Tree<any, any>;
|
||||
protected abstract _getTargetFromEvent(element: any): any | undefined;
|
||||
}
|
||||
|
||||
//#region - Files
|
||||
@@ -173,9 +170,9 @@ class FileIdentityProvider implements IIdentityProvider<IWorkspace | IWorkspaceF
|
||||
getId(element: IWorkspace | IWorkspaceFolder | IFileStat | URI): { toString(): string; } {
|
||||
if (URI.isUri(element)) {
|
||||
return element.toString();
|
||||
} else if (IWorkspace.isIWorkspace(element)) {
|
||||
} else if (isWorkspace(element)) {
|
||||
return element.id;
|
||||
} else if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
} else if (isWorkspaceFolder(element)) {
|
||||
return element.uri.toString();
|
||||
} else {
|
||||
return element.resource.toString();
|
||||
@@ -186,43 +183,31 @@ class FileIdentityProvider implements IIdentityProvider<IWorkspace | IWorkspaceF
|
||||
|
||||
class FileDataSource implements IAsyncDataSource<IWorkspace | URI, IWorkspaceFolder | IFileStat> {
|
||||
|
||||
private readonly _parents = new WeakMap<object, IWorkspaceFolder | IFileStat>();
|
||||
|
||||
constructor(
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
) { }
|
||||
|
||||
hasChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): boolean {
|
||||
return URI.isUri(element)
|
||||
|| IWorkspace.isIWorkspace(element)
|
||||
|| IWorkspaceFolder.isIWorkspaceFolder(element)
|
||||
|| isWorkspace(element)
|
||||
|| isWorkspaceFolder(element)
|
||||
|| element.isDirectory;
|
||||
}
|
||||
|
||||
getChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise<(IWorkspaceFolder | IFileStat)[]> {
|
||||
|
||||
if (IWorkspace.isIWorkspace(element)) {
|
||||
return Promise.resolve(element.folders).then(folders => {
|
||||
for (let child of folders) {
|
||||
this._parents.set(element, child);
|
||||
}
|
||||
return folders;
|
||||
});
|
||||
async getChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise<(IWorkspaceFolder | IFileStat)[]> {
|
||||
if (isWorkspace(element)) {
|
||||
return element.folders;
|
||||
}
|
||||
let uri: URI;
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
if (isWorkspaceFolder(element)) {
|
||||
uri = element.uri;
|
||||
} else if (URI.isUri(element)) {
|
||||
uri = element;
|
||||
} else {
|
||||
uri = element.resource;
|
||||
}
|
||||
return this._fileService.resolve(uri).then(stat => {
|
||||
for (const child of stat.children || []) {
|
||||
this._parents.set(stat, child);
|
||||
}
|
||||
return stat.children || [];
|
||||
});
|
||||
const stat = await this._fileService.resolve(uri);
|
||||
return stat.children ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +230,7 @@ class FileRenderer implements ITreeRenderer<IFileStat | IWorkspaceFolder, FuzzyS
|
||||
const { element } = node;
|
||||
let resource: URI;
|
||||
let fileKind: FileKind;
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
if (isWorkspaceFolder(element)) {
|
||||
resource = element.uri;
|
||||
fileKind = FileKind.ROOT_FOLDER;
|
||||
} else {
|
||||
@@ -327,7 +312,7 @@ class FileFilter implements ITreeFilter<IWorkspaceFolder | IFileStat> {
|
||||
}
|
||||
|
||||
filter(element: IWorkspaceFolder | IFileStat, _parentVisibility: TreeVisibility): boolean {
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
if (isWorkspaceFolder(element)) {
|
||||
// not a file
|
||||
return true;
|
||||
}
|
||||
@@ -345,7 +330,7 @@ class FileFilter implements ITreeFilter<IWorkspaceFolder | IFileStat> {
|
||||
|
||||
export class FileSorter implements ITreeSorter<IFileStat | IWorkspaceFolder> {
|
||||
compare(a: IFileStat | IWorkspaceFolder, b: IFileStat | IWorkspaceFolder): number {
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(a) && IWorkspaceFolder.isIWorkspaceFolder(b)) {
|
||||
if (isWorkspaceFolder(a) && isWorkspaceFolder(b)) {
|
||||
return a.index - b.index;
|
||||
}
|
||||
if ((a as IFileStat).isDirectory === (b as IFileStat).isDirectory) {
|
||||
@@ -363,12 +348,15 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
protected resource: URI,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService configService: IConfigurationService,
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super(parent, instantiationService, themeService, configService);
|
||||
super(parent, resource, instantiationService, themeService, configService, telemetryService);
|
||||
}
|
||||
|
||||
_createTree(container: HTMLElement) {
|
||||
@@ -406,7 +394,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
|
||||
});
|
||||
}
|
||||
|
||||
_setInput(element: BreadcrumbElement): Promise<void> {
|
||||
async _setInput(element: FileElement | OutlineElement2): Promise<void> {
|
||||
const { uri, kind } = (element as FileElement);
|
||||
let input: IWorkspace | URI;
|
||||
if (kind === FileKind.ROOT_FOLDER) {
|
||||
@@ -416,115 +404,120 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
|
||||
}
|
||||
|
||||
const tree = this._tree as WorkbenchAsyncDataTree<IWorkspace | URI, IWorkspaceFolder | IFileStat, FuzzyScore>;
|
||||
return tree.setInput(input).then(() => {
|
||||
let focusElement: IWorkspaceFolder | IFileStat | undefined;
|
||||
for (const { element } of tree.getNode().children) {
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element) && isEqual(element.uri, uri)) {
|
||||
focusElement = element;
|
||||
break;
|
||||
} else if (isEqual((element as IFileStat).resource, uri)) {
|
||||
focusElement = element as IFileStat;
|
||||
break;
|
||||
}
|
||||
await tree.setInput(input);
|
||||
let focusElement: IWorkspaceFolder | IFileStat | undefined;
|
||||
for (const { element } of tree.getNode().children) {
|
||||
if (isWorkspaceFolder(element) && isEqual(element.uri, uri)) {
|
||||
focusElement = element;
|
||||
break;
|
||||
} else if (isEqual((element as IFileStat).resource, uri)) {
|
||||
focusElement = element as IFileStat;
|
||||
break;
|
||||
}
|
||||
if (focusElement) {
|
||||
tree.reveal(focusElement, 0.5);
|
||||
tree.setFocus([focusElement], this._fakeEvent);
|
||||
}
|
||||
tree.domFocus();
|
||||
});
|
||||
}
|
||||
if (focusElement) {
|
||||
tree.reveal(focusElement, 0.5);
|
||||
tree.setFocus([focusElement], this._fakeEvent);
|
||||
}
|
||||
tree.domFocus();
|
||||
}
|
||||
|
||||
protected _getTargetFromEvent(element: any): any | undefined {
|
||||
if (element && !IWorkspaceFolder.isIWorkspaceFolder(element) && !(element as IFileStat).isDirectory) {
|
||||
return new FileElement((element as IFileStat).resource, FileKind.FILE);
|
||||
protected _previewElement(_element: any): IDisposable {
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
async _revealElement(element: IFileStat | IWorkspaceFolder, options: IEditorOptions, sideBySide: boolean): Promise<boolean> {
|
||||
let resource: URI | undefined;
|
||||
if (isWorkspaceFolder(element)) {
|
||||
resource = element.uri;
|
||||
} else if (!element.isDirectory) {
|
||||
resource = element.resource;
|
||||
}
|
||||
if (resource) {
|
||||
this._onWillPickElement.fire();
|
||||
await this._editorService.openEditor({ resource, options }, sideBySide ? SIDE_GROUP : undefined);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region - Symbols
|
||||
//#region - Outline
|
||||
|
||||
class OutlineTreeSorter<E> implements ITreeSorter<E> {
|
||||
|
||||
private _order: 'name' | 'type' | 'position';
|
||||
|
||||
constructor(
|
||||
private comparator: IOutlineComparator<E>,
|
||||
uri: URI | undefined,
|
||||
@ITextResourceConfigurationService configService: ITextResourceConfigurationService,
|
||||
) {
|
||||
this._order = configService.getValue(uri, 'breadcrumbs.symbolSortOrder');
|
||||
}
|
||||
|
||||
compare(a: E, b: E): number {
|
||||
if (this._order === 'name') {
|
||||
return this.comparator.compareByName(a, b);
|
||||
} else if (this._order === 'type') {
|
||||
return this.comparator.compareByType(a, b);
|
||||
} else {
|
||||
return this.comparator.compareByPosition(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker {
|
||||
|
||||
protected readonly _symbolSortOrder: BreadcrumbsConfig<'position' | 'name' | 'type'>;
|
||||
protected _outlineComparator: OutlineItemComparator;
|
||||
protected _createTree(container: HTMLElement, input: OutlineElement2) {
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IModeService private readonly _modeService: IModeService,
|
||||
) {
|
||||
super(parent, instantiationService, themeService, configurationService);
|
||||
this._symbolSortOrder = BreadcrumbsConfig.SymbolSortOrder.bindTo(this._configurationService);
|
||||
this._outlineComparator = new OutlineItemComparator();
|
||||
}
|
||||
const { config } = input.outline;
|
||||
|
||||
protected _createTree(container: HTMLElement) {
|
||||
return <WorkbenchDataTree<OutlineModel, any, FuzzyScore>>this._instantiationService.createInstance(
|
||||
return <WorkbenchDataTree<IOutline<any>, any, FuzzyScore>>this._instantiationService.createInstance(
|
||||
WorkbenchDataTree,
|
||||
'BreadcrumbsOutlinePicker',
|
||||
container,
|
||||
new OutlineVirtualDelegate(),
|
||||
[new OutlineGroupRenderer(), this._instantiationService.createInstance(OutlineElementRenderer)],
|
||||
new OutlineDataSource(),
|
||||
config.delegate,
|
||||
config.renderers,
|
||||
config.treeDataSource,
|
||||
{
|
||||
...config.options,
|
||||
sorter: this._instantiationService.createInstance(OutlineTreeSorter, config.comparator, undefined),
|
||||
collapseByDefault: true,
|
||||
expandOnlyOnTwistieClick: true,
|
||||
multipleSelectionSupport: false,
|
||||
sorter: this._outlineComparator,
|
||||
identityProvider: new OutlineIdentityProvider(),
|
||||
keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(),
|
||||
accessibilityProvider: new OutlineAccessibilityProvider(localize('breadcrumbs', "Breadcrumbs")),
|
||||
filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs')
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._symbolSortOrder.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
protected _setInput(input: OutlineElement2): Promise<void> {
|
||||
|
||||
protected _setInput(input: BreadcrumbElement): Promise<void> {
|
||||
const element = input as TreeElement;
|
||||
const model = OutlineModel.get(element)!;
|
||||
const tree = this._tree as WorkbenchDataTree<OutlineModel, any, FuzzyScore>;
|
||||
const viewState = input.outline.captureViewState();
|
||||
this.restoreViewState = () => { viewState.dispose(); };
|
||||
|
||||
const overrideConfiguration = {
|
||||
resource: model.uri,
|
||||
overrideIdentifier: this._modeService.getModeIdByFilepathOrFirstLine(model.uri)
|
||||
};
|
||||
this._outlineComparator.type = this._getOutlineItemCompareType(overrideConfiguration);
|
||||
const tree = this._tree as WorkbenchDataTree<IOutline<any>, any, FuzzyScore>;
|
||||
|
||||
tree.setInput(model);
|
||||
if (element !== model) {
|
||||
tree.reveal(element, 0.5);
|
||||
tree.setFocus([element], this._fakeEvent);
|
||||
tree.setInput(input.outline);
|
||||
if (input.element !== input.outline) {
|
||||
tree.reveal(input.element, 0.5);
|
||||
tree.setFocus([input.element], this._fakeEvent);
|
||||
}
|
||||
tree.domFocus();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected _getTargetFromEvent(element: any): any | undefined {
|
||||
if (element instanceof OutlineElement) {
|
||||
return element;
|
||||
}
|
||||
protected _previewElement(element: any): IDisposable {
|
||||
const outline: IOutline<any> = this._tree.getInput();
|
||||
return outline.preview(element);
|
||||
}
|
||||
|
||||
private _getOutlineItemCompareType(overrideConfiguration?: IConfigurationOverrides): OutlineSortOrder {
|
||||
switch (this._symbolSortOrder.getValue(overrideConfiguration)) {
|
||||
case 'name':
|
||||
return OutlineSortOrder.ByName;
|
||||
case 'type':
|
||||
return OutlineSortOrder.ByKind;
|
||||
case 'position':
|
||||
default:
|
||||
return OutlineSortOrder.ByPosition;
|
||||
}
|
||||
async _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise<boolean> {
|
||||
this._onWillPickElement.fire();
|
||||
const outline: IOutline<any> = this._tree.getInput();
|
||||
await outline.reveal(element, options, sideBySide);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction,
|
||||
EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction,
|
||||
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction,
|
||||
QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReopenResourcesAction, ToggleEditorTypeAction
|
||||
QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReopenResourcesAction, ToggleEditorTypeAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -42,7 +42,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
|
||||
import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/codeeditor';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { toLocalResource } from 'vs/base/common/resources';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
@@ -351,6 +351,10 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupLeftAction,
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupRightAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupUpAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), 'View: Move Editor Group Up', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupDownAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), 'View: Move Editor Group Down', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupLeftAction), 'View: Duplicate Editor Group Left', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupRightAction), 'View: Duplicate Editor Group Right', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupUpAction), 'View: Duplicate Editor Group Up', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupDownAction), 'View: Duplicate Editor Group Down', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToPreviousGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToNextGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToFirstGroupAction, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', CATEGORIES.View.value);
|
||||
|
||||
@@ -23,6 +23,7 @@ import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecent
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { openEditorWith, getAllAvailableEditors } from 'vs/workbench/services/editor/common/editorOpenWith';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class ExecuteCommandAction extends Action {
|
||||
|
||||
@@ -746,12 +747,13 @@ export class CloseEditorInAllGroupsAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseMoveGroupAction extends Action {
|
||||
class BaseMoveCopyGroupAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private direction: GroupDirection,
|
||||
private isMove: boolean,
|
||||
private editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label);
|
||||
@@ -766,9 +768,18 @@ export class BaseMoveGroupAction extends Action {
|
||||
}
|
||||
|
||||
if (sourceGroup) {
|
||||
const targetGroup = this.findTargetGroup(sourceGroup);
|
||||
if (targetGroup) {
|
||||
this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
|
||||
let resultGroup: IEditorGroup | undefined = undefined;
|
||||
if (this.isMove) {
|
||||
const targetGroup = this.findTargetGroup(sourceGroup);
|
||||
if (targetGroup) {
|
||||
resultGroup = this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
|
||||
}
|
||||
} else {
|
||||
resultGroup = this.editorGroupService.copyGroup(sourceGroup, sourceGroup, this.direction);
|
||||
}
|
||||
|
||||
if (resultGroup) {
|
||||
this.editorGroupService.activateGroup(resultGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -801,6 +812,18 @@ export class BaseMoveGroupAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
class BaseMoveGroupAction extends BaseMoveCopyGroupAction {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
direction: GroupDirection,
|
||||
editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, direction, true, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveGroupLeftAction extends BaseMoveGroupAction {
|
||||
|
||||
static readonly ID = 'workbench.action.moveActiveEditorGroupLeft';
|
||||
@@ -857,6 +880,74 @@ export class MoveGroupDownAction extends BaseMoveGroupAction {
|
||||
}
|
||||
}
|
||||
|
||||
class BaseDuplicateGroupAction extends BaseMoveCopyGroupAction {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
direction: GroupDirection,
|
||||
editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, direction, false, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateGroupLeftAction extends BaseDuplicateGroupAction {
|
||||
|
||||
static readonly ID = 'workbench.action.duplicateActiveEditorGroupLeft';
|
||||
static readonly LABEL = nls.localize('duplicateActiveGroupLeft', "Duplicate Editor Group Left");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, GroupDirection.LEFT, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateGroupRightAction extends BaseDuplicateGroupAction {
|
||||
|
||||
static readonly ID = 'workbench.action.duplicateActiveEditorGroupRight';
|
||||
static readonly LABEL = nls.localize('duplicateActiveGroupRight', "Duplicate Editor Group Right");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, GroupDirection.RIGHT, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateGroupUpAction extends BaseDuplicateGroupAction {
|
||||
|
||||
static readonly ID = 'workbench.action.duplicateActiveEditorGroupUp';
|
||||
static readonly LABEL = nls.localize('duplicateActiveGroupUp', "Duplicate Editor Group Up");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, GroupDirection.UP, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateGroupDownAction extends BaseDuplicateGroupAction {
|
||||
|
||||
static readonly ID = 'workbench.action.duplicateActiveEditorGroupDown';
|
||||
static readonly LABEL = nls.localize('duplicateActiveGroupDown', "Duplicate Editor Group Down");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, GroupDirection.DOWN, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class MinimizeOtherGroupsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.minimizeOtherEditors';
|
||||
@@ -1803,9 +1894,8 @@ export class ReopenResourcesAction extends Action {
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
@@ -1823,7 +1913,7 @@ export class ReopenResourcesAction extends Action {
|
||||
|
||||
const options = activeEditorPane.options;
|
||||
const group = activeEditorPane.group;
|
||||
await openEditorWith(activeInput, undefined, options, group, this.editorService, this.configurationService, this.quickInputService);
|
||||
await this.instantiationService.invokeFunction(openEditorWith, activeInput, undefined, options, group);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith';
|
||||
@@ -484,7 +483,6 @@ function registerOpenEditorAPICommands(): void {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const editorGroupsService = accessor.get(IEditorGroupsService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
|
||||
const [columnArg, optionsArg] = columnAndOptions ?? [];
|
||||
let group: IEditorGroup | undefined = undefined;
|
||||
@@ -504,7 +502,7 @@ function registerOpenEditorAPICommands(): void {
|
||||
const textOptions: ITextEditorOptions = optionsArg ? { ...optionsArg, override: false } : { override: false };
|
||||
|
||||
const input = editorService.createEditorInput({ resource: URI.revive(resource) });
|
||||
return openEditorWith(input, id, textOptions, group, editorService, configurationService, quickInputService);
|
||||
return openEditorWith(accessor, input, id, textOptions, group);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -902,24 +900,10 @@ function registerOtherEditorCommands(): void {
|
||||
id: TOGGLE_KEEP_EDITORS_COMMAND_ID,
|
||||
handler: accessor => {
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
const openerService = accessor.get(IOpenerService);
|
||||
|
||||
// Update setting
|
||||
const currentSetting = configurationService.getValue<boolean>('workbench.editor.enablePreview');
|
||||
const newSetting = currentSetting === true ? false : true;
|
||||
configurationService.updateValue('workbench.editor.enablePreview', newSetting);
|
||||
|
||||
// Inform user
|
||||
notificationService.prompt(
|
||||
Severity.Info,
|
||||
newSetting ?
|
||||
nls.localize('enablePreview', "Preview editors have been enabled in settings.") :
|
||||
nls.localize('disablePreview', "Preview editors have been disabled in settings."),
|
||||
[{
|
||||
label: nls.localize('learnMode', "Learn More"), run: () => openerService.open('https://go.microsoft.com/fwlink/?linkid=2147473')
|
||||
}]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -283,7 +283,8 @@ class DropOverlay extends Themable {
|
||||
// Open in target group
|
||||
const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({
|
||||
pinned: true, // always pin dropped editor
|
||||
sticky: sourceGroup.isSticky(draggedEditor.editor) // preserve sticky state
|
||||
sticky: sourceGroup.isSticky(draggedEditor.editor), // preserve sticky state
|
||||
override: false, // Use `draggedEditor.editor` as is. If it is already a custom editor, it will stay so.
|
||||
}));
|
||||
const copyEditor = this.isCopyOperation(event, draggedEditor);
|
||||
targetGroup.openEditor(draggedEditor.editor, options, copyEditor ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR);
|
||||
|
||||
@@ -322,8 +322,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
private createContainerContextMenu(): void {
|
||||
const menu = this._register(this.menuService.createMenu(MenuId.EmptyEditorGroupContext, this.contextKeyService));
|
||||
|
||||
this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, event => this.onShowContainerContextMenu(menu, event)));
|
||||
this._register(addDisposableListener(this.element, TouchEventType.Contextmenu, event => this.onShowContainerContextMenu(menu)));
|
||||
this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, e => this.onShowContainerContextMenu(menu, e)));
|
||||
this._register(addDisposableListener(this.element, TouchEventType.Contextmenu, () => this.onShowContainerContextMenu(menu)));
|
||||
}
|
||||
|
||||
private onShowContainerContextMenu(menu: IMenu, e?: MouseEvent): void {
|
||||
@@ -1704,13 +1704,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
layout(width: number, height: number): void {
|
||||
this.dimension = new Dimension(width, height);
|
||||
|
||||
// Ensure editor container gets height as CSS depending on the preferred height of the title control
|
||||
const titleHeight = this.titleDimensions.height;
|
||||
const editorHeight = Math.max(0, height - titleHeight);
|
||||
this.editorContainer.style.height = `${editorHeight}px`;
|
||||
// Layout the title area first to receive the size it occupies
|
||||
const titleAreaSize = this.titleAreaControl.layout({
|
||||
container: this.dimension,
|
||||
available: new Dimension(width, height - this.editorControl.minimumHeight)
|
||||
});
|
||||
|
||||
// Forward to controls
|
||||
this.titleAreaControl.layout(new Dimension(width, titleHeight));
|
||||
// Pass the container width and remaining height to the editor layout
|
||||
const editorHeight = Math.max(0, height - titleAreaSize.height);
|
||||
this.editorContainer.style.height = `${editorHeight}px`;
|
||||
this.editorControl.layout(new Dimension(width, editorHeight));
|
||||
}
|
||||
|
||||
@@ -1769,7 +1771,7 @@ export interface EditorReplacement {
|
||||
readonly options?: EditorOptions;
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector, environment) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
||||
// Letterpress
|
||||
const letterpress = `./media/letterpress${theme.type === 'dark' ? '-dark' : theme.type === 'hc' ? '-hc' : ''}.svg`;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/editorstatus';
|
||||
import * as nls from 'vs/nls';
|
||||
import { localize } from 'vs/nls';
|
||||
import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { format, compare, splitLines } from 'vs/base/common/strings';
|
||||
import { extname, basename, isEqual } from 'vs/base/common/resources';
|
||||
@@ -283,14 +283,15 @@ class State {
|
||||
}
|
||||
}
|
||||
|
||||
const nlsSingleSelectionRange = nls.localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)");
|
||||
const nlsSingleSelection = nls.localize('singleSelection', "Ln {0}, Col {1}");
|
||||
const nlsMultiSelectionRange = nls.localize('multiSelectionRange', "{0} selections ({1} characters selected)");
|
||||
const nlsMultiSelection = nls.localize('multiSelection', "{0} selections");
|
||||
const nlsEOLLF = nls.localize('endOfLineLineFeed', "LF");
|
||||
const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF");
|
||||
const nlsSingleSelectionRange = localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)");
|
||||
const nlsSingleSelection = localize('singleSelection', "Ln {0}, Col {1}");
|
||||
const nlsMultiSelectionRange = localize('multiSelectionRange', "{0} selections ({1} characters selected)");
|
||||
const nlsMultiSelection = localize('multiSelection', "{0} selections");
|
||||
const nlsEOLLF = localize('endOfLineLineFeed', "LF");
|
||||
const nlsEOLCRLF = localize('endOfLineCarriageReturnLineFeed', "CRLF");
|
||||
|
||||
export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private readonly tabFocusModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
private readonly columnSelectionModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
private readonly screenRedearModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
@@ -342,14 +343,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
if (!this.screenReaderNotification) {
|
||||
this.screenReaderNotification = this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code? (word wrap is disabled when using a screen reader)"),
|
||||
localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code? (word wrap is disabled when using a screen reader)"),
|
||||
[{
|
||||
label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"),
|
||||
label: localize('screenReaderDetectedExplanation.answerYes', "Yes"),
|
||||
run: () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'on');
|
||||
}
|
||||
}, {
|
||||
label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"),
|
||||
label: localize('screenReaderDetectedExplanation.answerNo', "No"),
|
||||
run: () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'off');
|
||||
}
|
||||
@@ -364,11 +365,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
private async showIndentationPicker(): Promise<unknown> {
|
||||
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (!activeTextEditorControl) {
|
||||
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
return this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
if (this.editorService.activeEditor?.isReadonly()) {
|
||||
return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
return this.quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
}
|
||||
|
||||
const picks: QuickPickInput<IQuickPickItem & { run(): void; }>[] = [
|
||||
@@ -390,25 +391,25 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
};
|
||||
});
|
||||
|
||||
picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") });
|
||||
picks.unshift({ type: 'separator', label: nls.localize('indentView', "change view") });
|
||||
picks.splice(3, 0, { type: 'separator', label: localize('indentConvert', "convert file") });
|
||||
picks.unshift({ type: 'separator', label: localize('indentView', "change view") });
|
||||
|
||||
const action = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
const action = await this.quickInputService.pick(picks, { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
return action?.run();
|
||||
}
|
||||
|
||||
private updateTabFocusModeElement(visible: boolean): void {
|
||||
if (visible) {
|
||||
if (!this.tabFocusModeElement.value) {
|
||||
const text = nls.localize('tabFocusModeEnabled', "Tab Moves Focus");
|
||||
const text = localize('tabFocusModeEnabled', "Tab Moves Focus");
|
||||
this.tabFocusModeElement.value = this.statusbarService.addEntry({
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('disableTabMode', "Disable Accessibility Mode"),
|
||||
tooltip: localize('disableTabMode', "Disable Accessibility Mode"),
|
||||
command: 'editor.action.toggleTabFocusMode',
|
||||
backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND),
|
||||
color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND)
|
||||
}, 'status.editor.tabFocusMode', nls.localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7);
|
||||
}, 'status.editor.tabFocusMode', localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7);
|
||||
}
|
||||
} else {
|
||||
this.tabFocusModeElement.clear();
|
||||
@@ -418,15 +419,15 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
private updateColumnSelectionModeElement(visible: boolean): void {
|
||||
if (visible) {
|
||||
if (!this.columnSelectionModeElement.value) {
|
||||
const text = nls.localize('columnSelectionModeEnabled', "Column Selection");
|
||||
const text = localize('columnSelectionModeEnabled', "Column Selection");
|
||||
this.columnSelectionModeElement.value = this.statusbarService.addEntry({
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('disableColumnSelectionMode', "Disable Column Selection Mode"),
|
||||
tooltip: localize('disableColumnSelectionMode', "Disable Column Selection Mode"),
|
||||
command: 'editor.action.toggleColumnSelection',
|
||||
backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND),
|
||||
color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND)
|
||||
}, 'status.editor.columnSelectionMode', nls.localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8);
|
||||
}, 'status.editor.columnSelectionMode', localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8);
|
||||
}
|
||||
} else {
|
||||
this.columnSelectionModeElement.clear();
|
||||
@@ -436,14 +437,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
private updateScreenReaderModeElement(visible: boolean): void {
|
||||
if (visible) {
|
||||
if (!this.screenRedearModeElement.value) {
|
||||
const text = nls.localize('screenReaderDetected', "Screen Reader Optimized");
|
||||
const text = localize('screenReaderDetected', "Screen Reader Optimized");
|
||||
this.screenRedearModeElement.value = this.statusbarService.addEntry({
|
||||
text,
|
||||
ariaLabel: text,
|
||||
command: 'showEditorScreenReaderNotification',
|
||||
backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND),
|
||||
color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND)
|
||||
}, 'status.editor.screenReaderMode', nls.localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6);
|
||||
}, 'status.editor.screenReaderMode', localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6);
|
||||
}
|
||||
} else {
|
||||
this.screenRedearModeElement.clear();
|
||||
@@ -459,11 +460,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('gotoLine', "Go to Line/Column"),
|
||||
tooltip: localize('gotoLine', "Go to Line/Column"),
|
||||
command: 'workbench.action.gotoLine'
|
||||
};
|
||||
|
||||
this.updateElement(this.selectionElement, props, 'status.editor.selection', nls.localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5);
|
||||
this.updateElement(this.selectionElement, props, 'status.editor.selection', localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5);
|
||||
}
|
||||
|
||||
private updateIndentationElement(text: string | undefined): void {
|
||||
@@ -475,11 +476,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('selectIndentation', "Select Indentation"),
|
||||
tooltip: localize('selectIndentation', "Select Indentation"),
|
||||
command: 'changeEditorIndentation'
|
||||
};
|
||||
|
||||
this.updateElement(this.indentationElement, props, 'status.editor.indentation', nls.localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4);
|
||||
this.updateElement(this.indentationElement, props, 'status.editor.indentation', localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4);
|
||||
}
|
||||
|
||||
private updateEncodingElement(text: string | undefined): void {
|
||||
@@ -491,11 +492,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('selectEncoding', "Select Encoding"),
|
||||
tooltip: localize('selectEncoding', "Select Encoding"),
|
||||
command: 'workbench.action.editor.changeEncoding'
|
||||
};
|
||||
|
||||
this.updateElement(this.encodingElement, props, 'status.editor.encoding', nls.localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3);
|
||||
this.updateElement(this.encodingElement, props, 'status.editor.encoding', localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3);
|
||||
}
|
||||
|
||||
private updateEOLElement(text: string | undefined): void {
|
||||
@@ -507,11 +508,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('selectEOL', "Select End of Line Sequence"),
|
||||
tooltip: localize('selectEOL', "Select End of Line Sequence"),
|
||||
command: 'workbench.action.editor.changeEOL'
|
||||
};
|
||||
|
||||
this.updateElement(this.eolElement, props, 'status.editor.eol', nls.localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2);
|
||||
this.updateElement(this.eolElement, props, 'status.editor.eol', localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2);
|
||||
}
|
||||
|
||||
private updateModeElement(text: string | undefined): void {
|
||||
@@ -523,11 +524,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('selectLanguageMode', "Select Language Mode"),
|
||||
tooltip: localize('selectLanguageMode', "Select Language Mode"),
|
||||
command: 'workbench.action.editor.changeLanguageMode'
|
||||
};
|
||||
|
||||
this.updateElement(this.modeElement, props, 'status.editor.mode', nls.localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1);
|
||||
this.updateElement(this.modeElement, props, 'status.editor.mode', localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1);
|
||||
}
|
||||
|
||||
private updateMetadataElement(text: string | undefined): void {
|
||||
@@ -539,10 +540,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('fileInfo', "File Information")
|
||||
tooltip: localize('fileInfo', "File Information")
|
||||
};
|
||||
|
||||
this.updateElement(this.metadataElement, props, 'status.editor.info', nls.localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100);
|
||||
this.updateElement(this.metadataElement, props, 'status.editor.info', localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100);
|
||||
}
|
||||
|
||||
private updateElement(element: MutableDisposable<IStatusbarEntryAccessor>, props: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number) {
|
||||
@@ -730,8 +731,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const modelOpts = model.getOptions();
|
||||
update.indentation = (
|
||||
modelOpts.insertSpaces
|
||||
? nls.localize('spacesSize', "Spaces: {0}", modelOpts.indentSize)
|
||||
: nls.localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize)
|
||||
? localize('spacesSize', "Spaces: {0}", modelOpts.indentSize)
|
||||
: localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -766,7 +767,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
if (editorWidget) {
|
||||
const screenReaderDetected = this.accessibilityService.isScreenReaderOptimized();
|
||||
if (screenReaderDetected) {
|
||||
const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
|
||||
const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor')?.accessibilitySupport;
|
||||
if (screenReaderConfiguration === 'auto') {
|
||||
if (!this.promptedScreenReader) {
|
||||
this.promptedScreenReader = true;
|
||||
@@ -921,7 +922,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
const line = splitLines(this.currentMarker.message)[0];
|
||||
const text = `${this.getType(this.currentMarker)} ${line}`;
|
||||
if (!this.statusBarEntryAccessor.value) {
|
||||
this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '', ariaLabel: '' }, 'statusbar.currentProblem', nls.localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT);
|
||||
this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '', ariaLabel: '' }, 'statusbar.currentProblem', localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT);
|
||||
}
|
||||
this.statusBarEntryAccessor.value.update({ text, ariaLabel: text });
|
||||
} else {
|
||||
@@ -934,9 +935,11 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
if (!currentMarker) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!previousMarker) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return IMarkerData.makeKey(previousMarker) !== IMarkerData.makeKey(currentMarker);
|
||||
}
|
||||
|
||||
@@ -946,6 +949,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
case MarkerSeverity.Warning: return '$(warning)';
|
||||
case MarkerSeverity.Info: return '$(info)';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -953,17 +957,21 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
if (!this.configurationService.getValue<boolean>('problems.showCurrentInStatus')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.editor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const position = this.editor.getPosition();
|
||||
if (!position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.markers.find(marker => Range.containsPosition(marker, position)) || null;
|
||||
}
|
||||
|
||||
@@ -971,13 +979,16 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
if (!this.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model && !changedResources.some(r => isEqual(model.uri, r))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateMarkers();
|
||||
}
|
||||
|
||||
@@ -985,10 +996,12 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
if (!this.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model) {
|
||||
this.markers = this.markerService.read({
|
||||
resource: model.uri,
|
||||
@@ -998,6 +1011,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
} else {
|
||||
this.markers = [];
|
||||
}
|
||||
|
||||
this.updateStatus();
|
||||
}
|
||||
}
|
||||
@@ -1007,9 +1021,11 @@ function compareMarker(a: IMarker, b: IMarker): number {
|
||||
if (res === 0) {
|
||||
res = MarkerSeverity.compare(a.severity, b.severity);
|
||||
}
|
||||
|
||||
if (res === 0) {
|
||||
res = Range.compareRangesUsingStarts(a, b);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1022,7 +1038,7 @@ export class ShowLanguageExtensionsAction extends Action {
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IExtensionGalleryService galleryService: IExtensionGalleryService
|
||||
) {
|
||||
super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));
|
||||
super(ShowLanguageExtensionsAction.ID, localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));
|
||||
|
||||
this.enabled = galleryService.isEnabled();
|
||||
}
|
||||
@@ -1035,7 +1051,7 @@ export class ShowLanguageExtensionsAction extends Action {
|
||||
export class ChangeModeAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.editor.changeLanguageMode';
|
||||
static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
|
||||
static readonly LABEL = localize('changeMode', "Change Language Mode");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
@@ -1054,14 +1070,14 @@ export class ChangeModeAction extends Action {
|
||||
|
||||
async run(): Promise<void> {
|
||||
const activeEditorPane = this.editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined;
|
||||
if (activeEditorPane?.isNotebookEditor) {
|
||||
if (activeEditorPane?.isNotebookEditor) { // TODO@rebornix TODO@jrieken debt: https://github.com/microsoft/vscode/issues/114554
|
||||
// it's inside notebook editor
|
||||
return this.commandService.executeCommand('notebook.cell.changeLanguage');
|
||||
}
|
||||
|
||||
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (!activeTextEditorControl) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1085,22 +1101,24 @@ export class ChangeModeAction extends Action {
|
||||
const languages = this.modeService.getRegisteredLanguageNames();
|
||||
const picks: QuickPickInput[] = languages.sort().map((lang, index) => {
|
||||
const modeId = this.modeService.getModeIdForLanguageName(lang.toLowerCase()) || 'unknown';
|
||||
const extensions = this.modeService.getExtensions(lang).join(' ');
|
||||
let description: string;
|
||||
if (currentLanguageId === lang) {
|
||||
description = nls.localize('languageDescription', "({0}) - Configured Language", modeId);
|
||||
description = localize('languageDescription', "({0}) - Configured Language", modeId);
|
||||
} else {
|
||||
description = nls.localize('languageDescriptionConfigured', "({0})", modeId);
|
||||
description = localize('languageDescriptionConfigured', "({0})", modeId);
|
||||
}
|
||||
|
||||
return {
|
||||
label: lang,
|
||||
meta: extensions,
|
||||
iconClasses: getIconClassesForModeId(modeId),
|
||||
description
|
||||
};
|
||||
});
|
||||
|
||||
if (hasLanguageSupport) {
|
||||
picks.unshift({ type: 'separator', label: nls.localize('languagesPicks', "languages (identifier)") });
|
||||
picks.unshift({ type: 'separator', label: localize('languagesPicks', "languages (identifier)") });
|
||||
}
|
||||
|
||||
// Offer action to configure via settings
|
||||
@@ -1115,22 +1133,22 @@ export class ChangeModeAction extends Action {
|
||||
picks.unshift(galleryAction);
|
||||
}
|
||||
|
||||
configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentLanguageId) };
|
||||
configureModeSettings = { label: localize('configureModeSettings', "Configure '{0}' language based settings...", currentLanguageId) };
|
||||
picks.unshift(configureModeSettings);
|
||||
configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
|
||||
configureModeAssociations = { label: localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
|
||||
picks.unshift(configureModeAssociations);
|
||||
}
|
||||
|
||||
// Offer to "Auto Detect"
|
||||
const autoDetectMode: IQuickPickItem = {
|
||||
label: nls.localize('autoDetect', "Auto Detect")
|
||||
label: localize('autoDetect', "Auto Detect")
|
||||
};
|
||||
|
||||
if (hasLanguageSupport) {
|
||||
picks.unshift(autoDetectMode);
|
||||
}
|
||||
|
||||
const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true });
|
||||
const pick = await this.quickInputService.pick(picks, { placeHolder: localize('pickLanguage', "Select Language Mode"), matchOnDescription: true });
|
||||
if (!pick) {
|
||||
return;
|
||||
}
|
||||
@@ -1178,6 +1196,8 @@ export class ChangeModeAction extends Action {
|
||||
modeSupport.setMode(languageSelection.languageIdentifier.language);
|
||||
}
|
||||
}
|
||||
|
||||
activeTextEditorControl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1194,12 +1214,12 @@ export class ChangeModeAction extends Action {
|
||||
id,
|
||||
label: lang,
|
||||
iconClasses: getIconClassesForModeId(id),
|
||||
description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : undefined
|
||||
description: (id === currentAssociation) ? localize('currentAssociation', "Current Association") : undefined
|
||||
};
|
||||
});
|
||||
|
||||
setTimeout(async () => {
|
||||
const language = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) });
|
||||
const language = await this.quickInputService.pick(picks, { placeHolder: localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) });
|
||||
if (language) {
|
||||
const fileAssociationsConfig = this.configurationService.inspect<{}>(FILES_ASSOCIATIONS_CONFIG);
|
||||
|
||||
@@ -1233,7 +1253,7 @@ export interface IChangeEOLEntry extends IQuickPickItem {
|
||||
export class ChangeEOLAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.editor.changeEOL';
|
||||
static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
|
||||
static readonly LABEL = localize('changeEndOfLine', "Change End of Line Sequence");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
@@ -1247,12 +1267,12 @@ export class ChangeEOLAction extends Action {
|
||||
async run(): Promise<void> {
|
||||
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (!activeTextEditorControl) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.editorService.activeEditor?.isReadonly()) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
await this.quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1265,7 +1285,7 @@ export class ChangeEOLAction extends Action {
|
||||
|
||||
const selectedIndex = (textModel?.getEOL() === '\n') ? 0 : 1;
|
||||
|
||||
const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] });
|
||||
const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] });
|
||||
if (eol) {
|
||||
const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (activeCodeEditor?.hasModel() && !this.editorService.activeEditor?.isReadonly()) {
|
||||
@@ -1275,13 +1295,15 @@ export class ChangeEOLAction extends Action {
|
||||
textModel.pushStackElement();
|
||||
}
|
||||
}
|
||||
|
||||
activeTextEditorControl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeEncodingAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.editor.changeEncoding';
|
||||
static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
|
||||
static readonly LABEL = localize('changeEncoding', "Change File Encoding");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
@@ -1296,25 +1318,26 @@ export class ChangeEncodingAction extends Action {
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (!getCodeEditor(this.editorService.activeTextEditorControl)) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (!activeTextEditorControl) {
|
||||
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
const activeEditorPane = this.editorService.activeEditorPane;
|
||||
if (!activeEditorPane) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeEditorPane.input);
|
||||
if (!encodingSupport) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
|
||||
await this.quickInputService.pick([{ label: localize('noFileEditor', "No file active at this time") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
const saveWithEncodingPick: IQuickPickItem = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
|
||||
const reopenWithEncodingPick: IQuickPickItem = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };
|
||||
const saveWithEncodingPick: IQuickPickItem = { label: localize('saveWithEncoding', "Save with Encoding") };
|
||||
const reopenWithEncodingPick: IQuickPickItem = { label: localize('reopenWithEncoding', "Reopen with Encoding") };
|
||||
|
||||
if (!Language.isDefaultVariant()) {
|
||||
const saveWithEncodingAlias = 'Save with Encoding';
|
||||
@@ -1334,7 +1357,7 @@ export class ChangeEncodingAction extends Action {
|
||||
} else if (activeEditorPane.input.isReadonly()) {
|
||||
action = reopenWithEncodingPick;
|
||||
} else {
|
||||
action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
}
|
||||
|
||||
if (!action) {
|
||||
@@ -1394,11 +1417,11 @@ export class ChangeEncodingAction extends Action {
|
||||
// If we have a guessed encoding, show it first unless it matches the configured encoding
|
||||
if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) {
|
||||
picks.unshift({ type: 'separator' });
|
||||
picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") });
|
||||
picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: localize('guessedEncoding', "Guessed from content") });
|
||||
}
|
||||
|
||||
const encoding = await this.quickInputService.pick(picks, {
|
||||
placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
|
||||
placeHolder: isReopenWithEncoding ? localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : localize('pickEncodingForSave', "Select File Encoding to Save with"),
|
||||
activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1]
|
||||
});
|
||||
|
||||
@@ -1414,5 +1437,7 @@ export class ChangeEncodingAction extends Action {
|
||||
if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) {
|
||||
activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
|
||||
}
|
||||
|
||||
activeTextEditorControl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-label {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container > .title-label {
|
||||
line-height: 35px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -22,16 +22,16 @@
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before {
|
||||
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container > .title-label > .monaco-icon-label-container {
|
||||
flex: none; /* helps to show decorations right next to the label and not at the end */
|
||||
}
|
||||
|
||||
/* Breadcrumbs */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control {
|
||||
flex: 1 50%;
|
||||
overflow: hidden;
|
||||
@@ -62,13 +62,11 @@
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder + .monaco-breadcrumb-item::before,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.relative-path .monaco-breadcrumb-item:nth-child(2)::before,
|
||||
.monaco-workbench.windows .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:nth-child(2)::before {
|
||||
/* workspace folder, item following workspace folder, or relative path -> hide first seperator */
|
||||
display: none;
|
||||
display: none; /* workspace folder, item following workspace folder, or relative path -> hide first seperator */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::after {
|
||||
/* use dot separator for workspace folder */
|
||||
content: '\00a0•\00a0';
|
||||
content: '\00a0•\00a0'; /* use dot separator for workspace folder */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -80,13 +78,18 @@
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon:last-child,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child {
|
||||
display: none; /* hides chevrons when no tabs visible and when last items */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon:last-child {
|
||||
display: none; /* hides chevrons when no tabs visible */
|
||||
}
|
||||
|
||||
/* Title Actions */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-icon-label::before {
|
||||
height: 18px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
/* Editor Actions Toolbar (via title actions) */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .title-actions {
|
||||
display: flex;
|
||||
flex: initial;
|
||||
opacity: 0.5;
|
||||
@@ -94,6 +97,6 @@
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .title-actions {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title > .title-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -3,17 +3,45 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
################################### z-index explainer ###################################
|
||||
|
||||
Tabs have various levels of z-index depending on state, typically:
|
||||
- scrollbar should be above all
|
||||
- sticky (compact, shrink) tabs need to be above non-sticky tabs for scroll under effect
|
||||
including non-sticky tabs-top borders, otherwise these borders would not scroll under
|
||||
(https://github.com/microsoft/vscode/issues/111641)
|
||||
- bottom-border needs to be above tabs bottom border to win but also support sticky tabs
|
||||
(https://github.com/microsoft/vscode/issues/99084) <- this currently cannot be done and
|
||||
is still broken. putting sticky-tabs above tabs bottom border would not render this
|
||||
border at all for sticky tabs.
|
||||
|
||||
On top of that there is 2 borders with a z-index for a general border below tabs
|
||||
- tabs bottom border
|
||||
- editor title bottom border (when breadcrumbs are disabled, this border will appear
|
||||
same as tabs bottom border)
|
||||
|
||||
The following tabls shows the current stacking order:
|
||||
|
||||
[z-index] [kind]
|
||||
7 scrollbar
|
||||
6 active-tab border-bottom
|
||||
5 tabs, title border bottom
|
||||
4 sticky-tab
|
||||
2 active/dirty-tab border top
|
||||
0 tab
|
||||
|
||||
##########################################################################################
|
||||
*/
|
||||
|
||||
/* Title Container */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container {
|
||||
display: flex;
|
||||
position: relative; /* position tabs border bottom or editor actions (when tabs wrap) relative to this container */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom::after {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.tabs-border-bottom::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
@@ -25,12 +53,12 @@
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container > .monaco-scrollable-element {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar {
|
||||
z-index: 3; /* on top of tabs */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar {
|
||||
z-index: 7;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -46,6 +74,13 @@
|
||||
overflow: scroll !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container {
|
||||
|
||||
/* Enable wrapping via flex layout and dynamic height */
|
||||
height: auto;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar {
|
||||
display: none; /* Chrome + Safari: hide scrollbar */
|
||||
}
|
||||
@@ -62,6 +97,10 @@
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab:last-child {
|
||||
margin-right: var(--last-tab-margin-right); /* when tabs wrap, we need a margin away from the absolute positioned editor actions */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-right,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-off:not(.sticky-compact) {
|
||||
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab actions is not left (unless sticky-compact) */
|
||||
@@ -74,6 +113,10 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab.sizing-fit {
|
||||
flex-grow: 1; /* grow the tabs to fill each row for a more homogeneous look when tabs wrap */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink {
|
||||
min-width: 80px;
|
||||
flex-basis: 0; /* all tabs are even */
|
||||
@@ -89,7 +132,7 @@
|
||||
|
||||
/** Sticky compact/shrink tabs do not scroll in case of overflow and are always above unsticky tabs which scroll under */
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
z-index: 4;
|
||||
|
||||
/** Sticky compact/shrink tabs are even and never grow */
|
||||
flex-basis: 0;
|
||||
@@ -118,9 +161,7 @@
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-compact,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky-shrink,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-shrink {
|
||||
|
||||
/** Disable sticky positions for sticky compact/shrink tabs if the available space is too little */
|
||||
position: static;
|
||||
position: static; /** disable sticky positions for sticky compact/shrink tabs if the available space is too little */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left .action-label {
|
||||
@@ -132,7 +173,7 @@
|
||||
content: '';
|
||||
display: flex;
|
||||
flex: 0;
|
||||
width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/microsoft/vscode/issues/45728) */
|
||||
width: 5px; /* reserve space to hide tab fade when close button is left or off (fixes https://github.com/microsoft/vscode/issues/45728) */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left {
|
||||
@@ -167,24 +208,26 @@
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 6; /* over possible title border */
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container {
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
height: 1px;
|
||||
background-color: var(--tab-border-top-color);
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container {
|
||||
z-index: 6;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
background-color: var(--tab-border-bottom-color);
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container {
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
height: 2px;
|
||||
background-color: var(--tab-dirty-border-top-color);
|
||||
@@ -203,13 +246,16 @@
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .tab-label::after {
|
||||
content: '';
|
||||
content: ''; /* enables a linear gradient to overlay the end of the label when tabs overflow */
|
||||
position: absolute;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 5px;
|
||||
opacity: 1;
|
||||
padding: 0;
|
||||
/* the rules below ensure that the gradient does not impact top/bottom borders (https://github.com/microsoft/vscode/issues/115129) */
|
||||
top: 1px;
|
||||
bottom: 1px;
|
||||
height: calc(100% - 2px);
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink:focus > .tab-label::after {
|
||||
@@ -243,7 +289,7 @@
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions {
|
||||
flex: 0;
|
||||
overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room... */
|
||||
overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions,
|
||||
@@ -301,7 +347,7 @@
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
/* No Tab Actions */
|
||||
/* Tab Actions: Off */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off {
|
||||
padding-right: 10px; /* give a little bit more room if tab actions is off */
|
||||
@@ -324,15 +370,6 @@
|
||||
pointer-events: none; /* don't allow tab actions to be clicked when running without tab actions */
|
||||
}
|
||||
|
||||
/* Editor Actions */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions {
|
||||
cursor: default;
|
||||
flex: initial;
|
||||
padding: 0 8px 0 4px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
/* Breadcrumbs */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control {
|
||||
@@ -350,11 +387,17 @@
|
||||
height: 22px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .outline-element-icon {
|
||||
padding-right: 3px;
|
||||
height: 22px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
|
||||
width: 16px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
@@ -362,6 +405,27 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child {
|
||||
display: none; /* hides chevrons when last item */
|
||||
}
|
||||
|
||||
/* Editor Actions Toolbar */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions {
|
||||
cursor: default;
|
||||
flex: initial;
|
||||
padding: 0 8px 0 4px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .editor-actions {
|
||||
|
||||
/* When tabs are wrapped, position the editor actions at the end of the very last row */
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,15 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before {
|
||||
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .monaco-icon-label::after,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs .monaco-icon-label::after {
|
||||
padding-right: 0; /* by default the icon label has a padding right and this isn't wanted when not showing tabs and not showing breadcrumbs */
|
||||
}
|
||||
|
||||
/* Title Actions */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label:not(span),
|
||||
@@ -59,13 +68,12 @@
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* Drag Cursor */
|
||||
/* Drag and Drop */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
/* Drag and Drop Feedback */
|
||||
|
||||
.monaco-editor-group-drag-image {
|
||||
display: inline-block;
|
||||
padding: 1px 7px;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import 'vs/css!./media/notabstitlecontrol';
|
||||
import { EditorResourceAccessor, Verbosity, IEditorInput, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||
import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { TitleControl, IToolbarActions, ITitleControlDimensions } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
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';
|
||||
@@ -15,6 +15,7 @@ import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/edito
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { withNullAsUndefined, assertIsDefined, assertAllDefined } from 'vs/base/common/types';
|
||||
import { IEditorGroupTitleDimensions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { equals } from 'vs/base/common/objects';
|
||||
|
||||
interface IRenderedEditorLabel {
|
||||
editor?: IEditorInput;
|
||||
@@ -50,7 +51,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
// Breadcrumbs
|
||||
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent });
|
||||
titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl));
|
||||
this._register({ dispose: () => titleContainer.classList.remove('breadcrumbs') }); // import to remove because the container is a shared dom node
|
||||
this._register({ dispose: () => titleContainer.classList.remove('breadcrumbs') }); // important to remove because the container is a shared dom node
|
||||
|
||||
// Right Actions Container
|
||||
const actionsContainer = document.createElement('div');
|
||||
@@ -67,16 +68,16 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this.enableGroupDragging(titleContainer);
|
||||
|
||||
// Pin on double click
|
||||
this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
|
||||
this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, e => this.onTitleDoubleClick(e)));
|
||||
|
||||
// Detect mouse click
|
||||
this._register(addDisposableListener(titleContainer, EventType.AUXCLICK, (e: MouseEvent) => this.onTitleAuxClick(e)));
|
||||
this._register(addDisposableListener(titleContainer, EventType.AUXCLICK, e => this.onTitleAuxClick(e)));
|
||||
|
||||
// Detect touch
|
||||
this._register(addDisposableListener(titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleTap(e)));
|
||||
|
||||
// Context Menu
|
||||
this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, (e: Event) => {
|
||||
this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, e => {
|
||||
if (this.group.activeEditor) {
|
||||
this.onContextMenu(this.group.activeEditor, e, titleContainer);
|
||||
}
|
||||
@@ -188,7 +189,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
|
||||
if (oldOptions.labelFormat !== newOptions.labelFormat) {
|
||||
if (oldOptions.labelFormat !== newOptions.labelFormat || !equals(oldOptions.decorations, newOptions.decorations)) {
|
||||
this.redraw();
|
||||
}
|
||||
}
|
||||
@@ -236,6 +237,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
|
||||
private redraw(): void {
|
||||
const editor = withNullAsUndefined(this.group.activeEditor);
|
||||
const options = this.accessor.partOptions;
|
||||
|
||||
const isEditorPinned = editor ? this.group.isPinned(editor) : false;
|
||||
const isGroupActive = this.accessor.activeGroup === this.group;
|
||||
@@ -291,14 +293,18 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
{
|
||||
title,
|
||||
italic: !isEditorPinned,
|
||||
extraClasses: ['no-tabs', 'title-label']
|
||||
extraClasses: ['no-tabs', 'title-label'],
|
||||
fileDecorations: {
|
||||
colors: Boolean(options.decorations?.colors),
|
||||
badges: Boolean(options.decorations?.badges)
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (isGroupActive) {
|
||||
editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || '';
|
||||
titleContainer.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || '';
|
||||
} else {
|
||||
editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND) || '';
|
||||
titleContainer.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND) || '';
|
||||
}
|
||||
|
||||
// Update Editor Actions Toolbar
|
||||
@@ -333,9 +339,11 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
};
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
layout(dimensions: ITitleControlDimensions): Dimension {
|
||||
if (this.breadcrumbsControl) {
|
||||
this.breadcrumbsControl.layout(undefined);
|
||||
}
|
||||
|
||||
return new Dimension(dimensions.container.width, this.getDimensions().height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
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';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { ICodeEditor, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
|
||||
export interface IRangeHighlightDecoration {
|
||||
resource: URI;
|
||||
range: IRange;
|
||||
isWholeLine?: boolean;
|
||||
}
|
||||
|
||||
export class RangeHighlightDecorations extends Disposable {
|
||||
|
||||
private rangeHighlightDecorationId: string | null = null;
|
||||
private editor: ICodeEditor | null = null;
|
||||
private readonly editorDisposables = this._register(new DisposableStore());
|
||||
|
||||
private readonly _onHighlightRemoved: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onHighlightRemoved = this._onHighlightRemoved.event;
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
removeHighlightRange() {
|
||||
if (this.editor && this.editor.getModel() && this.rangeHighlightDecorationId) {
|
||||
this.editor.deltaDecorations([this.rangeHighlightDecorationId], []);
|
||||
this._onHighlightRemoved.fire();
|
||||
}
|
||||
|
||||
this.rangeHighlightDecorationId = null;
|
||||
}
|
||||
|
||||
highlightRange(range: IRangeHighlightDecoration, editor?: any) {
|
||||
editor = editor ?? this.getEditor(range);
|
||||
if (isCodeEditor(editor)) {
|
||||
this.doHighlightRange(editor, range);
|
||||
} else if (isCompositeEditor(editor) && isCodeEditor(editor.activeCodeEditor)) {
|
||||
this.doHighlightRange(editor.activeCodeEditor, range);
|
||||
}
|
||||
}
|
||||
|
||||
private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) {
|
||||
this.removeHighlightRange();
|
||||
|
||||
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
|
||||
this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine));
|
||||
});
|
||||
|
||||
this.setEditor(editor);
|
||||
}
|
||||
|
||||
private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined {
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
const resource = activeEditor && activeEditor.resource;
|
||||
if (resource && isEqual(resource, resourceRange.resource)) {
|
||||
return this.editorService.activeTextEditorControl as ICodeEditor;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private setEditor(editor: ICodeEditor) {
|
||||
if (this.editor !== editor) {
|
||||
this.editorDisposables.clear();
|
||||
this.editor = editor;
|
||||
this.editorDisposables.add(this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
|
||||
if (
|
||||
e.reason === CursorChangeReason.NotSet
|
||||
|| e.reason === CursorChangeReason.Explicit
|
||||
|| e.reason === CursorChangeReason.Undo
|
||||
|| e.reason === CursorChangeReason.Redo
|
||||
) {
|
||||
this.removeHighlightRange();
|
||||
}
|
||||
}));
|
||||
this.editorDisposables.add(this.editor.onDidChangeModel(() => { this.removeHighlightRange(); }));
|
||||
this.editorDisposables.add(this.editor.onDidDispose(() => {
|
||||
this.removeHighlightRange();
|
||||
this.editor = null;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly _WHOLE_LINE_RANGE_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
});
|
||||
|
||||
private static readonly _RANGE_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight'
|
||||
});
|
||||
|
||||
private createRangeHighlightDecoration(isWholeLine: boolean = true): ModelDecorationOptions {
|
||||
return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
if (this.editor && this.editor.getModel()) {
|
||||
this.removeHighlightRange();
|
||||
this.editor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user