提交 c00060f0 编写于 作者: D Daniel Imms

Implement command tracking

Part of #45435
上级 060f00b1
......@@ -619,10 +619,10 @@ declare module 'vscode-xterm' {
declare module 'vscode-xterm' {
interface Terminal {
buffer: {
/**
* The viewport position.
*/
y: number;
ybase: number;
ydisp: number;
x: number;
};
/**
......
......@@ -267,6 +267,12 @@ export interface ITerminalInstance {
*/
disableLayout: boolean;
/**
* An object that tracks when commands are run and enables navigating and selecting between
* them.
*/
readonly commandTracker: ITerminalCommandTracker;
/**
* Dispose the terminal instance, removing it from the panel/service and freeing up resources.
*/
......@@ -440,3 +446,10 @@ export interface ITerminalInstance {
addDisposable(disposable: IDisposable): void;
}
export interface ITerminalCommandTracker {
focusPreviousCommand(): void;
focusNextCommand(): void;
selectToPreviousCommand(): void;
selectToNextCommand(): void;
}
\ No newline at end of file
......@@ -18,7 +18,7 @@ import { getTerminalDefaultShellUnixLike, getTerminalDefaultShellWindows } from
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction, SplitTerminalAction, FocusPreviousPaneTerminalAction, FocusNextPaneTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, ResizePaneDownTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions';
import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction, SplitTerminalAction, FocusPreviousPaneTerminalAction, FocusNextPaneTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, ResizePaneDownTerminalAction, FocusPreviousCommandAction, FocusNextCommandAction, SelectToPreviousCommandAction, SelectToNextCommandAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions';
import { Registry } from 'vs/platform/registry/common/platform';
import { ShowAllCommandsAction } from 'vs/workbench/parts/quickopen/browser/commandsHandler';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
......@@ -279,7 +279,11 @@ configurationRegistry.registerConfiguration({
ResizePaneLeftTerminalAction.ID,
ResizePaneRightTerminalAction.ID,
ResizePaneUpTerminalAction.ID,
ResizePaneDownTerminalAction.ID
ResizePaneDownTerminalAction.ID,
FocusPreviousCommandAction.ID,
FocusNextCommandAction.ID,
SelectToPreviousCommandAction.ID,
SelectToNextCommandAction.ID
].sort()
},
'terminal.integrated.env.osx': {
......@@ -474,6 +478,22 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTe
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow },
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Down', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousCommandAction, FocusPreviousCommandAction.ID, FocusPreviousCommandAction.LABEL, {
primary: null,
mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Previous Command', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextCommandAction, FocusNextCommandAction.ID, FocusNextCommandAction.LABEL, {
primary: null,
mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Next Command', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToPreviousCommandAction, SelectToPreviousCommandAction.ID, SelectToPreviousCommandAction.LABEL, {
primary: null,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select To Previous Command', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToNextCommandAction, SelectToNextCommandAction.ID, SelectToNextCommandAction.LABEL, {
primary: null,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select To Next Command', category);
terminalCommands.setup();
......
......@@ -977,3 +977,83 @@ export class RenameTerminalQuickOpenAction extends RenameTerminalAction {
return TPromise.as(null);
}
}
export class FocusPreviousCommandAction extends Action {
public static readonly ID = 'workbench.action.terminal.focusPreviousCommand';
public static readonly LABEL = nls.localize('workbench.action.terminal.focusPreviousCommand', "Focus Previous Command");
constructor(
id: string, label: string,
@ITerminalService private terminalService: ITerminalService
) {
super(id, label);
}
public run(): TPromise<any> {
const instance = this.terminalService.getActiveInstance();
if (instance) {
instance.commandTracker.focusPreviousCommand();
}
return TPromise.as(void 0);
}
}
export class FocusNextCommandAction extends Action {
public static readonly ID = 'workbench.action.terminal.focusNextCommand';
public static readonly LABEL = nls.localize('workbench.action.terminal.focusNextCommand', "Focus Next Command");
constructor(
id: string, label: string,
@ITerminalService private terminalService: ITerminalService
) {
super(id, label);
}
public run(): TPromise<any> {
const instance = this.terminalService.getActiveInstance();
if (instance) {
instance.commandTracker.focusNextCommand();
}
return TPromise.as(void 0);
}
}
export class SelectToPreviousCommandAction extends Action {
public static readonly ID = 'workbench.action.terminal.selectToPreviousCommand';
public static readonly LABEL = nls.localize('workbench.action.terminal.selectToPreviousCommand', "Select To Previous Command");
constructor(
id: string, label: string,
@ITerminalService private terminalService: ITerminalService
) {
super(id, label);
}
public run(): TPromise<any> {
const instance = this.terminalService.getActiveInstance();
if (instance) {
instance.commandTracker.selectToPreviousCommand();
}
return TPromise.as(void 0);
}
}
export class SelectToNextCommandAction extends Action {
public static readonly ID = 'workbench.action.terminal.selectToNextCommand';
public static readonly LABEL = nls.localize('workbench.action.terminal.selectToNextCommand', "Select To Next Command");
constructor(
id: string, label: string,
@ITerminalService private terminalService: ITerminalService
) {
super(id, label);
}
public run(): TPromise<any> {
const instance = this.terminalService.getActiveInstance();
if (instance) {
instance.commandTracker.selectToNextCommand();
}
return TPromise.as(void 0);
}
}
......@@ -40,6 +40,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ILogService } from 'vs/platform/log/common/log';
import { TerminalCommandTracker } from 'vs/workbench/parts/terminal/node/terminalCommandTracker';
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;
......@@ -103,6 +104,7 @@ export class TerminalInstance implements ITerminalInstance {
private _widgetManager: TerminalWidgetManager;
private _linkHandler: TerminalLinkHandler;
private _commandTracker: TerminalCommandTracker;
public disableLayout: boolean;
public get id(): number { return this._id; }
......@@ -115,6 +117,7 @@ export class TerminalInstance implements ITerminalInstance {
public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
public get isTitleSetByProcess(): boolean { return !!this._messageTitleListener; }
public get shellLaunchConfig(): IShellLaunchConfig { return Object.freeze(this._shellLaunchConfig); }
public get commandTracker(): TerminalCommandTracker { return this._commandTracker; }
public constructor(
private _terminalFocusContextKey: IContextKey<boolean>,
......@@ -330,6 +333,7 @@ export class TerminalInstance implements ITerminalInstance {
return false;
});
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._initialCwd);
this._commandTracker = new TerminalCommandTracker(this._xterm);
this._instanceDisposables.push(this._themeService.onThemeChange(theme => this._updateTheme(theme)));
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Terminal, IMarker } from 'vscode-xterm';
import { ITerminalCommandTracker } from 'vs/workbench/parts/terminal/common/terminal';
/**
* The minimize size of the prompt in which to assume the line is a command.
*/
const MINIMUM_PROMPT_LENGTH = 2;
enum Boundary {
Top,
Bottom
}
export class TerminalCommandTracker implements ITerminalCommandTracker {
private _currentMarker: IMarker | Boundary = Boundary.Bottom;
private _selectionStart: IMarker | Boundary | null = null;
constructor(
private _xterm: Terminal
) {
this._xterm.on('key', key => this._onKey(key));
}
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;
}
private _onEnter(): void {
if (this._xterm.buffer.x >= MINIMUM_PROMPT_LENGTH) {
this._xterm.addMarker(0);
}
}
public focusPreviousCommand(retainSelection: boolean = false): void {
if (!retainSelection) {
this._selectionStart = null;
}
let markerIndex;
if (this._currentMarker === Boundary.Bottom) {
markerIndex = this._xterm.markers.length - 1;
} else if (this._currentMarker === Boundary.Top) {
markerIndex = -1;
} else {
markerIndex = this._xterm.markers.indexOf(this._currentMarker) - 1;
}
if (markerIndex < 0) {
this._currentMarker = Boundary.Top;
this._xterm.scrollToTop();
return;
}
this._currentMarker = this._xterm.markers[markerIndex];
this._xterm.scrollToLine(this._currentMarker.line);
}
public focusNextCommand(retainSelection: boolean = false): void {
if (!retainSelection) {
this._selectionStart = null;
}
let markerIndex;
if (this._currentMarker === Boundary.Bottom) {
markerIndex = this._xterm.markers.length;
} else if (this._currentMarker === Boundary.Top) {
markerIndex = 0;
} else {
markerIndex = this._xterm.markers.indexOf(this._currentMarker) + 1;
}
if (markerIndex >= this._xterm.markers.length) {
this._currentMarker = Boundary.Bottom;
this._xterm.scrollToBottom();
return;
}
this._currentMarker = this._xterm.markers[markerIndex];
this._xterm.scrollToLine(this._currentMarker.line);
}
public selectToPreviousCommand(): void {
if (this._selectionStart === null) {
this._selectionStart = this._currentMarker;
}
this.focusPreviousCommand(true);
this._selectLines(this._currentMarker, this._selectionStart);
}
public selectToNextCommand(): void {
if (this._selectionStart === null) {
this._selectionStart = this._currentMarker;
}
this.focusNextCommand(true);
// if (!this._currentMarker
this._selectLines(this._currentMarker, this._selectionStart);
}
private _selectLines(start: IMarker | Boundary, end: IMarker | Boundary | null): void {
if (end === null) {
end = Boundary.Bottom;
}
let startLine = this._getLine(start);
let endLine = this._getLine(end);
if (startLine > endLine) {
const temp = startLine;
startLine = endLine;
endLine = temp;
}
// Subtract a line as the marker is on the line the command run, we do not want the next
// command in the selection for the current command
endLine -= 1;
this._xterm.selectLines(startLine, endLine);
}
private _getLine(marker: IMarker | Boundary): number {
// Use the _second last_ row as the last row is likely the prompt
if (marker === Boundary.Bottom) {
return this._xterm.buffer.ybase + this._xterm.rows - 1;
}
if (marker === Boundary.Top) {
return 0;
}
return marker.line;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册