未验证 提交 1f110359 编写于 作者: M meganrogge

start work

上级 18ab7aca
......@@ -548,7 +548,8 @@ export interface IProcessReadyEvent {
}
export const enum ProcessCapability {
CwdDetection = 'cwdDetection'
CwdDetection = 'cwdDetection',
ShellIntegration = 'shellIntegration'
}
/**
......
......@@ -182,6 +182,9 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
if (isLinux || isMacintosh) {
this.capabilities.push(ProcessCapability.CwdDetection);
}
if (shellLaunchConfig.executable === 'zsh') {
this.capabilities.push(ProcessCapability.ShellIntegration);
}
}
async start(): Promise<ITerminalLaunchError | undefined> {
......
......@@ -792,6 +792,12 @@ export interface ITerminalInstance {
* Triggers a quick pick that displays links from the viewport of the active terminal.
*/
showLinkQuickpick(type: TerminalLinkProviderType): Promise<void>;
/**
* Triggers a quick pick that displays recent commands or cwds. Selecting one will
* re-run it in the active terminal.
*/
runRecent(type: 'command' | 'cwd'): Promise<void>;
}
export interface IXtermTerminal {
......@@ -850,3 +856,25 @@ export const enum LinuxDistro {
Fedora = 2,
Ubuntu = 3,
}
export enum ShellIntegrationInteraction {
PromptStart = 'PROMPT_START',
CommandStart = 'COMMAND_START',
CommandExecuted = 'COMMAND_EXECUTED',
CommandFinished = 'COMMAND_FINISHED'
}
export interface TerminalCommand {
command: string;
timestamp: string;
cwd?: string;
exitCode?: number;
}
export enum ShellIntegrationInfo {
RemoteHost = 'RemoteHost',
CurrentDir = 'CurrentDir',
}
export interface IShellChangeEvent { type: ShellIntegrationInfo | ShellIntegrationInteraction, value: string }
......@@ -538,6 +538,34 @@ export function registerTerminalActions() {
return terminalGroupService.showPanel();
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: TerminalCommandId.RerunCommand,
title: { value: localize('workbench.action.terminal.rerunCommand', "Re-run Command"), original: 'Re-run Command' },
f1: true,
category,
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated)
});
}
async run(accessor: ServicesAccessor): Promise<void> {
await accessor.get(ITerminalService).activeInstance?.runRecent('command');
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: TerminalCommandId.GoToRecentDirectory,
title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory"), original: 'Go to Recent Directory' },
f1: true,
category,
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated)
});
}
async run(accessor: ServicesAccessor): Promise<void> {
await accessor.get(ITerminalService).activeInstance?.runRecent('cwd');
}
});
registerAction2(class extends Action2 {
constructor() {
super({
......
......@@ -597,7 +597,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
xterm.raw.onKey(e => this._onKey(e.key, e.domEvent));
xterm.raw.onSelectionChange(async () => this._onSelectionChange());
xterm.raw.buffer.onBufferChange(() => this._refreshAltBufferContextKey());
this._processManager.onProcessData(e => this._onProcessData(e));
xterm.raw.onData(async data => {
await this._processManager.write(data);
......@@ -639,6 +638,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._pathService.userHome().then(userHome => {
this._userHome = userHome.fsPath;
});
if (this.capabilities.includes(ProcessCapability.ShellIntegration)) {
//TODO: turn off polling for this case
xterm.commandTracker.onCwdChanged(cwd => this._cwd = cwd);
}
return xterm;
}
......@@ -858,6 +861,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._processManager.onProcessReady((e) => {
this._linkManager?.setWidgetManager(this._widgetManager);
this._capabilities = e.capabilities;
this.xterm?.setCapabilites(this._capabilities);
this._workspaceFolder = path.basename(e.cwd.toString());
});
......@@ -916,6 +920,54 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this.xterm?.raw.selectAll();
}
async runRecent(type: 'command' | 'cwd'): Promise<void> {
const commands = this.xterm?.commandTracker.getCommands();
if (!commands || !this.xterm) {
return;
}
type Item = IQuickPickItem;
const items: Item[] = [];
if (type === 'command') {
for (const { command, timestamp, cwd, exitCode } of commands) {
// trim off /r
const label = command.substring(0, command.length - 1);
if (label.length === 0) {
continue;
}
const cwdDescription = cwd ? `cwd: ${cwd} ` : '';
const exitCodeDescription = exitCode ? `exitCode: ${exitCode} ` : '';
items.push({
label,
description: exitCodeDescription + cwdDescription,
detail: timestamp,
id: timestamp
});
}
} else {
const cwds = this.xterm.commandTracker.getCommands().map(c => c.cwd).filter(c => c !== undefined);
const map = new Map<string, number>();
if (!cwds) {
return;
}
for (const cwd of cwds) {
const entry = map.get(cwd!);
if (entry) {
map.set(cwd!, entry + 1);
} else {
map.set(cwd!, 1);
}
}
const sorted = [...map.entries()].sort((a, b) => b[1] - a[1]);
for (const entry of sorted) {
items.push({ label: entry[0] });
}
}
const result = await this._quickInputService.pick(items.reverse(), {});
if (result) {
this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, true);
}
}
notifyFindWidgetFocusChanged(isFocused: boolean): void {
if (!this.xterm) {
return;
......
......@@ -5,12 +5,14 @@
import type { Terminal, IMarker, ITerminalAddon } from 'xterm';
import { ICommandTracker } from 'vs/workbench/contrib/terminal/common/terminal';
import { ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ProcessCapability } from 'vs/platform/terminal/common/terminal';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
/**
* The minimum size of the prompt in which to assume the line is a command.
*/
const MINIMUM_PROMPT_LENGTH = 2;
enum Boundary {
Top,
Bottom
......@@ -21,29 +23,104 @@ export const enum ScrollPosition {
Middle
}
export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon {
export class CommandTrackerAddon extends Disposable implements ICommandTracker, ITerminalAddon {
private _currentMarker: IMarker | Boundary = Boundary.Bottom;
private _selectionStart: IMarker | Boundary | null = null;
private _isDisposable: boolean = false;
private _terminal: Terminal | undefined;
private _capabilities: ProcessCapability[] | undefined = undefined;
private _dataIsCommand = false;
private _commands: TerminalCommand[] = [];
private _exitCode: number | undefined;
private _cwd: string | undefined;
private _currentCommand = '';
private readonly _onCwdChanged = this._register(new Emitter<string>());
readonly onCwdChanged = this._onCwdChanged.event;
activate(terminal: Terminal): void {
this._terminal = terminal;
terminal.onKey(e => this._onKey(e.key));
terminal.onIntegratedShellChange(e => {
if (!this._terminal) {
return;
}
if (this._terminal.buffer.active.cursorX >= MINIMUM_PROMPT_LENGTH) {
if (!this._shellIntegrationEnabled()) {
terminal.onKey(e => this._onKey(e.key));
} else if (e.type === ShellIntegrationInteraction.CommandFinished) {
this._terminal?.registerMarker(0);
this.clearMarker();
}
}
this._handleIntegratedShellChange(e);
});
terminal.onData(data => {
if (this._shellIntegrationEnabled() && this._dataIsCommand) {
this._currentCommand += data;
}
});
}
private _shellIntegrationEnabled(): boolean {
return this._capabilities?.includes(ProcessCapability.ShellIntegration) || false;
}
private _handleIntegratedShellChange(event: { type: string, value: string }): void {
if (!this._shellIntegrationEnabled()) {
return;
}
switch (event.type) {
case ShellIntegrationInfo.CurrentDir:
this._cwd = event.value;
this._onCwdChanged.fire(this._cwd);
break;
case ShellIntegrationInfo.RemoteHost:
break;
case ShellIntegrationInteraction.PromptStart:
break;
case ShellIntegrationInteraction.CommandStart:
this._dataIsCommand = true;
break;
case ShellIntegrationInteraction.CommandExecuted:
break;
case ShellIntegrationInteraction.CommandFinished:
this._exitCode = Number.parseInt(event.value);
if (!this._currentCommand.startsWith('\\') && this._currentCommand !== '') {
this._commands.push(
{
command: this._currentCommand,
timestamp: this._getCurrentTimestamp(),
cwd: this._cwd,
exitCode: this._exitCode
});
}
this._currentCommand = '';
break;
default:
return;
}
}
private _getCurrentTimestamp(): string {
const toTwoDigits = (v: number) => v < 10 ? `0${v}` : v;
const toThreeDigits = (v: number) => v < 10 ? `00${v}` : v < 100 ? `0${v}` : v;
const currentTime = new Date();
return `${currentTime.getFullYear()}-${toTwoDigits(currentTime.getMonth() + 1)}-${toTwoDigits(currentTime.getDate())} ${toTwoDigits(currentTime.getHours())}:${toTwoDigits(currentTime.getMinutes())}:${toTwoDigits(currentTime.getSeconds())}.${toThreeDigits(currentTime.getMilliseconds())}`;
}
dispose(): void {
getCommands(): TerminalCommand[] {
return this._commands;
}
override dispose(): void {
}
private _onKey(key: string): void {
if (key === '\x0d') {
this._onEnter();
}
// Clear the current marker so successive focus/selection actions are performed from the
// bottom of the buffer
this._currentMarker = Boundary.Bottom;
this._selectionStart = null;
this.clearMarker();
}
private _onEnter(): void {
......@@ -55,6 +132,19 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon {
}
}
clearMarker(): void {
// Clear the current marker so successive focus/selection actions are performed from the
// bottom of the buffer
this._currentMarker = Boundary.Bottom;
this._selectionStart = null;
}
setCapabilites(capabilties: ProcessCapability[]): void {
// this is created before the onProcessReady event
// gets fired, which has the capabilities
this._capabilities = capabilties;
}
scrollToPreviousCommand(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void {
if (!this._terminal) {
return;
......
......@@ -12,7 +12,7 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { ProcessCapability, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { ICommandTracker, ITerminalFont, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { isSafari } from 'vs/base/browser/browser';
import { IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
......@@ -139,11 +139,16 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal {
// Load addons
this._updateUnicodeVersion();
this._commandTrackerAddon = new CommandTrackerAddon();
this.raw.loadAddon(this._commandTrackerAddon);
}
setCapabilites(capabilties: ProcessCapability[]): void {
// this is created before the onProcessReady event
// gets fired, which has the capabilities
this._commandTrackerAddon.setCapabilites(capabilties);
}
attachToElement(container: HTMLElement) {
// Update the theme when attaching as the terminal location could have changed
this._updateTheme();
......
......@@ -326,6 +326,13 @@ export interface ICommandTracker {
selectToNextCommand(): void;
selectToPreviousLine(): void;
selectToNextLine(): void;
clearMarker(): void;
getCommands(): { command: string, timestamp: string, cwd?: string, exitCode?: number }[];
/**
* Fired when shell integration is enabled
* and the command tracker receives an updated cwd
*/
onCwdChanged: Event<string>;
}
export interface INavigationMode {
......@@ -497,6 +504,8 @@ export const enum TerminalCommandId {
SelectDefaultProfile = 'workbench.action.terminal.selectDefaultShell',
RunSelectedText = 'workbench.action.terminal.runSelectedText',
RunActiveFile = 'workbench.action.terminal.runActiveFile',
RerunCommand = 'workbench.action.terminal.rerunCommand',
GoToRecentDirectory = 'workbench.action.terminal.goToRecentDirectory',
SwitchTerminal = 'workbench.action.terminal.switchTerminal',
ScrollDownLine = 'workbench.action.terminal.scrollDown',
ScrollDownPage = 'workbench.action.terminal.scrollDownPage',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册