mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-02 08:15:56 +01:00
Add preserveTerminalName task presentation option to control terminal name preservation (#268350)
This commit is contained in:
11
.vscode/tasks.json
vendored
11
.vscode/tasks.json
vendored
@@ -1,6 +1,17 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Testing Task Name",
|
||||
"type": "shell",
|
||||
"command": "sleep 3s && echo 123",
|
||||
"presentation": {
|
||||
"close": false,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": false,
|
||||
},
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch-clientd",
|
||||
|
||||
@@ -657,6 +657,12 @@ export interface IShellLaunchConfig {
|
||||
* This allows extensions to control shell integration for terminals they create.
|
||||
*/
|
||||
shellIntegrationNonce?: string;
|
||||
|
||||
/**
|
||||
* For task terminals, controls whether to preserve the task name after task completion.
|
||||
* When true, prevents process title changes from overriding the task name.
|
||||
*/
|
||||
preserveTaskName?: boolean;
|
||||
}
|
||||
|
||||
export interface ITerminalTabAction {
|
||||
|
||||
@@ -1162,7 +1162,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
|
||||
return needsFolderQualification ? task.getQualifiedLabel() : (task.configurationProperties.name || '');
|
||||
}
|
||||
|
||||
private async _createShellLaunchConfig(task: CustomTask | ContributedTask, workspaceFolder: IWorkspaceFolder | undefined, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: WaitOnExitValue): Promise<IShellLaunchConfig | undefined> {
|
||||
private async _createShellLaunchConfig(task: CustomTask | ContributedTask, workspaceFolder: IWorkspaceFolder | undefined, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: WaitOnExitValue, presentationOptions: IPresentationOptions): Promise<IShellLaunchConfig | undefined> {
|
||||
let shellLaunchConfig: IShellLaunchConfig;
|
||||
const isShellCommand = task.command.runtime === RuntimeType.Shell;
|
||||
const needsFolderQualification = this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
|
||||
@@ -1365,6 +1365,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
|
||||
shellLaunchConfig.isFeatureTerminal = true;
|
||||
shellLaunchConfig.useShellEnvironment = true;
|
||||
shellLaunchConfig.tabActions = this._terminalTabActions;
|
||||
shellLaunchConfig.preserveTaskName = presentationOptions.preserveTerminalName;
|
||||
return shellLaunchConfig;
|
||||
}
|
||||
|
||||
@@ -1495,14 +1496,15 @@ 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,
|
||||
preserveTaskName: presentationOptions.preserveTerminalName
|
||||
};
|
||||
} else {
|
||||
const resolvedResult: { command: CommandString; args: CommandString[] } = await this._resolveCommandAndArgs(resolver, task.command);
|
||||
command = resolvedResult.command;
|
||||
args = resolvedResult.args;
|
||||
|
||||
this._currentTask.shellLaunchConfig = launchConfigs = await this._createShellLaunchConfig(task, workspaceFolder, resolver, platform, options, command, args, waitOnExit);
|
||||
this._currentTask.shellLaunchConfig = launchConfigs = await this._createShellLaunchConfig(task, workspaceFolder, resolver, platform, options, command, args, waitOnExit, presentationOptions);
|
||||
if (launchConfigs === undefined) {
|
||||
return [undefined, new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive using cmd.exe.'), TaskErrors.UnknownError)];
|
||||
}
|
||||
|
||||
@@ -197,6 +197,11 @@ const presentation: IJSONSchema = {
|
||||
close: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('JsonSchema.tasks.presentation.close', 'Controls whether the terminal the task runs in is closed when the task exits.')
|
||||
},
|
||||
preserveTerminalName: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: nls.localize('JsonSchema.tasks.presentation.preserveTerminalName', 'Controls whether to preserve the task name in the terminal after task completion.')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -136,6 +136,11 @@ export interface IPresentationOptionsConfig {
|
||||
* Note that if the terminal process exits with a non-zero exit code, it will not close.
|
||||
*/
|
||||
close?: boolean;
|
||||
|
||||
/**
|
||||
* Controls whether to preserve the task name in the terminal after task completion.
|
||||
*/
|
||||
preserveTerminalName?: boolean;
|
||||
}
|
||||
|
||||
export interface IRunOptionsConfig {
|
||||
@@ -879,7 +884,7 @@ namespace CommandOptions {
|
||||
namespace CommandConfiguration {
|
||||
|
||||
export namespace PresentationOptions {
|
||||
const properties: IMetaData<Tasks.IPresentationOptions, void>[] = [{ property: 'echo' }, { property: 'reveal' }, { property: 'revealProblems' }, { property: 'focus' }, { property: 'panel' }, { property: 'showReuseMessage' }, { property: 'clear' }, { property: 'group' }, { property: 'close' }];
|
||||
const properties: IMetaData<Tasks.IPresentationOptions, void>[] = [{ property: 'echo' }, { property: 'reveal' }, { property: 'revealProblems' }, { property: 'focus' }, { property: 'panel' }, { property: 'showReuseMessage' }, { property: 'clear' }, { property: 'group' }, { property: 'close' }, { property: 'preserveTerminalName' }];
|
||||
|
||||
interface IPresentationOptionsShape extends ILegacyCommandProperties {
|
||||
presentation?: IPresentationOptionsConfig;
|
||||
@@ -895,6 +900,7 @@ namespace CommandConfiguration {
|
||||
let clear: boolean;
|
||||
let group: string | undefined;
|
||||
let close: boolean | undefined;
|
||||
let preserveTerminalName: boolean | undefined;
|
||||
let hasProps = false;
|
||||
if (Types.isBoolean(config.echoCommand)) {
|
||||
echo = config.echoCommand;
|
||||
@@ -933,12 +939,15 @@ namespace CommandConfiguration {
|
||||
if (Types.isBoolean(presentation.close)) {
|
||||
close = presentation.close;
|
||||
}
|
||||
if (Types.isBoolean(presentation.preserveTerminalName)) {
|
||||
preserveTerminalName = presentation.preserveTerminalName;
|
||||
}
|
||||
hasProps = true;
|
||||
}
|
||||
if (!hasProps) {
|
||||
return undefined;
|
||||
}
|
||||
return { echo: echo!, reveal: reveal!, revealProblems: revealProblems!, focus: focus!, panel: panel!, showReuseMessage: showReuseMessage!, clear: clear!, group, close: close };
|
||||
return { echo: echo!, reveal: reveal!, revealProblems: revealProblems!, focus: focus!, panel: panel!, showReuseMessage: showReuseMessage!, clear: clear!, group, close: close, preserveTerminalName };
|
||||
}
|
||||
|
||||
export function assignProperties(target: Tasks.IPresentationOptions, source: Tasks.IPresentationOptions | undefined): Tasks.IPresentationOptions | undefined {
|
||||
@@ -951,7 +960,7 @@ namespace CommandConfiguration {
|
||||
|
||||
export function fillDefaults(value: Tasks.IPresentationOptions, context: IParseContext): Tasks.IPresentationOptions | undefined {
|
||||
const defaultEcho = context.engine === Tasks.ExecutionEngine.Terminal ? true : false;
|
||||
return _fillDefaults(value, { echo: defaultEcho, reveal: Tasks.RevealKind.Always, revealProblems: Tasks.RevealProblemKind.Never, focus: false, panel: Tasks.PanelKind.Shared, showReuseMessage: true, clear: false }, properties, context);
|
||||
return _fillDefaults(value, { echo: defaultEcho, reveal: Tasks.RevealKind.Always, revealProblems: Tasks.RevealProblemKind.Never, focus: false, panel: Tasks.PanelKind.Shared, showReuseMessage: true, clear: false, preserveTerminalName: false }, properties, context);
|
||||
}
|
||||
|
||||
export function freeze(value: Tasks.IPresentationOptions): Readonly<Tasks.IPresentationOptions> | undefined {
|
||||
|
||||
@@ -279,11 +279,16 @@ export interface IPresentationOptions {
|
||||
* Controls whether the terminal that the task runs in is closed when the task completes.
|
||||
*/
|
||||
close?: boolean;
|
||||
|
||||
/**
|
||||
* Controls whether to preserve the task name in the terminal after task completion.
|
||||
*/
|
||||
preserveTerminalName?: boolean;
|
||||
}
|
||||
|
||||
export namespace PresentationOptions {
|
||||
export const defaults: IPresentationOptions = {
|
||||
echo: true, reveal: RevealKind.Always, revealProblems: RevealProblemKind.Never, focus: false, panel: PanelKind.Shared, showReuseMessage: true, clear: false
|
||||
echo: true, reveal: RevealKind.Always, revealProblems: RevealProblemKind.Never, focus: false, panel: PanelKind.Shared, showReuseMessage: true, clear: false, preserveTerminalName: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2256,6 +2256,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
}
|
||||
|
||||
private _setTitle(title: string | undefined, eventSource: TitleEventSource): void {
|
||||
if (this._shellLaunchConfig?.type === 'Task' &&
|
||||
(this._shellLaunchConfig?.preserveTaskName || this._titleSource === TitleEventSource.Api) &&
|
||||
eventSource === TitleEventSource.Process) {
|
||||
// For task terminals with preserveTaskName enabled or with API-set titles, preserve the title even when the process title changes
|
||||
return;
|
||||
}
|
||||
|
||||
const reset = !title;
|
||||
title = this._updateTitleProperties(title, eventSource);
|
||||
const titleChanged = title !== this._title;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { TestConfigurationService } from '../../../../../platform/configuration/
|
||||
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
|
||||
import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js';
|
||||
import { TerminalCapabilityStore } from '../../../../../platform/terminal/common/capabilities/terminalCapabilityStore.js';
|
||||
import { GeneralShellType, ITerminalChildProcess, ITerminalProfile } from '../../../../../platform/terminal/common/terminal.js';
|
||||
import { GeneralShellType, ITerminalChildProcess, ITerminalProfile, TitleEventSource } from '../../../../../platform/terminal/common/terminal.js';
|
||||
import { IWorkspaceFolder } from '../../../../../platform/workspace/common/workspace.js';
|
||||
import { IViewDescriptorService } from '../../../../common/views.js';
|
||||
import { ITerminalConfigurationService, ITerminalInstance, ITerminalInstanceService } from '../../browser/terminal.js';
|
||||
@@ -150,6 +150,92 @@ suite('Workbench - TerminalInstance', () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
deepStrictEqual(terminalInstance.shellLaunchConfig.env, { TEST: 'TEST' });
|
||||
});
|
||||
|
||||
test('should preserve title for task terminals when preserveTaskName is enabled', async () => {
|
||||
const instantiationService = workbenchInstantiationService({
|
||||
configurationService: () => new TestConfigurationService({
|
||||
files: {},
|
||||
terminal: {
|
||||
integrated: {
|
||||
fontFamily: 'monospace',
|
||||
scrollback: 1000,
|
||||
fastScrollSensitivity: 2,
|
||||
mouseWheelScrollSensitivity: 1,
|
||||
unicodeVersion: '6',
|
||||
shellIntegration: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}, store);
|
||||
instantiationService.set(ITerminalProfileResolverService, new MockTerminalProfileResolverService());
|
||||
instantiationService.stub(IViewDescriptorService, new TestViewDescriptorService());
|
||||
instantiationService.stub(IEnvironmentVariableService, store.add(instantiationService.createInstance(EnvironmentVariableService)));
|
||||
instantiationService.stub(ITerminalInstanceService, store.add(new TestTerminalInstanceService()));
|
||||
|
||||
// Create a task terminal with type 'Task' and preserveTaskName enabled
|
||||
const taskTerminal = store.add(instantiationService.createInstance(TerminalInstance, terminalShellTypeContextKey, {
|
||||
type: 'Task',
|
||||
name: 'Test Task Name',
|
||||
preserveTaskName: true
|
||||
}));
|
||||
|
||||
// Wait for initialization
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Simulate setting the title via API (as the task system would do)
|
||||
await taskTerminal.rename('Test Task Name');
|
||||
strictEqual(taskTerminal.title, 'Test Task Name');
|
||||
|
||||
// Simulate a process title change (which happens when task completes)
|
||||
(taskTerminal as any)._setTitle('some-process-name', TitleEventSource.Process);
|
||||
|
||||
// Verify that the task name is preserved
|
||||
strictEqual(taskTerminal.title, 'Test Task Name', 'Task terminal should preserve API-set title when preserveTaskName is enabled');
|
||||
});
|
||||
|
||||
test('should allow process title changes for non-task terminals', async () => {
|
||||
const instantiationService = workbenchInstantiationService({
|
||||
configurationService: () => new TestConfigurationService({
|
||||
files: {},
|
||||
terminal: {
|
||||
integrated: {
|
||||
fontFamily: 'monospace',
|
||||
scrollback: 1000,
|
||||
fastScrollSensitivity: 2,
|
||||
mouseWheelScrollSensitivity: 1,
|
||||
unicodeVersion: '6',
|
||||
shellIntegration: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}, store);
|
||||
instantiationService.set(ITerminalProfileResolverService, new MockTerminalProfileResolverService());
|
||||
instantiationService.stub(IViewDescriptorService, new TestViewDescriptorService());
|
||||
instantiationService.stub(IEnvironmentVariableService, store.add(instantiationService.createInstance(EnvironmentVariableService)));
|
||||
instantiationService.stub(ITerminalInstanceService, store.add(new TestTerminalInstanceService()));
|
||||
|
||||
// Create a regular terminal (not a task terminal)
|
||||
const regularTerminal = store.add(instantiationService.createInstance(TerminalInstance, terminalShellTypeContextKey, {
|
||||
name: 'Regular Terminal'
|
||||
}));
|
||||
|
||||
// Wait for initialization
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Simulate setting the title via API
|
||||
await regularTerminal.rename('Regular Terminal');
|
||||
strictEqual(regularTerminal.title, 'Regular Terminal');
|
||||
|
||||
// Simulate a process title change
|
||||
(regularTerminal as any)._setTitle('bash', TitleEventSource.Process);
|
||||
|
||||
// Verify that the title was changed (regular terminals should allow process title changes)
|
||||
strictEqual(regularTerminal.title, 'bash', 'Regular terminal should allow process title changes to override API-set title');
|
||||
});
|
||||
});
|
||||
suite('parseExitResult', () => {
|
||||
test('should return no message for exit code = undefined', () => {
|
||||
|
||||
Reference in New Issue
Block a user