提交 80e66c2d 编写于 作者: D Daniel Imms

Add confirmOnKill setting, handle prompt special cases

上级 177c2110
......@@ -67,6 +67,7 @@ export const enum TerminalSettingId {
RightClickBehavior = 'terminal.integrated.rightClickBehavior',
Cwd = 'terminal.integrated.cwd',
ConfirmOnExit = 'terminal.integrated.confirmOnExit',
ConfirmOnKill = 'terminal.integrated.confirmOnKill',
EnableBell = 'terminal.integrated.enableBell',
CommandsToSkipShell = 'terminal.integrated.commandsToSkipShell',
AllowChords = 'terminal.integrated.allowChords',
......
......@@ -8,18 +8,26 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { listProcesses } from 'vs/base/node/ps';
import { ILogService } from 'vs/platform/log/common/log';
import { ProcessItem } from 'vs/base/common/processes';
import { parse } from 'path';
const enum Constants {
/**
* The amount of time to throttle checks when the process receives output.
*/
InactiveThrottleDuration = 10000,
InactiveThrottleDuration = 5000,
/**
* The amount of time to debounce check when the process receives input.
*/
ActiveDebounceDuration = 1000,
}
const ignoreProcessNames = [
// Popular prompt programs, these should not count as child processes
'starship',
'oh-my-posh'
];
/**
* Monitors a process for child processes, checking at differing times depending on input and output
* calls into the monitor.
......@@ -79,11 +87,39 @@ export class ChildProcessMonitor extends Disposable {
return;
}
const processItem = await listProcesses(this._pid);
this.hasChildProcesses = (processItem.children || false) && processItem.children.length > 0;
this.hasChildProcesses = this._processContainsChildren(processItem);
console.log('processItem', processItem);
}
@throttle(Constants.InactiveThrottleDuration)
private _refreshInactive(): void {
this._refreshActive();
}
private _processContainsChildren(processItem: ProcessItem): boolean {
// No child processes
if (!processItem.children) {
return false;
}
// A single child process, handle special cases
if (processItem.children.length === 1) {
const item = processItem.children[0];
let cmd: string;
if (item.cmd.startsWith(`"`)) {
cmd = item.cmd.substring(1, item.cmd.indexOf(`"`, 1));
} else {
const spaceIndex = item.cmd.indexOf(` `);
if (spaceIndex === -1) {
cmd = item.cmd;
} else {
cmd = item.cmd.substring(0, spaceIndex);
}
}
return ignoreProcessNames.indexOf(parse(cmd).name) === -1;
}
// Fallback, count child processes
return processItem.children.length > 0;
}
}
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Barrier } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
......
......@@ -12,11 +12,12 @@ import { ITerminalInstance, ITerminalInstanceService } from 'vs/workbench/contri
import { TerminalEditor } from 'vs/workbench/contrib/terminal/browser/terminalEditor';
import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
import { TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { KEYBINDING_CONTEXT_TERMINAL_EDITOR_FOCUS } from 'vs/workbench/contrib/terminal/common/terminal';
import { ConfirmOnKill, KEYBINDING_CONTEXT_TERMINAL_EDITOR_FOCUS } from 'vs/workbench/contrib/terminal/common/terminal';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class TerminalEditorInput extends EditorInput {
......@@ -71,13 +72,22 @@ export class TerminalEditorInput extends EditorInput {
return this._terminalInstance.resource;
}
override isDirty(): boolean { return this._terminalInstance.hasChildProcesses; }
override isDirty(): boolean {
const confirmOnKill = this._configurationService.getValue<ConfirmOnKill>(TerminalSettingId.ConfirmOnKill);
if (confirmOnKill === 'editor' || confirmOnKill === 'editorAndPanel') {
console.log('isDirty?', this._terminalInstance.hasChildProcesses);
return this._terminalInstance.hasChildProcesses;
}
console.log('isDirty?', false);
return false;
}
constructor(
private readonly _terminalInstance: ITerminalInstance,
@IThemeService private readonly _themeService: IThemeService,
@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ILifecycleService lifecycleService: ILifecycleService,
@IContextKeyService contextKeyService: IContextKeyService
) {
......@@ -98,13 +108,17 @@ export class TerminalEditorInput extends EditorInput {
this._terminalInstance.onIconChanged(() => this._onDidChangeLabel.fire()),
this._terminalInstance.onDidFocus(() => this._terminalEditorFocusContextKey.set(true)),
this._terminalInstance.onDidBlur(() => this._terminalEditorFocusContextKey.reset()),
this._terminalInstance.onDidChangeHasChildProcesses(() => {
console.log('TerminalEditorInput fire dirty change');
this._onDidChangeDirty.fire();
}),
this._terminalInstance.onDidChangeHasChildProcesses(() => this._onDidChangeDirty.fire()),
this._terminalInstance.statusList.onDidChangePrimaryStatus(() => this._onDidChangeLabel.fire())
];
// Refresh dirty state when the confirm on kill setting is changed
this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) {
this._onDidChangeDirty.fire();
}
});
// Don't dispose editor when instance is torn down on shutdown to avoid extra work and so
// the editor/tabs don't disappear
lifecycleService.onWillShutdown(() => {
......
......@@ -312,7 +312,11 @@ export class TerminalService implements ITerminalService {
}
async safeDisposeTerminal(instance: ITerminalInstance): Promise<void> {
if (this.configHelper.config.confirmOnExit) {
// Confirm on kill in the editor is handled by the editor input
if (instance.target !== TerminalLocation.Editor &&
instance.hasChildProcesses &&
(this.configHelper.config.confirmOnKill === 'panel' || this.configHelper.config.confirmOnKill === 'editorAndPanel')) {
const notConfirmed = await this._showTerminalCloseConfirmation(true);
if (notConfirmed) {
return;
......
......@@ -172,6 +172,8 @@ export interface ITerminalProfiles {
windows: { [key: string]: ITerminalProfileObject };
}
export type ConfirmOnKill = 'off' | 'editor' | 'panel' | 'editorAndPanel';
export interface ITerminalConfiguration {
shell: {
linux: string | null;
......@@ -222,6 +224,7 @@ export interface ITerminalConfiguration {
allowMnemonics: boolean;
cwd: string;
confirmOnExit: boolean;
confirmOnKill: ConfirmOnKill;
enableBell: boolean;
env: {
linux: { [key: string]: string };
......
......@@ -270,6 +270,12 @@ const terminalConfiguration: IConfigurationNode = {
type: 'boolean',
default: false
},
[TerminalSettingId.ConfirmOnKill]: {
description: localize('terminal.integrated.confirmOnKill', "Controls whether to confirm killing terminals when they have child processes."),
type: 'string',
enum: ['off', 'editor', 'panel', 'editorAndPanel'],
default: 'editor'
},
[TerminalSettingId.EnableBell]: {
description: localize('terminal.integrated.enableBell', "Controls whether the terminal bell is enabled, this shows up as a visual bell next to the terminal's name."),
type: 'boolean',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册