diff options
Diffstat (limited to 'src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts')
-rw-r--r-- | src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts | 190 |
1 files changed, 120 insertions, 70 deletions
diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 7cfab363b6d..22c53792466 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -3,58 +3,52 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'vs/base/common/path'; -import * as nls from 'vs/nls'; -import * as Objects from 'vs/base/common/objects'; -import * as Types from 'vs/base/common/types'; -import * as Platform from 'vs/base/common/platform'; import * as Async from 'vs/base/common/async'; -import * as resources from 'vs/base/common/resources'; import { IStringDictionary } from 'vs/base/common/collections'; +import { Emitter, Event } from 'vs/base/common/event'; +import { isUNC } from 'vs/base/common/extpath'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { LinkedMap, Touch } from 'vs/base/common/map'; +import * as Objects from 'vs/base/common/objects'; +import * as path from 'vs/base/common/path'; +import * as Platform from 'vs/base/common/platform'; +import * as resources from 'vs/base/common/resources'; import Severity from 'vs/base/common/severity'; -import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { isUNC } from 'vs/base/common/extpath'; +import * as Types from 'vs/base/common/types'; +import * as nls from 'vs/nls'; +import { IModelService } from 'vs/editor/common/services/model'; import { IFileService } from 'vs/platform/files/common/files'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/contrib/tasks/common/problemMatcher'; +import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { Markers } from 'vs/workbench/contrib/markers/common/markers'; +import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { ITerminalProfileResolverService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; -import { ITerminalService, ITerminalInstance, ITerminalGroupService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { IOutputService } from 'vs/workbench/services/output/common/output'; -import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors'; -import { - Task, CustomTask, ContributedTask, RevealKind, CommandOptions, IShellConfiguration, RuntimeType, PanelKind, - TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent, TaskSettingId -} from 'vs/workbench/contrib/tasks/common/tasks'; -import { - ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver, - Triggers, ITaskTerminateResponse, ITaskSystemInfoResolver, ITaskSystemInfo, IResolveSet, IResolvedVariables -} from 'vs/workbench/contrib/tasks/common/taskSystem'; -import { URI } from 'vs/base/common/uri'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Codicon } from 'vs/base/common/codicons'; import { Schemas } from 'vs/base/common/network'; -import { IPathService } from 'vs/workbench/services/path/common/pathService'; -import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; -import { ILogService } from 'vs/platform/log/common/log'; +import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy'; -import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; -import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; -import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; +import { ProblemCollectorEventKind, ProblemHandlingStrategy, StartStopProblemCollector, WatchingProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; -import { Codicon } from 'vs/base/common/codicons'; +import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IExtensionTaskSource, InMemoryTask, IShellConfiguration, IShellQuotingOptions, ITaskEvent, PanelKind, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, Task, TaskEvent, TaskEventKind, TaskScope, TaskSettingId, TaskSourceKind } from 'vs/workbench/contrib/tasks/common/tasks'; +import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; +import { IResolvedVariables, IResolveSet, ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskSystemInfoResolver, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers } from 'vs/workbench/contrib/tasks/common/taskSystem'; +import { ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { VSCodeOscProperty, VSCodeOscPt, VSCodeSequence } from 'vs/workbench/contrib/terminal/browser/terminalEscapeSequences'; +import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy'; +import { ITerminalProfileResolverService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IOutputService } from 'vs/workbench/services/output/common/output'; +import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; interface ITerminalData { terminal: ITerminalInstance; @@ -69,6 +63,8 @@ interface IActiveTerminalData { state?: TaskEventKind; } +const ReconnectionType = 'Task'; + class InstanceManager { private _currentInstances: number = 0; private _counter: number = 0; @@ -196,6 +192,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { private _terminals: IStringDictionary<ITerminalData>; private _idleTaskTerminals: LinkedMap<string, string>; private _sameTaskTerminals: IStringDictionary<string>; + private _terminalForTask: ITerminalInstance | undefined; private _taskSystemInfoResolver: ITaskSystemInfoResolver; private _lastTask: VerifiedTask | undefined; // Should always be set in run @@ -205,7 +202,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { private _previousTerminalInstance: ITerminalInstance | undefined; private _terminalStatusManager: TaskTerminalStatus; private _terminalCreationQueue: Promise<ITerminalInstance | void> = Promise.resolve(); - + private _hasReconnected: boolean = false; + private _tasksToReconnect: string[] = []; private readonly _onDidStateChange: Emitter<ITaskEvent>; get taskShellIntegrationStartSequence(): string { @@ -245,12 +243,24 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._terminals = Object.create(null); this._idleTaskTerminals = new LinkedMap<string, string>(); this._sameTaskTerminals = Object.create(null); - this._onDidStateChange = new Emitter(); this._taskSystemInfoResolver = taskSystemInfoResolver; this._register(this._terminalStatusManager = new TaskTerminalStatus(taskService)); } + private _reconnectToTerminals(terminals: ITerminalInstance[]): void { + for (const terminal of terminals) { + const taskForTerminal = terminal.shellLaunchConfig.attachPersistentProcess?.task; + if (taskForTerminal?.id && taskForTerminal?.lastTask) { + this._tasksToReconnect.push(taskForTerminal.id); + this._terminals[terminal.instanceId] = { terminal, lastTask: taskForTerminal.lastTask, group: taskForTerminal.group }; + } else { + this._logService.trace(`Could not reconnect to terminal ${terminal.instanceId} with process details ${terminal.shellLaunchConfig.attachPersistentProcess}`); + } + } + this._hasReconnected = true; + } + public get onDidStateChange(): Event<ITaskEvent> { return this._onDidStateChange.event; } @@ -263,6 +273,22 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._outputService.showChannel(this._outputChannelId, true); } + public reconnect(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult | undefined { + const terminals = this._terminalService.getReconnectedTerminals(ReconnectionType); + if (!terminals || terminals?.length === 0) { + return; + } + if (!this._hasReconnected && terminals && terminals.length > 0) { + this._reviveTerminals(); + this._reconnectToTerminals(terminals); + } + if (this._tasksToReconnect.includes(task._id)) { + this._terminalForTask = terminals.find(t => t.shellLaunchConfig.attachPersistentProcess?.task?.id === task._id); + this.run(task, resolver, trigger); + } + return undefined; + } + public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult { task = task.clone(); // A small amount of task state is stored in the task (instance) and tasks passed in to run may have that set already. const recentTaskKey = task.getRecentlyUsedKey() ?? ''; @@ -280,7 +306,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } try { - const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this._executeTask(task, resolver, trigger, new Set()) }; + const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this._executeTask(task, resolver, trigger, new Set(), undefined) }; executeResult.promise.then(summary => { this._lastTask = this._currentTask; }); @@ -430,12 +456,14 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } } - private _removeFromActiveTasks(task: Task): void { - if (!this._activeTasks[task.getMapKey()]) { + private _removeFromActiveTasks(task: Task | string): void { + const key = typeof task === 'string' ? task : task.getMapKey(); + if (!this._activeTasks[key]) { return; } - delete this._activeTasks[task.getMapKey()]; - this._removeInstances(task); + const taskToRemove = this._activeTasks[key]; + delete this._activeTasks[key]; + this._removeInstances(taskToRemove.task); } private _fireTaskEvent(event: ITaskEvent) { @@ -853,7 +881,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { })); watchingProblemMatcher.aboutToStart(); let delayer: Async.Delayer<any> | undefined = undefined; - [terminal, error] = await this._createTerminal(task, resolver, workspaceFolder); + [terminal, error] = this._terminalForTask ? [this._terminalForTask, undefined] : await this._createTerminal(task, resolver, workspaceFolder); + this._terminalForTask = undefined; if (error) { return Promise.reject(new Error((<TaskError>error).message)); @@ -935,7 +964,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }); }); } else { - [terminal, error] = await this._createTerminal(task, resolver, workspaceFolder); + [terminal, error] = this._terminalForTask ? [this._terminalForTask, undefined] : await this._createTerminal(task, resolver, workspaceFolder); + this._terminalForTask = undefined; if (error) { return Promise.reject(new Error((<TaskError>error).message)); @@ -1040,7 +1070,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { const isShellCommand = task.command.runtime === RuntimeType.Shell; const needsFolderQualification = this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; const terminalName = this._createTerminalName(task); - const type = 'Task'; + const type = ReconnectionType; const originalCommand = task.command.name; if (isShellCommand) { let os: Platform.OperatingSystem; @@ -1269,6 +1299,40 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return createdTerminal; } + private _reviveTerminals(): void { + if (Object.entries(this._terminals).length > 0) { + return; + } + const terminals = this._terminalService.getReconnectedTerminals(ReconnectionType)?.filter(t => !t.isDisposed); + if (!terminals?.length) { + return; + } + for (const terminal of terminals) { + const task = terminal.shellLaunchConfig.attachPersistentProcess?.task; + if (!task) { + continue; + } + const terminalData = { lastTask: task.lastTask, group: task.group, terminal }; + this._terminals[terminal.instanceId] = terminalData; + terminal.onDisposed(() => this._deleteTaskAndTerminal(terminal, terminalData)); + } + } + + private _deleteTaskAndTerminal(terminal: ITerminalInstance, terminalData: ITerminalData): void { + delete this._terminals[terminal.instanceId]; + delete this._sameTaskTerminals[terminalData.lastTask]; + this._idleTaskTerminals.delete(terminalData.lastTask); + // Delete the task now as a work around for cases when the onExit isn't fired. + // This can happen if the terminal wasn't shutdown with an "immediate" flag and is expected. + // For correct terminal re-use, the task needs to be deleted immediately. + // Note that this shouldn't be a problem anymore since user initiated terminal kills are now immediate. + const mapKey = terminalData.lastTask; + this._removeFromActiveTasks(mapKey); + if (this._busyTasks[mapKey]) { + delete this._busyTasks[mapKey]; + } + } + private async _createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver, workspaceFolder: IWorkspaceFolder | undefined): Promise<[ITerminalInstance | undefined, TaskError | undefined]> { const platform = resolver.taskSystemInfo ? resolver.taskSystemInfo.platform : Platform.platform; const options = await this._resolveOptions(resolver, task.command.options); @@ -1308,7 +1372,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }, 'Executing task: {0}', task._label), { excludeLeadingNewLine: true }) : undefined, isFeatureTerminal: true, icon: task.configurationProperties.icon?.id ? ThemeIcon.fromId(task.configurationProperties.icon.id) : undefined, - color: task.configurationProperties.icon?.color || undefined, + color: task.configurationProperties.icon?.color || undefined }; } else { const resolvedResult: { command: CommandString; args: CommandString[] } = await this._resolveCommandAndArgs(resolver, task.command); @@ -1368,28 +1432,14 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } this._terminalCreationQueue = this._terminalCreationQueue.then(() => this._doCreateTerminal(group, launchConfigs!)); - const result: ITerminalInstance = (await this._terminalCreationQueue)!; - - const terminalKey = result.instanceId.toString(); - result.onDisposed((terminal) => { - const terminalData = this._terminals[terminalKey]; - if (terminalData) { - delete this._terminals[terminalKey]; - delete this._sameTaskTerminals[terminalData.lastTask]; - this._idleTaskTerminals.delete(terminalData.lastTask); - // Delete the task now as a work around for cases when the onExit isn't fired. - // This can happen if the terminal wasn't shutdown with an "immediate" flag and is expected. - // For correct terminal re-use, the task needs to be deleted immediately. - // Note that this shouldn't be a problem anymore since user initiated terminal kills are now immediate. - const mapKey = task.getMapKey(); - this._removeFromActiveTasks(task); - if (this._busyTasks[mapKey]) { - delete this._busyTasks[mapKey]; - } - } - }); - this._terminals[terminalKey] = { terminal: result, lastTask: taskKey, group }; - return [result, undefined]; + const terminal: ITerminalInstance = (await this._terminalCreationQueue)!; + terminal.shellLaunchConfig.task = { lastTask: taskKey, group, label: task._label, id: task._id }; + terminal.shellLaunchConfig.reconnectionOwner = ReconnectionType; + const terminalKey = terminal.instanceId.toString(); + const terminalData = { terminal: terminal, lastTask: taskKey, group }; + terminal.onDisposed(() => this._deleteTaskAndTerminal(terminal, terminalData)); + this._terminals[terminalKey] = terminalData; + return [terminal, undefined]; } private _buildShellCommandLine(platform: Platform.Platform, shellExecutable: string, shellOptions: IShellConfiguration | undefined, command: CommandString, originalCommand: CommandString | undefined, args: CommandString[]): string { |