Merge pull request #140659 from microsoft/tyriar/capability_impl

Create terminal capability impl objects for progressive enhancement
This commit is contained in:
Daniel Imms
2022-01-13 11:54:48 -08:00
committed by GitHub
13 changed files with 198 additions and 80 deletions
@@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, TerminalIcon, TitleEventSource, TerminalShellType, IExtensionTerminalProfile, TerminalLocation, ProcessPropertyType, IProcessPropertyMap } from 'vs/platform/terminal/common/terminal';
import { ICommandTracker, INavigationMode, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalFont, ITerminalBackend, ITerminalProcessExtHostProxy, IRegisterContributedProfileArgs, IShellIntegration, ITerminalCapabilityStore } from 'vs/workbench/contrib/terminal/common/terminal';
import { ICommandTracker, INavigationMode, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalFont, ITerminalBackend, ITerminalProcessExtHostProxy, IRegisterContributedProfileArgs, IShellIntegration } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { IEditableData } from 'vs/workbench/common/views';
@@ -17,6 +17,7 @@ import { DeserializedTerminalEditorInput } from 'vs/workbench/contrib/terminal/b
import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput';
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
import { IKeyMods } from 'vs/platform/quickinput/common/quickInput';
import { ITerminalCapabilityStore } from 'vs/workbench/contrib/terminal/common/capabilities/capabilities';
export const ITerminalService = createDecorator<ITerminalService>('terminalService');
export const ITerminalEditorService = createDecorator<ITerminalEditorService>('terminalEditorService');
@@ -1175,7 +1175,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
protected _createProcessManager(): TerminalProcessManager {
const processManager = this._instantiationService.createInstance(TerminalProcessManager, this._instanceId, this._configHelper);
this.capabilities.addCapabilityStore(processManager.capabilities);
this.capabilities.add(processManager.capabilities);
processManager.onProcessReady(async (e) => {
this._onProcessIdReady.fire(this);
this._initialCwd = await this.getInitialCwd();
@@ -29,6 +29,7 @@ import { IProcessEnvironment, isMacintosh, isWindows, OperatingSystem, OS } from
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalCapabilityStore } from 'vs/workbench/contrib/terminal/common/capabilities/terminalCapabilityStore';
import { NaiveCwdDetectionCapability } from 'vs/workbench/contrib/terminal/common/capabilities/naiveCwdDetectionCapability';
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;
@@ -103,11 +104,6 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
private readonly _onProcessExit = this._register(new Emitter<number | undefined>());
readonly onProcessExit = this._onProcessExit.event;
private readonly _onCapabilityDisabled = new Emitter<TerminalCapability>();
readonly onCapabilityDisabled = this._onCapabilityDisabled.event;
private readonly _onCapabilityEnabled = new Emitter<TerminalCapability>();
readonly onCapabilityEnabled = this._onCapabilityEnabled.event;
get persistentProcessId(): number | undefined { return this._process?.id; }
get shouldPersist(): boolean { return this._process ? this._process.shouldPersist : false; }
get hasWrittenData(): boolean { return this._hasWrittenData; }
@@ -296,7 +292,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
// Add any capabilities inherit to the backend
if (this.os === OperatingSystem.Linux || this.os === OperatingSystem.Macintosh) {
this.capabilities.addCapability(TerminalCapability.NaiveCwdDetection);
this.capabilities.add(TerminalCapability.NaiveCwdDetection, new NaiveCwdDetectionCapability());
}
this._dataFilter.newProcess(this._process, reset);
@@ -9,6 +9,7 @@ import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { TerminalCapability } from 'vs/platform/terminal/common/terminal';
import { TerminalCapabilityStore } from 'vs/workbench/contrib/terminal/common/capabilities/terminalCapabilityStore';
import { CommandDetectionCapability } from 'vs/workbench/contrib/terminal/common/capabilities/commandDetectionCapability';
/**
* Shell integration is a feature that enhances the terminal's understanding of what's happening
@@ -104,7 +105,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
type = ShellIntegrationInteraction.CommandFinished;
break;
case ShellIntegrationOscPt.EnableShellIntegration:
this.capabilities.addCapability(TerminalCapability.CommandDetection);
this.capabilities.add(TerminalCapability.CommandDetection, new CommandDetectionCapability());
return true;
default:
return false;
@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { TerminalCapability } from 'vs/platform/terminal/common/terminal';
import { CommandDetectionCapability } from 'vs/workbench/contrib/terminal/common/capabilities/commandDetectionCapability';
import { CwdDetectionCapability } from 'vs/workbench/contrib/terminal/common/capabilities/cwdDetectionCapability';
import { NaiveCwdDetectionCapability } from 'vs/workbench/contrib/terminal/common/capabilities/naiveCwdDetectionCapability';
import { PartialCommandDetectionCapability } from 'vs/workbench/contrib/terminal/common/capabilities/partialCommandDetectionCapability';
/**
* An object that keeps track of additional capabilities and their implementations for features that
* are not available for all terminals.
*/
export interface ITerminalCapabilityStore {
/**
* An iterable of all capabilities in the store.
*/
readonly items: IterableIterator<TerminalCapability>;
/**
* Fired when a capability is added.
*/
readonly onDidAddCapability: Event<TerminalCapability>;
/**
* Fired when a capability is removed.
*/
readonly onDidRemoveCapability: Event<TerminalCapability>;
/**
* Gets whether the capability exists in the store.
*/
has(capability: TerminalCapability): boolean;
/**
* Gets the implementation of a capability if it has been added to the store.
*/
get<T extends TerminalCapability>(capability: T): ITerminalCapabilityImplMap[T] | undefined;
}
/**
* Maps capability types to their implementation, enabling strongly typed fetching of
* implementations.
*/
export interface ITerminalCapabilityImplMap {
[TerminalCapability.CwdDetection]: InstanceType<typeof CwdDetectionCapability>;
[TerminalCapability.CommandDetection]: InstanceType<typeof CommandDetectionCapability>;
[TerminalCapability.NaiveCwdDetection]: InstanceType<typeof NaiveCwdDetectionCapability>;
[TerminalCapability.PartialCommandDetection]: InstanceType<typeof PartialCommandDetectionCapability>;
}
@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TerminalCapability } from 'vs/platform/terminal/common/terminal';
export class CommandDetectionCapability {
readonly type = TerminalCapability.CommandDetection;
// TODO: Encapsulate the functionality the capability brings here
}
@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TerminalCapability } from 'vs/platform/terminal/common/terminal';
export class CwdDetectionCapability {
readonly type = TerminalCapability.CwdDetection;
// TODO: Encapsulate the functionality the capability brings here
}
@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TerminalCapability } from 'vs/platform/terminal/common/terminal';
export class NaiveCwdDetectionCapability {
readonly type = TerminalCapability.NaiveCwdDetection;
// TODO: Encapsulate the functionality the capability brings here
}
@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TerminalCapability } from 'vs/platform/terminal/common/terminal';
export class PartialCommandDetectionCapability {
readonly type = TerminalCapability.CwdDetection;
// TODO: Encapsulate the functionality the capability brings here
}
@@ -6,32 +6,40 @@
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { TerminalCapability } from 'vs/platform/terminal/common/terminal';
import { ITerminalCapabilityStore } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalCapabilityImplMap, ITerminalCapabilityStore } from 'vs/workbench/contrib/terminal/common/capabilities/capabilities';
export class TerminalCapabilityStore extends Disposable implements ITerminalCapabilityStore {
readonly items: TerminalCapability[] = [];
private _map: Map<TerminalCapability, { type: TerminalCapability }> = new Map();
private readonly _onDidRemoveCapability = this._register(new Emitter<TerminalCapability>());
readonly onDidRemoveCapability = this._onDidRemoveCapability.event;
private readonly _onDidAddCapability = this._register(new Emitter<TerminalCapability>());
readonly onDidAddCapability = this._onDidAddCapability.event;
addCapability(capability: TerminalCapability) {
this.items.push(capability);
get items(): IterableIterator<TerminalCapability> {
return this._map.keys();
}
add<T extends TerminalCapability>(capability: T, impl: ITerminalCapabilityImplMap[T]) {
this._map.set(capability, impl);
this._onDidAddCapability.fire(capability);
}
removeCapability(capability: TerminalCapability) {
const index = this.items.indexOf(capability);
if (index === -1) {
get<T extends TerminalCapability>(capability: T): ITerminalCapabilityImplMap[T] | undefined {
// HACK: This isn't totally safe since the Map key and value are not connected
return this._map.get(capability) as ITerminalCapabilityImplMap[T] | undefined;
}
remove(capability: TerminalCapability) {
if (!this._map.has(capability)) {
return;
}
this.items.splice(index, 1);
this._map.delete(capability);
this._onDidRemoveCapability.fire(capability);
}
has(capability: TerminalCapability) {
return this.items.includes(capability);
return this._map.has(capability);
}
}
@@ -43,18 +51,40 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT
private readonly _onDidAddCapability = this._register(new Emitter<TerminalCapability>());
readonly onDidAddCapability = this._onDidAddCapability.event;
get items(): readonly TerminalCapability[] {
return this._stores.reduce<TerminalCapability[]>((p, c) => {
p.push(...c.items);
return p;
}, []);
get items(): IterableIterator<TerminalCapability> {
return this._items();
}
has(capability: TerminalCapability) {
return this.items.includes(capability);
private *_items(): IterableIterator<TerminalCapability> {
for (const store of this._stores) {
for (const c of store.items) {
yield c;
}
}
}
addCapabilityStore(store: ITerminalCapabilityStore) {
has(capability: TerminalCapability): boolean {
for (const store of this._stores) {
for (const c of store.items) {
if (c === capability) {
return true;
}
}
}
return false;
}
get<T extends TerminalCapability>(capability: T): ITerminalCapabilityImplMap[T] | undefined {
for (const store of this._stores) {
const c = store.get(capability);
if (c) {
return c;
}
}
return undefined;
}
add(store: ITerminalCapabilityStore) {
this._stores.push(store);
for (const capability of store.items) {
this._onDidAddCapability.fire(capability);
@@ -8,12 +8,13 @@ import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform';
import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, IProcessProperty, TitleEventSource, ProcessPropertyType, IFixedTerminalDimensions, IExtensionTerminalProfile, ICreateContributedTerminalProfileOptions, IProcessPropertyMap, ITerminalEnvironment, TerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/terminal';
import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, IProcessProperty, TitleEventSource, ProcessPropertyType, IFixedTerminalDimensions, IExtensionTerminalProfile, ICreateContributedTerminalProfileOptions, IProcessPropertyMap, ITerminalEnvironment, TerminalCommand } from 'vs/platform/terminal/common/terminal';
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { IProcessDetails } from 'vs/platform/terminal/common/terminalProcess';
import { Registry } from 'vs/platform/registry/common/platform';
import { ITerminalCapabilityStore } from 'vs/workbench/contrib/terminal/common/capabilities/capabilities';
export const TERMINAL_VIEW_ID = 'terminal';
@@ -403,18 +404,6 @@ export interface ITerminalProcessManager extends IDisposable {
updateProperty<T extends ProcessPropertyType>(property: T, value: IProcessPropertyMap[T]): void;
}
export interface ITerminalCapabilityStore {
readonly items: readonly TerminalCapability[];
readonly onDidRemoveCapability: Event<TerminalCapability>;
readonly onDidAddCapability: Event<TerminalCapability>;
has(capability: TerminalCapability): boolean;
}
export interface ITerminalCapabilityStoreController {
addCapability(capability: TerminalCapability): void;
removeCapability(capability: TerminalCapability): void;
}
export const enum ProcessState {
// The process has not been initialized yet.
Uninitialized = 1,
@@ -22,7 +22,7 @@ import { TerminalCapabilityStore } from 'vs/workbench/contrib/terminal/common/ca
function createInstance(partial?: Partial<ITerminalInstance>): Pick<ITerminalInstance, 'shellLaunchConfig' | 'userHome' | 'cwd' | 'initialCwd' | 'processName' | 'sequence' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'> {
const capabilities = new TerminalCapabilityStore();
if (!isWindows) {
capabilities.addCapability(TerminalCapability.NaiveCwdDetection);
capabilities.add(TerminalCapability.NaiveCwdDetection, null!);
}
return {
shellLaunchConfig: {},
@@ -169,7 +169,7 @@ suite('Workbench - TerminalInstance', () => {
instantiationService.stub(IWorkspaceContextService, new TestContextService());
capabilities = new TerminalCapabilityStore();
if (!isWindows) {
capabilities.addCapability(TerminalCapability.NaiveCwdDetection);
capabilities.add(TerminalCapability.NaiveCwdDetection, null!);
}
const ROOT_1_URI = getUri(ROOT_1);
@@ -24,31 +24,31 @@ suite('TerminalCapabilityStore', () => {
test('should fire events when capabilities are added', () => {
assertEvents(addEvents, []);
store.addCapability(TerminalCapability.CwdDetection);
store.add(TerminalCapability.CwdDetection, null!);
assertEvents(addEvents, [TerminalCapability.CwdDetection]);
});
test('should fire events when capabilities are removed', async () => {
assertEvents(removeEvents, []);
store.addCapability(TerminalCapability.CwdDetection);
store.add(TerminalCapability.CwdDetection, null!);
assertEvents(removeEvents, []);
store.removeCapability(TerminalCapability.CwdDetection);
store.remove(TerminalCapability.CwdDetection);
assertEvents(removeEvents, [TerminalCapability.CwdDetection]);
});
test('has should return whether a capability is present', () => {
deepStrictEqual(store.has(TerminalCapability.CwdDetection), false);
store.addCapability(TerminalCapability.CwdDetection);
store.add(TerminalCapability.CwdDetection, null!);
deepStrictEqual(store.has(TerminalCapability.CwdDetection), true);
store.removeCapability(TerminalCapability.CwdDetection);
store.remove(TerminalCapability.CwdDetection);
deepStrictEqual(store.has(TerminalCapability.CwdDetection), false);
});
test('items should reflect current state', () => {
deepStrictEqual(store.items, []);
store.addCapability(TerminalCapability.CwdDetection);
deepStrictEqual(store.items, [TerminalCapability.CwdDetection]);
store.addCapability(TerminalCapability.NaiveCwdDetection);
deepStrictEqual(store.items, [TerminalCapability.CwdDetection, TerminalCapability.NaiveCwdDetection]);
store.removeCapability(TerminalCapability.CwdDetection);
deepStrictEqual(store.items, [TerminalCapability.NaiveCwdDetection]);
deepStrictEqual(Array.from(store.items), []);
store.add(TerminalCapability.CwdDetection, null!);
deepStrictEqual(Array.from(store.items), [TerminalCapability.CwdDetection]);
store.add(TerminalCapability.NaiveCwdDetection, null!);
deepStrictEqual(Array.from(store.items), [TerminalCapability.CwdDetection, TerminalCapability.NaiveCwdDetection]);
store.remove(TerminalCapability.CwdDetection);
deepStrictEqual(Array.from(store.items), [TerminalCapability.NaiveCwdDetection]);
});
});
@@ -73,52 +73,52 @@ suite('TerminalCapabilityStoreMultiplexer', () => {
test('should fire events when capabilities are enabled', async () => {
assertEvents(addEvents, []);
multiplexer.addCapabilityStore(store1);
multiplexer.addCapabilityStore(store2);
store1.addCapability(TerminalCapability.CwdDetection);
multiplexer.add(store1);
multiplexer.add(store2);
store1.add(TerminalCapability.CwdDetection, null!);
assertEvents(addEvents, [TerminalCapability.CwdDetection]);
store2.addCapability(TerminalCapability.NaiveCwdDetection);
store2.add(TerminalCapability.NaiveCwdDetection, null!);
assertEvents(addEvents, [TerminalCapability.NaiveCwdDetection]);
});
test('should fire events when capabilities are disabled', async () => {
assertEvents(removeEvents, []);
multiplexer.addCapabilityStore(store1);
multiplexer.addCapabilityStore(store2);
store1.addCapability(TerminalCapability.CwdDetection);
store2.addCapability(TerminalCapability.NaiveCwdDetection);
multiplexer.add(store1);
multiplexer.add(store2);
store1.add(TerminalCapability.CwdDetection, null!);
store2.add(TerminalCapability.NaiveCwdDetection, null!);
assertEvents(removeEvents, []);
store1.removeCapability(TerminalCapability.CwdDetection);
store1.remove(TerminalCapability.CwdDetection);
assertEvents(removeEvents, [TerminalCapability.CwdDetection]);
store2.removeCapability(TerminalCapability.NaiveCwdDetection);
store2.remove(TerminalCapability.NaiveCwdDetection);
assertEvents(removeEvents, [TerminalCapability.NaiveCwdDetection]);
});
test('should fire events when stores are added', async () => {
assertEvents(addEvents, []);
store1.addCapability(TerminalCapability.CwdDetection);
store1.add(TerminalCapability.CwdDetection, null!);
assertEvents(addEvents, []);
store2.addCapability(TerminalCapability.NaiveCwdDetection);
multiplexer.addCapabilityStore(store1);
multiplexer.addCapabilityStore(store2);
store2.add(TerminalCapability.NaiveCwdDetection, null!);
multiplexer.add(store1);
multiplexer.add(store2);
assertEvents(addEvents, [TerminalCapability.CwdDetection, TerminalCapability.NaiveCwdDetection]);
});
test('items should return items from all stores', () => {
deepStrictEqual(multiplexer.items, []);
multiplexer.addCapabilityStore(store1);
multiplexer.addCapabilityStore(store2);
store1.addCapability(TerminalCapability.CwdDetection);
deepStrictEqual(multiplexer.items, [TerminalCapability.CwdDetection]);
store1.addCapability(TerminalCapability.CommandDetection);
store2.addCapability(TerminalCapability.NaiveCwdDetection);
deepStrictEqual(multiplexer.items, [TerminalCapability.CwdDetection, TerminalCapability.CommandDetection, TerminalCapability.NaiveCwdDetection]);
store2.removeCapability(TerminalCapability.NaiveCwdDetection);
deepStrictEqual(multiplexer.items, [TerminalCapability.CwdDetection, TerminalCapability.CommandDetection]);
deepStrictEqual(Array.from(multiplexer.items).sort(), [].sort());
multiplexer.add(store1);
multiplexer.add(store2);
store1.add(TerminalCapability.CwdDetection, null!);
deepStrictEqual(Array.from(multiplexer.items).sort(), [TerminalCapability.CwdDetection].sort());
store1.add(TerminalCapability.CommandDetection, null!);
store2.add(TerminalCapability.NaiveCwdDetection, null!);
deepStrictEqual(Array.from(multiplexer.items).sort(), [TerminalCapability.CwdDetection, TerminalCapability.CommandDetection, TerminalCapability.NaiveCwdDetection].sort());
store2.remove(TerminalCapability.NaiveCwdDetection);
deepStrictEqual(Array.from(multiplexer.items).sort(), [TerminalCapability.CwdDetection, TerminalCapability.CommandDetection].sort());
});
test('has should return whether a capability is present', () => {
deepStrictEqual(multiplexer.has(TerminalCapability.CwdDetection), false);
multiplexer.addCapabilityStore(store1);
store1.addCapability(TerminalCapability.CwdDetection);
multiplexer.add(store1);
store1.add(TerminalCapability.CwdDetection, null!);
deepStrictEqual(multiplexer.has(TerminalCapability.CwdDetection), true);
store1.removeCapability(TerminalCapability.CwdDetection);
store1.remove(TerminalCapability.CwdDetection);
deepStrictEqual(multiplexer.has(TerminalCapability.CwdDetection), false);
});
});