Merge pull request #5985 from Microsoft/tyriar/integrated_terminal

integrated terminal
This commit is contained in:
Daniel Imms 2016-05-16 10:50:04 -07:00
commit e9ef53ff76
11 changed files with 510 additions and 0 deletions

10
npm-shrinkwrap.json generated
View file

@ -340,6 +340,11 @@
"from": "preserve@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz"
},
"pty.js": {
"version": "0.3.0",
"from": "https://github.com/platformio/pty.js/tarball/22e84bd6ef177c7c211a767564ffe64426cb8a69",
"resolved": "https://github.com/platformio/pty.js/tarball/22e84bd6ef177c7c211a767564ffe64426cb8a69"
},
"randomatic": {
"version": "1.1.5",
"from": "randomatic@>=1.1.3 <2.0.0",
@ -390,6 +395,11 @@
"from": "string_decoder@>=0.10.0 <0.11.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
},
"term.js": {
"version": "0.0.7",
"from": "https://github.com/jeremyramin/term.js/tarball/master",
"resolved": "https://github.com/jeremyramin/term.js/tarball/master"
},
"typechecker": {
"version": "2.0.8",
"from": "typechecker@>=2.0.1 <2.1.0",

View file

@ -27,8 +27,10 @@
"iconv-lite": "0.4.13",
"minimist": "^1.2.0",
"native-keymap": "0.1.2",
"pty.js": "https://github.com/platformio/pty.js/tarball/22e84bd6ef177c7c211a767564ffe64426cb8a69",
"sax": "1.1.2",
"semver": "4.3.6",
"term.js": "https://github.com/jeremyramin/term.js/tarball/master",
"vscode-debugprotocol": "1.8.0",
"vscode-textmate": "1.0.11",
"winreg": "1.2.0",

View file

@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {TPromise} from 'vs/base/common/winjs.base';
import {createDecorator, ServiceIdentifier} from 'vs/platform/instantiation/common/instantiation';
import path = require('path');
import platform = require('vs/base/common/platform');
export const TERMINAL_PANEL_ID = 'workbench.panel.terminal';
export const TERMINAL_SERVICE_ID = 'terminalService';
export const TERMINAL_DEFAULT_SHELL_UNIX_LIKE = process.env.SHELL || 'sh';
export const TERMINAL_DEFAULT_SHELL_WINDOWS = platform.isWindows ? path.resolve(process.env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe') : '';
export var ITerminalService = createDecorator<ITerminalService>(TERMINAL_SERVICE_ID);
export interface ITerminalConfiguration {
integratedTerminal: {
shell: {
unixLike: string,
windows: string
},
fontFamily: string,
ansiColors: {
black: string,
red: string,
green: string,
yellow: string,
blue: string,
magenta: string,
cyan: string,
white: string,
brightBlack: string,
brightRed: string,
brightGreen: string,
brightYellow: string,
brightBlue: string,
brightMagenta: string,
brightCyan: string,
brightWhite: string,
}
};
}
export interface ITerminalService {
serviceId: ServiceIdentifier<any>;
show(): TPromise<any>;
}

View file

@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .integrated-terminal {
align-items: flex-end;
display: flex;
}
.monaco-workbench .integrated-terminal {
background-color: #1e1e1e!important;
color: #CCC;
-webkit-user-select: initial;
overflow: hidden; /* prevents the terminal output being incorrectly placed over the title */
}
.monaco-workbench .integrated-terminal .terminal {
background-color: #1e1e1e!important;
}
.monaco-workbench .integrated-terminal .terminal span {
display: inline-block; /* make terminal characters cover height of line */
}
.monaco-workbench .integrated-terminal .terminal-cursor {
background-color: #CCC;
}
.monaco-workbench .integrated-terminal .reverse-video {
color: #1e1e1e;
}
/* High Contrast Theming */
.hc-dark .monaco-workbench .integrated-terminal .terminal {
background-color: black!important;
}
.hc-dark .monaco-workbench .integrated-terminal .terminal-cursor {
background-color: white;
}
.hc-dark .monaco-workbench .integrated-terminal .reverse-video {
color: black;
}

View file

@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'pty.js' {
export function fork(file: string, args: string[], options: any): Terminal;
export function spawn(file: string, args: string[], options: any): Terminal;
export function createTerminal(file: string, args: string[], options: any): Terminal;
export interface Terminal {
/**
* The title of the active process.
*/
process: string;
on(event: string, callback: (data: any) => void): void;
resize(columns: number, rows: number): void;
write(data: string): void;
}
}

View file

@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'term.js' {
function init(options: any): TermJsTerminal;
// There seems to be no way to export this so it can be referenced outside of this file when a
// module is a function.
interface TermJsTerminal {
on(event: string, callback: (data: any) => void): void;
resize(columns: number, rows: number): void;
}
export = init;
}

View file

@ -0,0 +1,143 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/terminal.contribution';
import nls = require('vs/nls');
import product from 'vs/platform/product';
//import {KeyMod, KeyCode} from 'vs/base/common/keyCodes';
import {SyncActionDescriptor} from 'vs/platform/actions/common/actions';
import {registerSingleton} from 'vs/platform/instantiation/common/extensions';
import {IWorkbenchActionRegistry, Extensions as ActionExtensions} from 'vs/workbench/common/actionRegistry';
import {TerminalService} from 'vs/workbench/parts/terminal/electron-browser/terminalService';
import {ToggleTerminalAction} from 'vs/workbench/parts/terminal/electron-browser/terminalActions';
import {ITerminalService, TERMINAL_PANEL_ID, TERMINAL_DEFAULT_SHELL_UNIX_LIKE, TERMINAL_DEFAULT_SHELL_WINDOWS} from 'vs/workbench/parts/terminal/common/terminal';
import * as panel from 'vs/workbench/browser/panel';
import {Registry} from 'vs/platform/platform';
import {Extensions, IConfigurationRegistry} from 'vs/platform/configuration/common/configurationRegistry';
// Only enable in the alpha channel until more stable
if (product.quality === 'alpha') {
let configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'terminal',
'order': 100,
'title': nls.localize('integratedTerminalConfigurationTitle', "(Experimental) Integrated terminal configuration"),
'type': 'object',
'properties': {
'integratedTerminal.shell.unixLike': {
'description': nls.localize('integratedTerminal.shell.unixLike', "The path of the shell that the terminal uses on Linux and OS X."),
'type': 'string',
'default': TERMINAL_DEFAULT_SHELL_UNIX_LIKE
},
'integratedTerminal.shell.windows': {
'description': nls.localize('integratedTerminal.shell.windows', "The path of the shell that the terminal uses on Windows."),
'type': 'string',
'default': TERMINAL_DEFAULT_SHELL_WINDOWS
},
'integratedTerminal.fontFamily': {
'description': nls.localize('integratedTerminal.fontFamily', "The font family used by the terminal (CSS font-family format)."),
'type': 'string',
'default': 'Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"'
},
'integratedTerminal.ansiColors.black': {
'description': nls.localize('integratedTerminal.ansiColors.black', "Black color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#000000'
},
'integratedTerminal.ansiColors.red': {
'description': nls.localize('integratedTerminal.ansiColors.red', "Red color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#cd3131'
},
'integratedTerminal.ansiColors.green': {
'description': nls.localize('integratedTerminal.ansiColors.green', "Green color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#09885a'
},
'integratedTerminal.ansiColors.yellow': {
'description': nls.localize('integratedTerminal.ansiColors.yellow', "Yellow color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#e5e510'
},
'integratedTerminal.ansiColors.blue': {
'description': nls.localize('integratedTerminal.ansiColors.blue', "Blue color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#0451a5'
},
'integratedTerminal.ansiColors.magenta': {
'description': nls.localize('integratedTerminal.ansiColors.magenta', "Magenta color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#bc05bc'
},
'integratedTerminal.ansiColors.cyan': {
'description': nls.localize('integratedTerminal.ansiColors.cyan', "Cyan color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#0598bc'
},
'integratedTerminal.ansiColors.white': {
'description': nls.localize('integratedTerminal.ansiColors.white', "White color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#e5e5e5'
},
'integratedTerminal.ansiColors.brightBlack': {
'description': nls.localize('integratedTerminal.ansiColors.brightBlack', "Bright black color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#000000'
},
'integratedTerminal.ansiColors.brightRed': {
'description': nls.localize('integratedTerminal.ansiColors.brightRed', "Bright red color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#cd3131'
},
'integratedTerminal.ansiColors.brightGreen': {
'description': nls.localize('integratedTerminal.ansiColors.brightGreen', "Bright green color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#09885a'
},
'integratedTerminal.ansiColors.brightYellow': {
'description': nls.localize('integratedTerminal.ansiColors.brightYellow', "Bright yellow color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#e5e510'
},
'integratedTerminal.ansiColors.brightBlue': {
'description': nls.localize('integratedTerminal.ansiColors.brightBlue', "Bright blue color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#0451a5'
},
'integratedTerminal.ansiColors.brightMagenta': {
'description': nls.localize('integratedTerminal.ansiColors.brightMagenta', "Bright magenta color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#bc05bc'
},
'integratedTerminal.ansiColors.brightCyan': {
'description': nls.localize('integratedTerminal.ansiColors.brightCyan', "Bright cyan color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#0598bc'
},
'integratedTerminal.ansiColors.brightWhite': {
'description': nls.localize('integratedTerminal.ansiColors.brightWhite', "Bright white color for terminals that support ANSI escape sequences (format: #rrggbb)"),
'type': 'string',
'default': '#e5e5e5'
}
}
});
// Register Service
registerSingleton(ITerminalService, TerminalService);
// Register Output Panel
(<panel.PanelRegistry>Registry.as(panel.Extensions.Panels)).registerPanel(new panel.PanelDescriptor(
'vs/workbench/parts/terminal/electron-browser/terminalPanel',
'TerminalPanel',
TERMINAL_PANEL_ID,
nls.localize('terminal', "Terminal"),
'terminal'
));
// Register toggle output action globally
let actionRegistry = <IWorkbenchActionRegistry>Registry.as(ActionExtensions.WorkbenchActions);
// { primary: KeyMod.CtrlCmd | KeyCode.US_BACKTICK }
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTerminalAction, ToggleTerminalAction.ID, ToggleTerminalAction.LABEL), 'View: ' + ToggleTerminalAction.LABEL, nls.localize('viewCategory', "View"));
}

View file

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {TPromise} from 'vs/base/common/winjs.base';
import nls = require('vs/nls');
import {Action} from 'vs/base/common/actions';
import {ITerminalService} from 'vs/workbench/parts/terminal/common/terminal';
export class ToggleTerminalAction extends Action {
public static ID = 'workbench.action.terminal.toggleTerminal';
public static LABEL = nls.localize('toggleTerminal', "(Experimental) Toggle Terminal");
constructor(
id: string, label: string,
@ITerminalService private terminalService: ITerminalService
) {
super(id, label);
}
public run(event?: any): TPromise<any> {
return this.terminalService.show();
}
}

View file

@ -0,0 +1,158 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import termJs = require('term.js');
import lifecycle = require('vs/base/common/lifecycle');
import fs = require('fs');
import DOM = require('vs/base/browser/dom');
import {fork, Terminal} from 'pty.js';
import platform = require('vs/base/common/platform');
import {TPromise} from 'vs/base/common/winjs.base';
import {Builder, Dimension} from 'vs/base/browser/builder';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {ITerminalConfiguration, TERMINAL_PANEL_ID} from 'vs/workbench/parts/terminal/common/terminal';
import {Panel} from 'vs/workbench/browser/panel';
import {DomScrollableElement} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {ScrollbarVisibility} from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
const TERMINAL_CHAR_WIDTH = 8;
const TERMINAL_CHAR_HEIGHT = 18;
export class TerminalPanel extends Panel {
private toDispose: lifecycle.IDisposable[];
private ptyProcess: Terminal;
private parentDomElement: HTMLElement;
private terminal;
private terminalDomElement: HTMLDivElement;
constructor(
@IConfigurationService private configurationService: IConfigurationService,
@ITelemetryService telemetryService: ITelemetryService,
@IWorkspaceContextService private contextService: IWorkspaceContextService
) {
super(TERMINAL_PANEL_ID, telemetryService);
this.toDispose = [];
}
public layout(dimension: Dimension): void {
let cols = Math.floor(this.parentDomElement.offsetWidth / TERMINAL_CHAR_WIDTH);
let rows = Math.floor(this.parentDomElement.offsetHeight / TERMINAL_CHAR_HEIGHT);
this.terminal.resize(cols, rows);
this.ptyProcess.resize(cols, rows);
}
public create(parent: Builder): TPromise<void> {
super.create(parent);
this.parentDomElement = parent.getHTMLElement();
return this.createTerminal();
}
private createTerminal(): TPromise<void> {
return new TPromise<void>(resolve => {
this.parentDomElement.innerHTML = '';
this.ptyProcess = fork(this.getShell(), [], {
name: fs.existsSync('/usr/share/terminfo/x/xterm-256color') ? 'xterm-256color' : 'xterm',
cwd: this.contextService.getWorkspace() ? this.contextService.getWorkspace().resource.fsPath : process.env.HOME
});
this.terminalDomElement = document.createElement('div');
this.parentDomElement.classList.add('integrated-terminal');
let terminalScrollbar = new DomScrollableElement(this.terminalDomElement, {
canUseTranslate3d: false,
horizontal: ScrollbarVisibility.Hidden,
vertical: ScrollbarVisibility.Auto
});
this.toDispose.push(terminalScrollbar);
this.terminal = termJs({
cursorBlink: false // term.js' blinking cursor breaks selection
});
this.ptyProcess.on('data', (data) => {
this.terminal.write(data);
});
this.terminal.on('data', (data) => {
this.ptyProcess.write(data);
return false;
});
this.ptyProcess.on('exit', (data) => {
this.terminal.destroy();
// TODO: When multiple terminals are supported this should do something smarter. There is
// also a weird bug here at leasy on Ubuntu 15.10 where the new terminal text does not
// repaint correctly.
this.createTerminal();
});
this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mousedown', (event) => {
// Drop selection and focus terminal on Linux to enable middle button paste when click
// occurs on the selection itself.
if (event.which === 2 && platform.isLinux) {
this.focusTerminal(true);
}
}));
this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mouseup', (event) => {
if (event.which !== 3) {
this.focusTerminal();
}
}));
this.terminal.open(this.terminalDomElement);
this.parentDomElement.appendChild(terminalScrollbar.getDomNode());
let config = this.configurationService.getConfiguration<ITerminalConfiguration>();
this.terminalDomElement.style.fontFamily = config.integratedTerminal.fontFamily;
this.terminal.colors = this.getTerminalColors();
resolve(void 0);
});
}
private focusTerminal(force?: boolean): void {
let text = window.getSelection().toString();
if (!text || force) {
this.terminal.focus();
if (this.terminal._textarea) {
this.terminal._textarea.focus();
}
}
}
private getShell(): string {
let config = this.configurationService.getConfiguration<ITerminalConfiguration>();
if (platform.isWindows) {
return config.integratedTerminal.shell.windows;
}
return config.integratedTerminal.shell.unixLike;
}
private getTerminalColors(): string[] {
let config = this.configurationService.getConfiguration<ITerminalConfiguration>().integratedTerminal.ansiColors;
let colors = [
config.black,
config.red,
config.green,
config.yellow,
config.blue,
config.magenta,
config.cyan,
config.white,
config.brightBlack,
config.brightRed,
config.brightGreen,
config.brightYellow,
config.brightBlue,
config.brightMagenta,
config.brightCyan,
config.brightWhite
];
return colors;
}
public dispose(): void {
this.toDispose = lifecycle.dispose(this.toDispose);
super.dispose();
}
}

View file

@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {TPromise} from 'vs/base/common/winjs.base';
import {IPanelService} from 'vs/workbench/services/panel/common/panelService';
import {IPartService} from 'vs/workbench/services/part/common/partService';
import {ITerminalService, TERMINAL_PANEL_ID} from 'vs/workbench/parts/terminal/common/terminal';
export class TerminalService implements ITerminalService {
public serviceId = ITerminalService;
constructor(
@IPanelService private panelService: IPanelService,
@IPartService private partService: IPartService
) {
}
public show(): TPromise<any> {
const panel = this.panelService.getActivePanel();
if (panel && panel.getId() === TERMINAL_PANEL_ID) {
this.partService.setPanelHidden(true);
return TPromise.as(null);
}
return this.panelService.openPanel(TERMINAL_PANEL_ID, true);
}
}

View file

@ -58,6 +58,8 @@ define([
'vs/workbench/parts/output/browser/output.contribution',
'vs/workbench/parts/terminal/electron-browser/terminal.contribution',
'vs/workbench/parts/markdown/browser/markdown.contribution',
'vs/workbench/parts/markdown/browser/markdownActions.contribution',