Vscode merge (#4582)

* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd

* fix issues with merges

* bump node version in azpipe

* replace license headers

* remove duplicate launch task

* fix build errors

* fix build errors

* fix tslint issues

* working through package and linux build issues

* more work

* wip

* fix packaged builds

* working through linux build errors

* wip

* wip

* wip

* fix mac and linux file limits

* iterate linux pipeline

* disable editor typing

* revert series to parallel

* remove optimize vscode from linux

* fix linting issues

* revert testing change

* add work round for new node

* readd packaging for extensions

* fix issue with angular not resolving decorator dependencies
This commit is contained in:
Anthony Dresser
2019-03-19 17:44:35 -07:00
committed by GitHub
parent 833d197412
commit 87765e8673
1879 changed files with 54505 additions and 38058 deletions

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#C5C5C5"><path d="M12.714 9.603c-.07.207-.15.407-.246.601l1.017 2.139c-.335.424-.718.807-1.142 1.143l-2.14-1.018c-.193.097-.394.176-.601.247l-.795 2.235c-.265.03-.534.05-.807.05-.272 0-.541-.02-.806-.05l-.795-2.235c-.207-.071-.408-.15-.602-.247l-2.14 1.017c-.424-.336-.807-.719-1.143-1.143l1.017-2.139c-.094-.193-.175-.393-.245-.6l-2.236-.796c-.03-.265-.05-.534-.05-.807s.02-.542.05-.807l2.236-.795c.07-.207.15-.407.246-.601l-1.016-2.139c.336-.423.719-.807 1.143-1.142l2.14 1.017c.193-.096.394-.176.602-.247l.793-2.236c.265-.03.534-.05.806-.05.273 0 .542.02.808.05l.795 2.236c.207.07.407.15.601.246l2.14-1.017c.424.335.807.719 1.142 1.142l-1.017 2.139c.096.194.176.394.246.601l2.236.795c.029.266.049.535.049.808s-.02.542-.05.807l-2.236.796zm-4.714-4.603c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z"/><circle cx="8" cy="8" r="1.5"/></g></svg>

After

Width:  |  Height:  |  Size: 927 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#424242"><path d="M12.714 9.603c-.07.207-.15.407-.246.601l1.017 2.139c-.335.424-.718.807-1.142 1.143l-2.14-1.018c-.193.097-.394.176-.601.247l-.795 2.235c-.265.03-.534.05-.807.05-.272 0-.541-.02-.806-.05l-.795-2.235c-.207-.071-.408-.15-.602-.247l-2.14 1.017c-.424-.336-.807-.719-1.143-1.143l1.017-2.139c-.094-.193-.175-.393-.245-.6l-2.236-.796c-.03-.265-.05-.534-.05-.807s.02-.542.05-.807l2.236-.795c.07-.207.15-.407.246-.601l-1.016-2.139c.336-.423.719-.807 1.143-1.142l2.14 1.017c.193-.096.394-.176.602-.247l.793-2.236c.265-.03.534-.05.806-.05.273 0 .542.02.808.05l.795 2.236c.207.07.407.15.601.246l2.14-1.017c.424.335.807.719 1.142 1.142l-1.017 2.139c.096.194.176.394.246.601l2.236.795c.029.266.049.535.049.808s-.02.542-.05.807l-2.236.796zm-4.714-4.603c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z"/><circle cx="8" cy="8" r="1.5"/></g></svg>

After

Width:  |  Height:  |  Size: 927 B

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#C5C5C5;}
</style>
<path class="st0" d="M10,3V2c0-0.6-0.4-1-1-1H6C5.4,1,5,1.4,5,2v1H2v1h1v10c0,0.6,0.4,1,1,1h7c0.6,0,1-0.4,1-1V4h1V3H10z M6,12H5V6
h1V12z M6,2h3v1H6V2z M8,12H7V6h1V12z M10,12H9V6h1V12z"/>
</svg>

After

Width:  |  Height:  |  Size: 592 B

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#424242;}
</style>
<path class="st0" d="M10,3V2c0-0.6-0.4-1-1-1H6C5.4,1,5,1.4,5,2v1H2v1h1v10c0,0.6,0.4,1,1,1h7c0.6,0,1-0.4,1-1V4h1V3H10z M6,12H5V6
h1V12z M6,2h3v1H6V2z M8,12H7V6h1V12z M10,12H9V6h1V12z"/>
</svg>

After

Width:  |  Height:  |  Size: 592 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.714 2H6.286v12h3.428V2z" fill="#C5C5C5"/><path d="M14 6.286H2v3.428h12V6.286z" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 208 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1H6v14h4V1z" fill="#424242"/><path d="M15 6H1v4h14V6z" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 185 B

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .panel.integrated-terminal .xterm-viewport {
/* Use the hack presented in http://stackoverflow.com/a/38748186/1156119 to get opacity transitions working on the scrollbar */
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
transition: background-color 800ms linear;
}
.monaco-workbench .panel.integrated-terminal .xterm-viewport::-webkit-scrollbar {
width: 10px;
}
.monaco-workbench .panel.integrated-terminal .xterm-viewport::-webkit-scrollbar-track {
opacity: 0;
}
.monaco-workbench .panel.integrated-terminal .xterm-viewport::-webkit-scrollbar-thumb {
min-height: 20px;
background-color: inherit;
}
.monaco-workbench .panel.integrated-terminal .find-focused .xterm .xterm-viewport,
.monaco-workbench .panel.integrated-terminal .xterm.focus .xterm-viewport,
.monaco-workbench .panel.integrated-terminal .xterm:focus .xterm-viewport,
.monaco-workbench .panel.integrated-terminal .xterm:hover .xterm-viewport {
transition: opacity 100ms linear;
cursor: default;
}
.monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
transition: opacity 0ms linear;
}
.monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:window-inactive {
background-color: inherit;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 15H0V1h16v14z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M1 2v12h14V2H1zm13 11H2v-3h12v3zm0-5H2V5h12v3z" id="iconBg"/><g id="iconFg" style="display: none;"><path class="icon-vs-fg" d="M14 8H2V5h12v3zm0 2H2v3h12v-3z"/></g></svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 15H0V1h16v14z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M1 2v12h14V2H1zm13 11H2v-3h12v3zm0-5H2V5h12v3z" id="iconBg"/><g id="iconFg" style="display: none;"><path class="icon-vs-fg" d="M14 8H2V5h12v3zm0 2H2v3h12v-3z"/></g></svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -1 16 16" enable-background="new 0 -1 16 16"><path fill="#C5C5C5" d="M1 1v12h14v-12h-14zm1 3h4.999v8h-4.999v-8zm12 8h-5.001v-8h5.001v8z"/></svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -1 16 16" enable-background="new 0 -1 16 16"><path fill="#424242" d="M1 1v12h14v-12h-14zm1 3h4.999v8h-4.999v-8zm12 8h-5.001v-8h5.001v8z"/></svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@@ -0,0 +1,172 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .panel.integrated-terminal {
align-content: flex-start;
align-items: baseline;
display: flex;
flex-direction: column;
background-color: transparent!important;
user-select: initial;
position: relative;
}
.monaco-workbench .panel.integrated-terminal .terminal-outer-container {
height: 100%;
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.monaco-workbench .panel.integrated-terminal .terminal-tab {
height: 100%;
}
.monaco-workbench .panel.integrated-terminal .terminal-wrapper {
display: none;
margin: 0 10px;
}
.monaco-workbench .panel.integrated-terminal .terminal-wrapper.active {
display: block;
position: absolute;
bottom: 2px; /* Matches padding-bottom on .terminal-outer-container */
top: 0;
}
.monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper {
margin-left: 20px;
}
.monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper {
margin-right: 20px;
}
.monaco-workbench .panel.integrated-terminal .xterm a:not(.xterm-invalid-link) {
/* To support message box sizing */
position: relative;
}
.monaco-workbench .panel.integrated-terminal .terminal-wrapper > div {
height: 100%;
}
.monaco-workbench .panel.integrated-terminal .xterm-viewport {
margin-right: -10px;
}
.monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport {
margin-right: -20px;
}
.monaco-workbench .panel.integrated-terminal canvas {
/* Align the viewport and canvases to the bottom of the panel */
position: absolute;
right: -20px;
bottom: 0;
left: 0;
/* Disable upstream's style */
top: auto;
}
.monaco-workbench .panel.integrated-terminal {
font-variant-ligatures: none;
}
.monaco-workbench .panel.integrated-terminal .split-view-view {
/* Make relative as terminal absolute positioning needs it deeper in the tree */
position: relative;
box-sizing: border-box;
}
/* border-color is set by theme key terminal.border */
.monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:not(:first-child) {
border-left-width: 1px;
border-left-style: solid;
}
.monaco-workbench .panel.integrated-terminal .monaco-split-view2.vertical .split-view-view:not(:first-child) {
border-top-width: 1px;
border-top-style: solid;
}
.monaco-workbench .panel.integrated-terminal.enable-ligatures {
font-variant-ligatures: normal;
}
.monaco-workbench .panel.integrated-terminal.disable-bold .xterm-bold {
font-weight: normal !important;
}
/* Use the default cursor when alt is active to help with clicking to move cursor */
.monaco-workbench .panel.integrated-terminal .terminal-outer-container.alt-active .xterm {
cursor: default;
}
.monaco-workbench .panel.integrated-terminal .xterm {
position: absolute;
bottom: 0;
left: 0;
user-select: none;
}
.monaco-workbench .panel.integrated-terminal .monaco-split-view2.vertical .split-view-view:not(:last-child) .xterm {
/* When vertical and NOT the bottom terminal, align to the top instead to prevent the output jumping around erratically */
top: 0;
bottom: auto;
}
.monaco-workbench .panel.integrated-terminal .xterm:focus {
/* Hide outline when focus jumps from xterm to the text area */
outline: none;
}
.hc-black .monaco-workbench .panel.integrated-terminal .xterm.focus::before,
.hc-black .monaco-workbench .panel.integrated-terminal .xterm:focus::before {
display: block;
content: "";
border: 1px solid;
position: absolute;
left: -5px;
top: 0;
right: -5px;
bottom: 0;
z-index: 10;
}
.hc-black .monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:not(:only-child) .xterm.focus::before,
.hc-black .monaco-workbench .panel.integrated-terminal .monaco-split-view2.horizontal .split-view-view:not(:only-child) .xterm:focus::before {
right: 0;
}
.monaco-workbench .panel.integrated-terminal .xterm .xterm-helpers {
position: absolute;
top: 0;
}
.monaco-workbench .panel.integrated-terminal .xterm .xterm-helper-textarea:focus {
/* Override the general vscode style applies `opacity:1!important` to textareas */
opacity: 0 !important;
}
/* Light theme */
.monaco-workbench .terminal-action.kill { background: url('kill.svg') center center no-repeat; }
.monaco-workbench .terminal-action.new { background: url('new.svg') center center no-repeat; }
.monaco-workbench .terminal-action.split { background: url('split.svg') center center no-repeat; }
.monaco-workbench .panel.right .terminal-action.split { background: url('split-horizontal.svg') center center no-repeat; }
/* Dark theme / HC theme */
.vs-dark .monaco-workbench .terminal-action.kill, .hc-black .monaco-workbench .terminal-action.kill { background: url('kill-inverse.svg') center center no-repeat; }
.vs-dark .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-inverse.svg') center center no-repeat; }
.vs-dark .monaco-workbench .terminal-action.split, .hc-black .monaco-workbench .terminal-action.split { background: url('split-inverse.svg') center center no-repeat; }
.vs-dark .monaco-workbench .panel.right .terminal-action.split, .hc-black .monaco-workbench .panel.right .terminal-action.split { background: url('split-horizontal-inverse.svg') center center no-repeat; }
.vs-dark .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events),
.hc-black .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events) {
cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text;
}
.monaco-workbench .quick-open-terminal-configure {
background-image: url('configure.svg');
}
.vs-dark .monaco-workbench .quick-open-terminal-configure,
.hc-black .monaco-workbench .quick-open-terminal-configure {
background-image: url('configure-inverse.svg');
}

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .terminal-widget-overlay {
position: absolute;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
.monaco-workbench .terminal-message-widget {
font-size: 12px;
line-height: 19px;
padding: 4px 5px;
animation: fadein 100ms linear;
white-space: nowrap;
/* Must be drawn on the top of the terminal's canvases */
z-index: 20;
}

View File

@@ -0,0 +1,169 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (Source EULA)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 10;
}
.xterm .xterm-helper-textarea {
/*
* HACK: to fix IE's blinking cursor
* Move textarea out of the screen to the far left, so that the cursor is not visible.
*/
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm:not(.enable-mouse-events) {
cursor: text;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 100;
color: transparent;
}
.xterm .xterm-accessibility-tree:focus [id^="xterm-active-item-"] {
outline: 1px solid #F80;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.xterm-cursor-pointer {
cursor: pointer !important;
}
.xterm.xterm-cursor-crosshair {
/* Column selection mode */
cursor: crosshair !important;
}

View File

@@ -0,0 +1,519 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import * as platform from 'vs/base/common/platform';
import 'vs/css!./media/scrollbar';
import 'vs/css!./media/terminal';
import 'vs/css!./media/widgets';
import 'vs/css!./media/xterm';
import * as nls from 'vs/nls';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionBarExtensions, IActionBarRegistry, Scope } from 'vs/workbench/browser/actions';
import * as panel from 'vs/workbench/browser/panel';
import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen';
import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { AllowWorkspaceShellTerminalCommand, ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, DisallowWorkspaceShellTerminalCommand, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel';
import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen';
import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle } from 'vs/workbench/contrib/terminal/common/terminal';
import { registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
import { setupTerminalCommands, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
import { setupTerminalMenu } from 'vs/workbench/contrib/terminal/common/terminalMenu';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { DEFAULT_COMMANDS_TO_SKIP_SHELL } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
const quickOpenRegistry = (Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen));
const inTerminalsPicker = 'inTerminalPicker';
quickOpenRegistry.registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
TerminalPickerHandler,
TerminalPickerHandler.ID,
TERMINAL_PICKER_PREFIX,
inTerminalsPicker,
nls.localize('quickOpen.terminal', "Show All Opened Terminals")
)
);
const quickOpenNavigateNextInTerminalPickerId = 'workbench.action.quickOpenNavigateNextInTerminalPicker';
CommandsRegistry.registerCommand(
{ id: quickOpenNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigateNextInTerminalPickerId, true) });
const quickOpenNavigatePreviousInTerminalPickerId = 'workbench.action.quickOpenNavigatePreviousInTerminalPicker';
CommandsRegistry.registerCommand(
{ id: quickOpenNavigatePreviousInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigatePreviousInTerminalPickerId, false) });
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
configurationRegistry.registerConfiguration({
id: 'terminal',
order: 100,
title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
type: 'object',
properties: {
'terminal.integrated.shellArgs.linux': {
markdownDescription: nls.localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
type: 'array',
items: {
type: 'string'
},
default: []
},
'terminal.integrated.shellArgs.osx': {
markdownDescription: nls.localize('terminal.integrated.shellArgs.osx', "The command line arguments to use when on the macOS terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
type: 'array',
items: {
type: 'string'
},
// Unlike on Linux, ~/.profile is not sourced when logging into a macOS session. This
// is the reason terminals on macOS typically run login shells by default which set up
// the environment. See http://unix.stackexchange.com/a/119675/115410
default: ['-l']
},
'terminal.integrated.shellArgs.windows': {
markdownDescription: nls.localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
'anyOf': [
{
type: 'array',
items: {
type: 'string',
markdownDescription: nls.localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).")
},
},
{
type: 'string',
markdownDescription: nls.localize('terminal.integrated.shellArgs.windows.string', "The command line arguments in [command-line format](https://msdn.microsoft.com/en-au/08dfcab2-eb6e-49a4-80eb-87d4076c98c6) to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).")
}
],
default: []
},
'terminal.integrated.macOptionIsMeta': {
description: nls.localize('terminal.integrated.macOptionIsMeta', "Controls whether to treat the option key as the meta key in the terminal on macOS."),
type: 'boolean',
default: false
},
'terminal.integrated.macOptionClickForcesSelection': {
description: nls.localize('terminal.integrated.macOptionClickForcesSelection', "Controls whether to force selection when using Option+click on macOS. This will force a regular (line) selection and disallow the use of column selection mode. This enables copying and pasting using the regular terminal selection, for example, when mouse mode is enabled in tmux."),
type: 'boolean',
default: false
},
'terminal.integrated.copyOnSelection': {
description: nls.localize('terminal.integrated.copyOnSelection', "Controls whether text selected in the terminal will be copied to the clipboard."),
type: 'boolean',
default: false
},
'terminal.integrated.drawBoldTextInBrightColors': {
description: nls.localize('terminal.integrated.drawBoldTextInBrightColors', "Controls whether bold text in the terminal will always use the \"bright\" ANSI color variant."),
type: 'boolean',
default: true
},
'terminal.integrated.fontFamily': {
markdownDescription: nls.localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to `#editor.fontFamily#`'s value."),
type: 'string'
},
// TODO: Support font ligatures
// 'terminal.integrated.fontLigatures': {
// 'description': nls.localize('terminal.integrated.fontLigatures', "Controls whether font ligatures are enabled in the terminal."),
// 'type': 'boolean',
// 'default': false
// },
'terminal.integrated.fontSize': {
description: nls.localize('terminal.integrated.fontSize', "Controls the font size in pixels of the terminal."),
type: 'number',
default: EDITOR_FONT_DEFAULTS.fontSize
},
'terminal.integrated.letterSpacing': {
description: nls.localize('terminal.integrated.letterSpacing', "Controls the letter spacing of the terminal, this is an integer value which represents the amount of additional pixels to add between characters."),
type: 'number',
default: DEFAULT_LETTER_SPACING
},
'terminal.integrated.lineHeight': {
description: nls.localize('terminal.integrated.lineHeight', "Controls the line height of the terminal, this number is multiplied by the terminal font size to get the actual line-height in pixels."),
type: 'number',
default: DEFAULT_LINE_HEIGHT
},
'terminal.integrated.fontWeight': {
type: 'string',
enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
description: nls.localize('terminal.integrated.fontWeight', "The font weight to use within the terminal for non-bold text."),
default: 'normal'
},
'terminal.integrated.fontWeightBold': {
type: 'string',
enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
description: nls.localize('terminal.integrated.fontWeightBold', "The font weight to use within the terminal for bold text."),
default: 'bold'
},
'terminal.integrated.cursorBlinking': {
description: nls.localize('terminal.integrated.cursorBlinking', "Controls whether the terminal cursor blinks."),
type: 'boolean',
default: false
},
'terminal.integrated.cursorStyle': {
description: nls.localize('terminal.integrated.cursorStyle', "Controls the style of terminal cursor."),
enum: [TerminalCursorStyle.BLOCK, TerminalCursorStyle.LINE, TerminalCursorStyle.UNDERLINE],
default: TerminalCursorStyle.BLOCK
},
'terminal.integrated.scrollback': {
description: nls.localize('terminal.integrated.scrollback', "Controls the maximum amount of lines the terminal keeps in its buffer."),
type: 'number',
default: 1000
},
'terminal.integrated.setLocaleVariables': {
markdownDescription: nls.localize('terminal.integrated.setLocaleVariables', "Controls whether locale variables are set at startup of the terminal."),
type: 'boolean',
default: true
},
'terminal.integrated.rendererType': {
type: 'string',
enum: ['auto', 'canvas', 'dom'],
enumDescriptions: [
nls.localize('terminal.integrated.rendererType.auto', "Let VS Code guess which renderer to use."),
nls.localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer"),
nls.localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer.")
],
default: 'auto',
description: nls.localize('terminal.integrated.rendererType', "Controls how the terminal is rendered.")
},
'terminal.integrated.rightClickBehavior': {
type: 'string',
enum: ['default', 'copyPaste', 'selectWord'],
enumDescriptions: [
nls.localize('terminal.integrated.rightClickBehavior.default', "Show the context menu."),
nls.localize('terminal.integrated.rightClickBehavior.copyPaste', "Copy when there is a selection, otherwise paste."),
nls.localize('terminal.integrated.rightClickBehavior.selectWord', "Select the word under the cursor and show the context menu.")
],
default: platform.isMacintosh ? 'selectWord' : platform.isWindows ? 'copyPaste' : 'default',
description: nls.localize('terminal.integrated.rightClickBehavior', "Controls how terminal reacts to right click.")
},
'terminal.integrated.cwd': {
description: nls.localize('terminal.integrated.cwd', "An explicit start path where the terminal will be launched, this is used as the current working directory (cwd) for the shell process. This may be particularly useful in workspace settings if the root directory is not a convenient cwd."),
type: 'string',
default: undefined
},
'terminal.integrated.confirmOnExit': {
description: nls.localize('terminal.integrated.confirmOnExit', "Controls whether to confirm on exit if there are active terminal sessions."),
type: 'boolean',
default: false
},
'terminal.integrated.enableBell': {
description: nls.localize('terminal.integrated.enableBell', "Controls whether the terminal bell is enabled."),
type: 'boolean',
default: false
},
'terminal.integrated.commandsToSkipShell': {
description: nls.localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open.\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n')),
type: 'array',
items: {
type: 'string'
},
default: []
},
'terminal.integrated.env.osx': {
markdownDescription: nls.localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the VS Code process to be used by the terminal on macOS. Set to `null` to delete the environment variable."),
type: 'object',
additionalProperties: {
type: ['string', 'null']
},
default: {}
},
'terminal.integrated.env.linux': {
markdownDescription: nls.localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Linux. Set to `null` to delete the environment variable."),
type: 'object',
additionalProperties: {
type: ['string', 'null']
},
default: {}
},
'terminal.integrated.env.windows': {
markdownDescription: nls.localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Windows. Set to `null` to delete the environment variable."),
type: 'object',
additionalProperties: {
type: ['string', 'null']
},
default: {}
},
'terminal.integrated.showExitAlert': {
description: nls.localize('terminal.integrated.showExitAlert', "Controls whether to show the alert \"The terminal process terminated with exit code\" when exit code is non-zero."),
type: 'boolean',
default: true
},
'terminal.integrated.splitCwd': {
description: nls.localize('terminal.integrated.splitCwd', "Controls the working directory a split terminal starts with."),
type: 'string',
enum: ['workspaceRoot', 'initial', 'inherited'],
enumDescriptions: [
nls.localize('terminal.integrated.splitCwd.workspaceRoot', "A new split terminal will use the workspace root as the working directory. In a multi-root workspace a choice for which root folder to use is offered."),
nls.localize('terminal.integrated.splitCwd.initial', "A new split terminal will use the working directory that the parent terminal started with."),
nls.localize('terminal.integrated.splitCwd.inherited', "On macOS and Linux, a new split terminal will use the working directory of the parent terminal. On Windows, this behaves the same as initial."),
],
default: 'inherited'
},
'terminal.integrated.windowsEnableConpty': {
description: nls.localize('terminal.integrated.windowsEnableConpty', "Whether to use ConPTY for Windows terminal process communication (requires Windows 10 build number 18309+). Winpty will be used if this is false."),
type: 'boolean',
default: false
}
}
});
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenTermAction, QuickOpenTermAction.ID, QuickOpenTermAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal"));
const actionBarRegistry = Registry.as<IActionBarRegistry>(ActionBarExtensions.Actionbar);
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTermContributor);
(<panel.PanelRegistry>Registry.as(panel.Extensions.Panels)).registerPanel(new panel.PanelDescriptor(
TerminalPanel,
TERMINAL_PANEL_ID,
nls.localize('terminal', "Terminal"),
'terminal',
40,
TERMINAL_COMMAND_ID.TOGGLE
));
// On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl
const category = nls.localize('terminalCategory', "Terminal");
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), 'Terminal: Kill the Active Terminal Instance', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C }
}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FOCUS)), 'Terminal: Copy Selection', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKTICK,
mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_BACKTICK }
}), 'Terminal: Create New Integrated Terminal', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearSelectionTerminalAction, ClearSelectionTerminalAction.ID, ClearSelectionTerminalAction.LABEL, {
primary: KeyCode.Escape,
linux: { primary: KeyCode.Escape }
}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE)), 'Terminal: Escape selection', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewInActiveWorkspaceTerminalAction, CreateNewInActiveWorkspaceTerminalAction.ID, CreateNewInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (In Active Workspace)', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveTerminalAction, FocusActiveTerminalAction.ID, FocusActiveTerminalAction.LABEL), 'Terminal: Focus Terminal', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL), 'Terminal: Focus Next Terminal', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL), 'Terminal: Focus Previous Terminal', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_V,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V },
// Don't apply to Mac since cmd+v works
mac: { primary: 0 }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL, {
// Don't use ctrl+a by default as that would override the common go to start
// of prompt shell binding
primary: 0,
// Technically this doesn't need to be here as it will fall back to this
// behavior anyway when handed to xterm.js, having this handled by VS Code
// makes it easier for users to see how it works though.
mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_A }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select All', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunSelectedTextInTerminalAction, RunSelectedTextInTerminalAction.ID, RunSelectedTextInTerminalAction.LABEL), 'Terminal: Run Selected Text In Active Terminal', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunActiveFileInTerminalAction, RunActiveFileInTerminalAction.ID, RunActiveFileInTerminalAction.LABEL), 'Terminal: Run Active File In Active Terminal', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTerminalAction, ToggleTerminalAction.ID, ToggleTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.US_BACKTICK,
mac: { primary: KeyMod.WinCtrl | KeyCode.US_BACKTICK }
}), 'View: Toggle Integrated Terminal', nls.localize('viewCategory', "View"));
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollDownTerminalAction, ScrollDownTerminalAction.ID, ScrollDownTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Down (Line)', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollDownPageTerminalAction, ScrollDownPageTerminalAction.ID, ScrollDownPageTerminalAction.LABEL, {
primary: KeyMod.Shift | KeyCode.PageDown,
mac: { primary: KeyCode.PageDown }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Down (Page)', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToBottomTerminalAction, ScrollToBottomTerminalAction.ID, ScrollToBottomTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.End,
linux: { primary: KeyMod.Shift | KeyCode.End }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll to Bottom', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollUpTerminalAction, ScrollUpTerminalAction.ID, ScrollUpTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow },
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Up (Line)', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollUpPageTerminalAction, ScrollUpPageTerminalAction.ID, ScrollUpPageTerminalAction.LABEL, {
primary: KeyMod.Shift | KeyCode.PageUp,
mac: { primary: KeyCode.PageUp }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Up (Page)', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToTopTerminalAction, ScrollToTopTerminalAction.ID, ScrollToTopTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.Home,
linux: { primary: KeyMod.Shift | KeyCode.Home }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll to Top', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAction, ClearTerminalAction.ID, ClearTerminalAction.LABEL, {
primary: 0,
mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category);
if (platform.isWindows) {
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category);
}
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowWorkspaceShellTerminalCommand, AllowWorkspaceShellTerminalCommand.ID, AllowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Allow Workspace Shell Configuration', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand.ID, DisallowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Disallow Workspace Shell Configuration', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_F
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Find Widget', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_F
}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Focus Find Widget', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(HideTerminalFindWidgetAction, HideTerminalFindWidgetAction.ID, HideTerminalFindWidgetAction.LABEL, {
primary: KeyCode.Escape,
secondary: [KeyMod.Shift | KeyCode.Escape]
}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE)), 'Terminal: Hide Find Widget', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DeleteWordLeftTerminalAction, DeleteWordLeftTerminalAction.ID, DeleteWordLeftTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.Backspace,
mac: { primary: KeyMod.Alt | KeyCode.Backspace }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete Word Left', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DeleteWordRightTerminalAction, DeleteWordRightTerminalAction.ID, DeleteWordRightTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.Delete,
mac: { primary: KeyMod.Alt | KeyCode.Delete }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete Word Right', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DeleteToLineStartTerminalAction, DeleteToLineStartTerminalAction.ID, DeleteToLineStartTerminalAction.LABEL, {
primary: 0,
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete To Line Start', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(MoveToLineStartTerminalAction, MoveToLineStartTerminalAction.ID, MoveToLineStartTerminalAction.LABEL, {
primary: 0,
mac: { primary: KeyMod.CtrlCmd | KeyCode.LeftArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line Start', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(MoveToLineEndTerminalAction, MoveToLineEndTerminalAction.ID, MoveToLineEndTerminalAction.LABEL, {
primary: 0,
mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line End', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_5],
mac: {
primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH,
secondary: [KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_5]
}
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitInActiveWorkspaceTerminalAction, SplitInActiveWorkspaceTerminalAction.ID, SplitInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Split Terminal (In Active Workspace)', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousPaneTerminalAction, FocusPreviousPaneTerminalAction.ID, FocusPreviousPaneTerminalAction.LABEL, {
primary: KeyMod.Alt | KeyCode.LeftArrow,
secondary: [KeyMod.Alt | KeyCode.UpArrow],
mac: {
primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.LeftArrow,
secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.UpArrow]
}
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Previous Pane', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextPaneTerminalAction, FocusNextPaneTerminalAction.ID, FocusNextPaneTerminalAction.LABEL, {
primary: KeyMod.Alt | KeyCode.RightArrow,
secondary: [KeyMod.Alt | KeyCode.DownArrow],
mac: {
primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.RightArrow,
secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.DownArrow]
}
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Next Pane', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneLeftTerminalAction, ResizePaneLeftTerminalAction.ID, ResizePaneLeftTerminalAction.LABEL, {
primary: 0,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow },
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Left', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneRightTerminalAction, ResizePaneRightTerminalAction.ID, ResizePaneRightTerminalAction.LABEL, {
primary: 0,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow },
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Right', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneUpTerminalAction, ResizePaneUpTerminalAction.ID, ResizePaneUpTerminalAction.LABEL, {
primary: 0,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow },
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Up', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTerminalAction, ResizePaneDownTerminalAction.ID, ResizePaneDownTerminalAction.LABEL, {
primary: 0,
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(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, {
primary: 0,
mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll To Previous Command', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToNextCommandAction, ScrollToNextCommandAction.ID, ScrollToNextCommandAction.LABEL, {
primary: 0,
mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll To Next Command', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToPreviousCommandAction, SelectToPreviousCommandAction.ID, SelectToPreviousCommandAction.LABEL, {
primary: 0,
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: 0,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select To Next Command', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToPreviousLineAction, SelectToPreviousLineAction.ID, SelectToPreviousLineAction.LABEL), 'Terminal: Select To Previous Line', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToNextLineAction, SelectToNextLineAction.ID, SelectToNextLineAction.LABEL), 'Terminal: Select To Next Line', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEscapeSequenceLoggingAction, ToggleEscapeSequenceLoggingAction.ID, ToggleEscapeSequenceLoggingAction.LABEL), 'Terminal: Toggle Escape Sequence Logging', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, {
primary: KeyMod.Alt | KeyCode.KEY_R,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R }
}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find by regex');
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRegexCommand, ToggleRegexCommand.ID_TERMINAL_FOCUS, ToggleRegexCommand.LABEL, {
primary: KeyMod.Alt | KeyCode.KEY_R,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find by regex', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, {
primary: KeyMod.Alt | KeyCode.KEY_W,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W }
}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find whole word');
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleWholeWordCommand, ToggleWholeWordCommand.ID_TERMINAL_FOCUS, ToggleWholeWordCommand.LABEL, {
primary: KeyMod.Alt | KeyCode.KEY_W,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find whole word', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, {
primary: KeyMod.Alt | KeyCode.KEY_C,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C }
}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find match case');
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID_TERMINAL_FOCUS, ToggleCaseSensitiveCommand.LABEL, {
primary: KeyMod.Alt | KeyCode.KEY_C,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find match case', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindNext, FindNext.ID_TERMINAL_FOCUS, FindNext.LABEL, {
primary: KeyCode.F3,
mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find next', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindNext, FindNext.ID, FindNext.LABEL, {
primary: KeyCode.F3,
mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] }
}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find next');
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, FindPrevious.ID_TERMINAL_FOCUS, FindPrevious.LABEL, {
primary: KeyMod.Shift | KeyCode.F3,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3] },
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find previous', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, {
primary: KeyMod.Shift | KeyCode.F3,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3] },
}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find previous');
const sendSequenceTerminalCommand = new SendSequenceTerminalCommand({
id: SendSequenceTerminalCommand.ID,
precondition: null,
description: {
description: `Send Custom Sequence To Terminal`,
args: [{
name: 'args',
schema: {
'type': 'object',
'required': ['text'],
'properties': {
'text': {
'type': 'string'
}
},
}
}]
}
});
sendSequenceTerminalCommand.register();
setupTerminalCommands();
setupTerminalMenu();
registerColors();

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Terminal as XTermTerminal } from 'vscode-xterm';
import { ITerminalInstance, IWindowsShellHelper, ITerminalProcessManager, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProcessEnvironment } from 'vs/base/common/platform';
export const ITerminalInstanceService = createDecorator<ITerminalInstanceService>('terminalInstanceService');
export interface ITerminalInstanceService {
_serviceBrand: any;
getXtermConstructor(): Promise<typeof XTermTerminal>;
createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper;
createTerminalProcessManager(id: number, configHelper: ITerminalConfigHelper): ITerminalProcessManager;
createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess;
}
export interface IBrowserTerminalConfigHelper extends ITerminalConfigHelper {
panelContainer: HTMLElement;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,279 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Terminal, IMarker } from 'vscode-xterm';
import { ITerminalCommandTracker } from 'vs/workbench/contrib/terminal/common/terminal';
import { IDisposable } 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
}
export const enum ScrollPosition {
Top,
Middle
}
export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposable {
private _currentMarker: IMarker | Boundary = Boundary.Bottom;
private _selectionStart: IMarker | Boundary | null = null;
private _isDisposable: boolean = false;
constructor(
private _xterm: Terminal
) {
this._xterm.on('key', key => this._onKey(key));
}
public 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;
}
private _onEnter(): void {
if (this._xterm._core.buffer.x >= MINIMUM_PROMPT_LENGTH) {
this._xterm.addMarker(0);
}
}
public scrollToPreviousCommand(scrollPosition: ScrollPosition = ScrollPosition.Top, 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 if (this._isDisposable) {
markerIndex = this._findPreviousCommand();
this._currentMarker.dispose();
this._isDisposable = false;
} 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._scrollToMarker(this._currentMarker, scrollPosition);
}
public scrollToNextCommand(scrollPosition: ScrollPosition = ScrollPosition.Top, 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 if (this._isDisposable) {
markerIndex = this._findNextCommand();
this._currentMarker.dispose();
this._isDisposable = false;
} 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._scrollToMarker(this._currentMarker, scrollPosition);
}
private _scrollToMarker(marker: IMarker, position: ScrollPosition): void {
let line = marker.line;
if (position === ScrollPosition.Middle) {
line = Math.max(line - this._xterm.rows / 2, 0);
}
this._xterm.scrollToLine(line);
}
public selectToPreviousCommand(): void {
if (this._selectionStart === null) {
this._selectionStart = this._currentMarker;
}
this.scrollToPreviousCommand(ScrollPosition.Middle, true);
this._selectLines(this._currentMarker, this._selectionStart);
}
public selectToNextCommand(): void {
if (this._selectionStart === null) {
this._selectionStart = this._currentMarker;
}
this.scrollToNextCommand(ScrollPosition.Middle, true);
this._selectLines(this._currentMarker, this._selectionStart);
}
public selectToPreviousLine(): void {
if (this._selectionStart === null) {
this._selectionStart = this._currentMarker;
}
this.scrollToPreviousLine(ScrollPosition.Middle, true);
this._selectLines(this._currentMarker, this._selectionStart);
}
public selectToNextLine(): void {
if (this._selectionStart === null) {
this._selectionStart = this._currentMarker;
}
this.scrollToNextLine(ScrollPosition.Middle, true);
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._core.buffer.ybase + this._xterm.rows - 1;
}
if (marker === Boundary.Top) {
return 0;
}
return marker.line;
}
public scrollToPreviousLine(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void {
if (!retainSelection) {
this._selectionStart = null;
}
if (this._currentMarker === Boundary.Top) {
this._xterm.scrollToTop();
return;
}
if (this._currentMarker === Boundary.Bottom) {
this._currentMarker = this._xterm.addMarker(this._getOffset() - 1);
} else {
const offset = this._getOffset();
if (this._isDisposable) {
this._currentMarker.dispose();
}
this._currentMarker = this._xterm.addMarker(offset - 1);
}
this._isDisposable = true;
this._scrollToMarker(this._currentMarker, scrollPosition);
}
public scrollToNextLine(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void {
if (!retainSelection) {
this._selectionStart = null;
}
if (this._currentMarker === Boundary.Bottom) {
this._xterm.scrollToBottom();
return;
}
if (this._currentMarker === Boundary.Top) {
this._currentMarker = this._xterm.addMarker(this._getOffset() + 1);
} else {
const offset = this._getOffset();
if (this._isDisposable) {
this._currentMarker.dispose();
}
this._currentMarker = this._xterm.addMarker(offset + 1);
}
this._isDisposable = true;
this._scrollToMarker(this._currentMarker, scrollPosition);
}
private _getOffset(): number {
if (this._currentMarker === Boundary.Bottom) {
return 0;
} else if (this._currentMarker === Boundary.Top) {
return 0 - (this._xterm._core.buffer.ybase + this._xterm._core.buffer.y);
} else {
let offset = this._getLine(this._currentMarker);
offset -= this._xterm._core.buffer.ybase + this._xterm._core.buffer.y;
return offset;
}
}
private _findPreviousCommand(): number {
if (this._currentMarker === Boundary.Top) {
return 0;
} else if (this._currentMarker === Boundary.Bottom) {
return this._xterm.markers.length - 1;
}
let i;
for (i = this._xterm.markers.length - 1; i >= 0; i--) {
if (this._xterm.markers[i].line < this._currentMarker.line) {
return i;
}
}
return -1;
}
private _findNextCommand(): number {
if (this._currentMarker === Boundary.Top) {
return 0;
} else if (this._currentMarker === Boundary.Bottom) {
return this._xterm.markers.length - 1;
}
let i;
for (i = 0; i < this._xterm.markers.length; i++) {
if (this._xterm.markers[i].line > this._currentMarker.line) {
return i;
}
}
return this._xterm.markers.length;
}
}

View File

@@ -0,0 +1,268 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITerminalConfiguration, ITerminalFont, IShellLaunchConfig, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal';
import Severity from 'vs/base/common/severity';
import { Terminal as XTermTerminal } from 'vscode-xterm';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal';
const MINIMUM_FONT_SIZE = 6;
const MAXIMUM_FONT_SIZE = 25;
/**
* Encapsulates terminal configuration logic, the primary purpose of this file is so that platform
* specific test cases can be written.
*/
export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
public panelContainer: HTMLElement;
private _charMeasureElement: HTMLElement;
private _lastFontMeasurement: ITerminalFont;
public config: ITerminalConfiguration;
public constructor(
private readonly _linuxDistro: LinuxDistro,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IConfigurationService private readonly _workspaceConfigurationService: IConfigurationService,
@INotificationService private readonly _notificationService: INotificationService,
@IStorageService private readonly _storageService: IStorageService
) {
this._updateConfig();
this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(TERMINAL_CONFIG_SECTION)) {
this._updateConfig();
}
});
}
private _updateConfig(): void {
this.config = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
}
public configFontIsMonospace(): boolean {
this._createCharMeasureElementIfNecessary();
const fontSize = 15;
const fontFamily = this.config.fontFamily || this._configurationService.getValue<IEditorOptions>('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily;
const i_rect = this._getBoundingRectFor('i', fontFamily, fontSize);
const w_rect = this._getBoundingRectFor('w', fontFamily, fontSize);
const invalidBounds = !i_rect.width || !w_rect.width;
if (invalidBounds) {
// There is no reason to believe the font is not Monospace.
return true;
}
return i_rect.width === w_rect.width;
}
private _createCharMeasureElementIfNecessary() {
// Create charMeasureElement if it hasn't been created or if it was orphaned by its parent
if (!this._charMeasureElement || !this._charMeasureElement.parentElement) {
this._charMeasureElement = document.createElement('div');
this.panelContainer.appendChild(this._charMeasureElement);
}
}
private _getBoundingRectFor(char: string, fontFamily: string, fontSize: number): ClientRect | DOMRect {
const style = this._charMeasureElement.style;
style.display = 'inline-block';
style.fontFamily = fontFamily;
style.fontSize = fontSize + 'px';
style.lineHeight = 'normal';
this._charMeasureElement.innerText = char;
const rect = this._charMeasureElement.getBoundingClientRect();
style.display = 'none';
return rect;
}
private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont {
this._createCharMeasureElementIfNecessary();
const rect = this._getBoundingRectFor('X', fontFamily, fontSize);
// Bounding client rect was invalid, use last font measurement if available.
if (this._lastFontMeasurement && !rect.width && !rect.height) {
return this._lastFontMeasurement;
}
this._lastFontMeasurement = {
fontFamily,
fontSize,
letterSpacing,
lineHeight,
charWidth: rect.width,
charHeight: Math.ceil(rect.height)
};
return this._lastFontMeasurement;
}
/**
* Gets the font information based on the terminal.integrated.fontFamily
* terminal.integrated.fontSize, terminal.integrated.lineHeight configuration properties
*/
public getFont(xterm?: XTermTerminal, excludeDimensions?: boolean): ITerminalFont {
const editorConfig = this._configurationService.getValue<IEditorOptions>('editor');
let fontFamily = this.config.fontFamily || editorConfig.fontFamily || EDITOR_FONT_DEFAULTS.fontFamily;
let fontSize = this._toInteger(this.config.fontSize, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize);
// Work around bad font on Fedora/Ubuntu
if (!this.config.fontFamily) {
if (this._linuxDistro === LinuxDistro.Fedora) {
fontFamily = '\'DejaVu Sans Mono\', monospace';
}
if (this._linuxDistro === LinuxDistro.Ubuntu) {
fontFamily = '\'Ubuntu Mono\', monospace';
// Ubuntu mono is somehow smaller, so set fontSize a bit larger to get the same perceived size.
fontSize = this._toInteger(fontSize + 2, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize);
}
}
const letterSpacing = this.config.letterSpacing ? Math.max(Math.floor(this.config.letterSpacing), MINIMUM_LETTER_SPACING) : DEFAULT_LETTER_SPACING;
const lineHeight = this.config.lineHeight ? Math.max(this.config.lineHeight, 1) : DEFAULT_LINE_HEIGHT;
if (excludeDimensions) {
return {
fontFamily,
fontSize,
letterSpacing,
lineHeight
};
}
// Get the character dimensions from xterm if it's available
if (xterm) {
if (xterm._core.charMeasure && xterm._core.charMeasure.width && xterm._core.charMeasure.height) {
return {
fontFamily,
fontSize,
letterSpacing,
lineHeight,
charHeight: xterm._core.charMeasure.height,
charWidth: xterm._core.charMeasure.width
};
}
}
// Fall back to measuring the font ourselves
return this._measureFont(fontFamily, fontSize, letterSpacing, lineHeight);
}
public setWorkspaceShellAllowed(isAllowed: boolean): void {
this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, isAllowed, StorageScope.WORKSPACE);
}
public isWorkspaceShellAllowed(defaultValue: boolean | undefined = undefined): boolean | undefined {
return this._storageService.getBoolean(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, StorageScope.WORKSPACE, defaultValue);
}
public checkWorkspaceShellPermissions(platformOverride: platform.Platform = platform.platform): boolean {
// Check whether there is a workspace setting
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
const shellConfigValue = this._workspaceConfigurationService.inspect<string>(`terminal.integrated.shell.${platformKey}`);
const shellArgsConfigValue = this._workspaceConfigurationService.inspect<string[]>(`terminal.integrated.shellArgs.${platformKey}`);
const envConfigValue = this._workspaceConfigurationService.inspect<string[]>(`terminal.integrated.env.${platformKey}`);
// Check if workspace setting exists and whether it's whitelisted
let isWorkspaceShellAllowed: boolean | undefined = false;
if (shellConfigValue.workspace !== undefined || shellArgsConfigValue.workspace !== undefined || envConfigValue.workspace !== undefined) {
isWorkspaceShellAllowed = this.isWorkspaceShellAllowed(undefined);
}
// Always allow [] args as it would lead to an odd error message and should not be dangerous
if (shellConfigValue.workspace === undefined && envConfigValue.workspace === undefined &&
shellArgsConfigValue.workspace && shellArgsConfigValue.workspace.length === 0) {
isWorkspaceShellAllowed = true;
}
// Check if the value is neither blacklisted (false) or whitelisted (true) and ask for
// permission
if (isWorkspaceShellAllowed === undefined) {
let shellString: string | undefined;
if (shellConfigValue.workspace) {
shellString = `shell: "${shellConfigValue.workspace}"`;
}
let argsString: string | undefined;
if (shellArgsConfigValue.workspace) {
argsString = `shellArgs: [${shellArgsConfigValue.workspace.map(v => '"' + v + '"').join(', ')}]`;
}
let envString: string | undefined;
if (envConfigValue.workspace) {
envString = `env: {${Object.keys(envConfigValue.workspace).map(k => `${k}:${envConfigValue.workspace![k]}`).join(', ')}}`;
}
// Should not be localized as it's json-like syntax referencing settings keys
const workspaceConfigStrings: string[] = [];
if (shellString) {
workspaceConfigStrings.push(shellString);
}
if (argsString) {
workspaceConfigStrings.push(argsString);
}
if (envString) {
workspaceConfigStrings.push(envString);
}
const workspaceConfigString = workspaceConfigStrings.join(', ');
this._notificationService.prompt(Severity.Info, nls.localize('terminal.integrated.allowWorkspaceShell', "Do you allow this workspace to modify your terminal shell? {0}", workspaceConfigString),
[{
label: nls.localize('allow', "Allow"),
run: () => this.setWorkspaceShellAllowed(true)
},
{
label: nls.localize('disallow', "Disallow"),
run: () => this.setWorkspaceShellAllowed(false)
}]
);
}
return !!isWorkspaceShellAllowed;
}
public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, platformOverride: platform.Platform = platform.platform): void {
const isWorkspaceShellAllowed = this.checkWorkspaceShellPermissions(platformOverride);
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
const shellConfigValue = this._workspaceConfigurationService.inspect<string>(`terminal.integrated.shell.${platformKey}`);
const shellArgsConfigValue = this._workspaceConfigurationService.inspect<string[]>(`terminal.integrated.shellArgs.${platformKey}`);
shell.executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || shellConfigValue.default;
shell.args = (isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.user) || shellArgsConfigValue.default;
// Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's
// safe to assume that this was used by accident as Sysnative does not
// exist and will break the terminal in non-WoW64 environments.
if ((platformOverride === platform.Platform.Windows) && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') && process.env.windir) {
const sysnativePath = path.join(process.env.windir, 'Sysnative').toLowerCase();
if (shell.executable.toLowerCase().indexOf(sysnativePath) === 0) {
shell.executable = path.join(process.env.windir, 'System32', shell.executable.substr(sysnativePath.length));
}
}
// Convert / to \ on Windows for convenience
if (platformOverride === platform.Platform.Windows) {
shell.executable = shell.executable.replace(/\//g, '\\');
}
}
private _toInteger(source: any, minimum: number, maximum: number, fallback: number): number {
let r = parseInt(source, 10);
if (isNaN(r)) {
return fallback;
}
if (typeof minimum === 'number') {
r = Math.max(minimum, r);
}
if (typeof maximum === 'number') {
r = Math.min(maximum, r);
}
return r;
}
}

View File

@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SimpleFindWidget } from 'vs/editor/contrib/find/simpleFindWidget';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/terminal/common/terminal';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
export class TerminalFindWidget extends SimpleFindWidget {
protected _findInputFocused: IContextKey<boolean>;
protected _findWidgetFocused: IContextKey<boolean>;
constructor(
findState: FindReplaceState,
@IContextViewService _contextViewService: IContextViewService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@ITerminalService private readonly _terminalService: ITerminalService
) {
super(_contextViewService, _contextKeyService, findState, true);
this._register(findState.onFindReplaceStateChange(() => {
this.show();
}));
this._findInputFocused = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED.bindTo(this._contextKeyService);
this._findWidgetFocused = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED.bindTo(this._contextKeyService);
}
public find(previous: boolean) {
const instance = this._terminalService.getActiveInstance();
if (instance !== null) {
if (previous) {
instance.findPrevious(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue() });
} else {
instance.findNext(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue() });
}
}
}
public hide() {
super.hide();
const instance = this._terminalService.getActiveInstance();
if (instance) {
instance.focus();
}
}
protected onInputChanged() {
// Ignore input changes for now
const instance = this._terminalService.getActiveInstance();
if (instance !== null) {
instance.findNext(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue(), incremental: true });
}
}
protected onFocusTrackerFocus() {
const instance = this._terminalService.getActiveInstance();
if (instance) {
instance.notifyFindWidgetFocusChanged(true);
}
this._findWidgetFocused.set(true);
}
protected onFocusTrackerBlur() {
const instance = this._terminalService.getActiveInstance();
if (instance) {
instance.notifyFindWidgetFocusChanged(false);
}
this._findWidgetFocused.reset();
}
protected onFindInputFocusTrackerFocus() {
this._findInputFocused.set(true);
}
protected onFindInputFocusTrackerBlur() {
this._findInputFocused.reset();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,374 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITerminalService, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITextEditorSelection } from 'vs/platform/editor/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IFileService } from 'vs/platform/files/common/files';
import { ILinkMatcherOptions } from 'vscode-xterm';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { posix, win32 } from 'vs/base/common/path';
const pathPrefix = '(\\.\\.?|\\~)';
const pathSeparatorClause = '\\/';
// '":; are allowed in paths but they are often separators so ignore them
// Also disallow \\ to prevent a catastropic backtracking case #24798
const excludedPathCharactersClause = '[^\\0\\s!$`&*()\\[\\]+\'":;\\\\]';
/** A regex that matches paths in the form /foo, ~/foo, ./foo, ../foo, foo/bar */
const unixLocalLinkClause = '((' + pathPrefix + '|(' + excludedPathCharactersClause + ')+)?(' + pathSeparatorClause + '(' + excludedPathCharactersClause + ')+)+)';
const winDrivePrefix = '[a-zA-Z]:';
const winPathPrefix = '(' + winDrivePrefix + '|\\.\\.?|\\~)';
const winPathSeparatorClause = '(\\\\|\\/)';
const winExcludedPathCharactersClause = '[^\\0<>\\?\\|\\/\\s!$`&*()\\[\\]+\'":;]';
/** A regex that matches paths in the form c:\foo, ~\foo, .\foo, ..\foo, foo\bar */
const winLocalLinkClause = '((' + winPathPrefix + '|(' + winExcludedPathCharactersClause + ')+)?(' + winPathSeparatorClause + '(' + winExcludedPathCharactersClause + ')+)+)';
/** As xterm reads from DOM, space in that case is nonbreaking char ASCII code - 160,
replacing space with nonBreakningSpace or space ASCII code - 32. */
const lineAndColumnClause = [
'((\\S*)", line ((\\d+)( column (\\d+))?))', // "(file path)", line 45 [see #40468]
'((\\S*) on line ((\\d+)(, column (\\d+))?))', // (file path) on line 8, column 13
'((\\S*):line ((\\d+)(, column (\\d+))?))', // (file path):line 8, column 13
'(([^\\s\\(\\)]*)(\\s?[\\(\\[](\\d+)(,\\s?(\\d+))?)[\\)\\]])', // (file path)(45), (file path) (45), (file path)(45,18), (file path) (45,18), (file path)(45, 18), (file path) (45, 18), also with []
'(([^:\\s\\(\\)<>\'\"\\[\\]]*)(:(\\d+))?(:(\\d+))?)' // (file path):336, (file path):336:9
].join('|').replace(/ /g, `[${'\u00A0'} ]`);
// Changing any regex may effect this value, hence changes this as well if required.
const winLineAndColumnMatchIndex = 12;
const unixLineAndColumnMatchIndex = 11;
// Each line and column clause have 6 groups (ie no. of expressions in round brackets)
const lineAndColumnClauseGroupCount = 6;
/** Higher than local link, lower than hypertext */
const CUSTOM_LINK_PRIORITY = -1;
/** Lowest */
const LOCAL_LINK_PRIORITY = -2;
export type XtermLinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void;
export type XtermLinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
interface IPath {
join(...paths: string[]): string;
normalize(path: string): string;
}
export class TerminalLinkHandler {
private _hoverDisposables: IDisposable[] = [];
private _mouseMoveDisposable: IDisposable;
private _widgetManager: TerminalWidgetManager;
private _processCwd: string;
private _gitDiffPreImagePattern: RegExp;
private _gitDiffPostImagePattern: RegExp;
private readonly _tooltipCallback: (event: MouseEvent, uri: string) => boolean | void;
constructor(
private _xterm: any,
private _platform: platform.Platform,
private readonly _processManager: ITerminalProcessManager,
@IOpenerService private readonly _openerService: IOpenerService,
@IEditorService private readonly _editorService: IEditorService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ITerminalService private readonly _terminalService: ITerminalService,
@IFileService private readonly _fileService: IFileService
) {
// Matches '--- a/src/file1', capturing 'src/file1' in group 1
this._gitDiffPreImagePattern = /^--- a\/(\S*)/;
// Matches '+++ b/src/file1', capturing 'src/file1' in group 1
this._gitDiffPostImagePattern = /^\+\+\+ b\/(\S*)/;
this._tooltipCallback = (e: MouseEvent) => {
if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') {
const target = (e.target as HTMLElement);
this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString());
} else {
this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString());
}
};
this.registerWebLinkHandler();
this.registerLocalLinkHandler();
this.registerGitDiffLinkHandlers();
}
public setWidgetManager(widgetManager: TerminalWidgetManager): void {
this._widgetManager = widgetManager;
}
public set processCwd(processCwd: string) {
this._processCwd = processCwd;
}
public registerCustomLinkHandler(regex: RegExp, handler: (uri: string) => void, matchIndex?: number, validationCallback?: XtermLinkMatcherValidationCallback): number {
const options: ILinkMatcherOptions = {
matchIndex,
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: CUSTOM_LINK_PRIORITY
};
if (validationCallback) {
options.validationCallback = (uri: string, callback: (isValid: boolean) => void) => validationCallback(uri, callback);
}
return this._xterm.registerLinkMatcher(regex, this._wrapLinkHandler(handler), options);
}
public registerWebLinkHandler(): void {
const wrappedHandler = this._wrapLinkHandler(uri => {
this._handleHypertextLink(uri);
});
this._xterm.webLinksInit(wrappedHandler, {
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateWebLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e)
});
}
public registerLocalLinkHandler(): void {
const wrappedHandler = this._wrapLinkHandler(url => {
this._handleLocalLink(url);
});
this._xterm.registerLinkMatcher(this._localLinkRegex, wrappedHandler, {
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: LOCAL_LINK_PRIORITY
});
}
public registerGitDiffLinkHandlers(): void {
const wrappedHandler = this._wrapLinkHandler(url => {
this._handleLocalLink(url);
});
const options = {
matchIndex: 1,
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => this._widgetManager.closeMessage(),
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: LOCAL_LINK_PRIORITY
};
this._xterm.registerLinkMatcher(this._gitDiffPreImagePattern, wrappedHandler, options);
this._xterm.registerLinkMatcher(this._gitDiffPostImagePattern, wrappedHandler, options);
}
public dispose(): void {
this._xterm = null;
this._hoverDisposables = dispose(this._hoverDisposables);
this._mouseMoveDisposable = dispose(this._mouseMoveDisposable);
}
private _wrapLinkHandler(handler: (uri: string) => boolean | void): XtermLinkMatcherHandler {
return (event: MouseEvent, uri: string) => {
// Prevent default electron link handling so Alt+Click mode works normally
event.preventDefault();
// Require correct modifier on click
if (!this._isLinkActivationModifierDown(event)) {
// If the modifier is not pressed, the terminal should be
// focused if it's not already
this._terminalService.getActiveInstance()!.focus(true);
return false;
}
return handler(uri);
};
}
protected get _localLinkRegex(): RegExp {
const baseLocalLinkClause = this._processManager.os === platform.OperatingSystem.Windows ? winLocalLinkClause : unixLocalLinkClause;
// Append line and column number regex
return new RegExp(`${baseLocalLinkClause}(${lineAndColumnClause})`);
}
protected get _gitDiffPreImageRegex(): RegExp {
return this._gitDiffPreImagePattern;
}
protected get _gitDiffPostImageRegex(): RegExp {
return this._gitDiffPostImagePattern;
}
private _handleLocalLink(link: string): PromiseLike<any> {
return this._resolvePath(link).then(resolvedLink => {
if (!resolvedLink) {
return Promise.resolve(null);
}
const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link);
const selection: ITextEditorSelection = {
startLineNumber: lineColumnInfo.lineNumber,
startColumn: lineColumnInfo.columnNumber
};
return this._editorService.openEditor({ resource: resolvedLink, options: { pinned: true, selection } });
});
}
private _validateLocalLink(link: string, callback: (isValid: boolean) => void): void {
this._resolvePath(link).then(resolvedLink => callback(!!resolvedLink));
}
private _validateWebLink(link: string, callback: (isValid: boolean) => void): void {
callback(true);
}
private _handleHypertextLink(url: string): void {
const uri = URI.parse(url);
this._openerService.open(uri);
}
private _isLinkActivationModifierDown(event: MouseEvent): boolean {
const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor');
if (editorConf.multiCursorModifier === 'ctrlCmd') {
return !!event.altKey;
}
return platform.isMacintosh ? event.metaKey : event.ctrlKey;
}
private _getLinkHoverString(): string {
const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor');
if (editorConf.multiCursorModifier === 'ctrlCmd') {
return nls.localize('terminalLinkHandler.followLinkAlt', 'Alt + click to follow link');
}
if (platform.isMacintosh) {
return nls.localize('terminalLinkHandler.followLinkCmd', 'Cmd + click to follow link');
}
return nls.localize('terminalLinkHandler.followLinkCtrl', 'Ctrl + click to follow link');
}
private get osPath(): IPath {
if (this._processManager.os === platform.OperatingSystem.Windows) {
return win32;
}
return posix;
}
protected _preprocessPath(link: string): string | null {
if (link.charAt(0) === '~') {
// Resolve ~ -> userHome
if (!this._processManager.userHome) {
return null;
}
link = this.osPath.join(this._processManager.userHome, link.substring(1));
} else if (link.charAt(0) !== '/' && link.charAt(0) !== '~') {
// Resolve workspace path . | .. | <relative_path> -> <path>/. | <path>/.. | <path>/<relative_path>
if (this._processManager.os === platform.OperatingSystem.Windows) {
if (!link.match('^' + winDrivePrefix)) {
if (!this._processCwd) {
// Abort if no workspace is open
return null;
}
link = this.osPath.join(this._processCwd, link);
}
} else {
if (!this._processCwd) {
// Abort if no workspace is open
return null;
}
link = this.osPath.join(this._processCwd, link);
}
}
link = this.osPath.normalize(link);
return link;
}
private _resolvePath(link: string): PromiseLike<URI | null> {
const preprocessedLink = this._preprocessPath(link);
if (!preprocessedLink) {
return Promise.resolve(null);
}
const linkUrl = this.extractLinkUrl(preprocessedLink);
if (!linkUrl) {
return Promise.resolve(null);
}
try {
let uri: URI;
if (this._processManager.remoteAuthority) {
uri = URI.from({
scheme: REMOTE_HOST_SCHEME,
authority: this._processManager.remoteAuthority,
path: linkUrl
});
} else {
uri = URI.file(linkUrl);
}
return this._fileService.resolveFile(uri).then(stat => {
if (stat.isDirectory) {
return null;
}
return uri;
}).catch(() => {
// Does not exist
return null;
});
} catch {
// Errors in parsing the path
return Promise.resolve(null);
}
}
/**
* Returns line and column number of URl if that is present.
*
* @param link Url link which may contain line and column number.
*/
public extractLineColumnInfo(link: string): LineColumnInfo {
const matches: string[] | null = this._localLinkRegex.exec(link);
const lineColumnInfo: LineColumnInfo = {
lineNumber: 1,
columnNumber: 1
};
if (!matches) {
return lineColumnInfo;
}
const lineAndColumnMatchIndex = this._platform === platform.Platform.Windows ? winLineAndColumnMatchIndex : unixLineAndColumnMatchIndex;
for (let i = 0; i < lineAndColumnClause.length; i++) {
const lineMatchIndex = lineAndColumnMatchIndex + (lineAndColumnClauseGroupCount * i);
const rowNumber = matches[lineMatchIndex];
if (rowNumber) {
lineColumnInfo['lineNumber'] = parseInt(rowNumber, 10);
// Check if column number exists
const columnNumber = matches[lineMatchIndex + 2];
if (columnNumber) {
lineColumnInfo['columnNumber'] = parseInt(columnNumber, 10);
}
break;
}
}
return lineColumnInfo;
}
/**
* Returns url from link as link may contain line and column information.
*
* @param link url link which may contain line and column number.
*/
public extractLinkUrl(link: string): string | null {
const matches: string[] | null = this._localLinkRegex.exec(link);
if (!matches) {
return null;
}
return matches[1];
}
}
export interface LineColumnInfo {
lineNumber: number;
columnNumber: number;
}

View File

@@ -0,0 +1,353 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import { Action, IAction } from 'vs/base/common/actions';
import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ITerminalService, TERMINAL_PANEL_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget';
import { editorHoverBackground, editorHoverBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { Panel } from 'vs/workbench/browser/panel';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { URI } from 'vs/base/common/uri';
import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
import { DataTransfers } from 'vs/base/browser/dnd';
import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
import { IStorageService } from 'vs/platform/storage/common/storage';
const FIND_FOCUS_CLASS = 'find-focused';
export class TerminalPanel extends Panel {
private _actions: IAction[];
private _copyContextMenuAction: IAction;
private _contextMenuActions: IAction[];
private _cancelContextMenu: boolean = false;
private _fontStyleElement: HTMLElement;
private _parentDomElement: HTMLElement;
private _terminalContainer: HTMLElement;
private _findWidget: TerminalFindWidget;
constructor(
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ITerminalService private readonly _terminalService: ITerminalService,
@IThemeService protected readonly _themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@INotificationService private readonly _notificationService: INotificationService,
@IStorageService storageService: IStorageService
) {
super(TERMINAL_PANEL_ID, telemetryService, _themeService, storageService);
}
public create(parent: HTMLElement): void {
super.create(parent);
this._parentDomElement = parent;
dom.addClass(this._parentDomElement, 'integrated-terminal');
this._fontStyleElement = document.createElement('style');
this._terminalContainer = document.createElement('div');
dom.addClass(this._terminalContainer, 'terminal-outer-container');
this._findWidget = this._instantiationService.createInstance(TerminalFindWidget, this._terminalService.getFindState());
this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer.classList.add(FIND_FOCUS_CLASS));
this._findWidget.focusTracker.onDidBlur(() => this._terminalContainer.classList.remove(FIND_FOCUS_CLASS));
this._parentDomElement.appendChild(this._fontStyleElement);
this._parentDomElement.appendChild(this._terminalContainer);
this._parentDomElement.appendChild(this._findWidget.getDomNode());
this._attachEventListeners();
this._terminalService.setContainers(this.getContainer(), this._terminalContainer);
this._register(this.themeService.onThemeChange(theme => this._updateTheme(theme)));
this._register(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fontFamily')) {
this._updateFont();
}
if (e.affectsConfiguration('terminal.integrated.fontFamily') || e.affectsConfiguration('editor.fontFamily')) {
const configHelper = this._terminalService.configHelper;
if (!configHelper.configFontIsMonospace()) {
const choices: IPromptChoice[] = [{
label: nls.localize('terminal.useMonospace', "Use 'monospace'"),
run: () => this._configurationService.updateValue('terminal.integrated.fontFamily', 'monospace'),
}];
this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts."), choices);
}
}
}));
this._updateFont();
this._updateTheme();
this._register(this.onDidChangeVisibility(visible => {
if (visible) {
if (this._terminalService.terminalInstances.length > 0) {
this._updateFont();
this._updateTheme();
} else {
// Check if instances were already restored as part of workbench restore
if (this._terminalService.terminalInstances.length === 0) {
this._terminalService.createTerminal();
}
if (this._terminalService.terminalInstances.length > 0) {
this._updateFont();
this._updateTheme();
}
}
}
}));
// Force another layout (first is setContainers) since config has changed
this.layout(new dom.Dimension(this._terminalContainer.offsetWidth, this._terminalContainer.offsetHeight));
}
public layout(dimension?: dom.Dimension): void {
if (!dimension) {
return;
}
this._terminalService.terminalTabs.forEach(t => t.layout(dimension.width, dimension.height));
}
public getActions(): IAction[] {
if (!this._actions) {
this._actions = [
this._instantiationService.createInstance(SwitchTerminalAction, SwitchTerminalAction.ID, SwitchTerminalAction.LABEL),
this._instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.SHORT_LABEL),
this._instantiationService.createInstance(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL),
this._instantiationService.createInstance(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.PANEL_LABEL)
];
this._actions.forEach(a => {
this._register(a);
});
}
return this._actions;
}
private _getContextMenuActions(): IAction[] {
if (!this._contextMenuActions) {
this._copyContextMenuAction = this._instantiationService.createInstance(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.SHORT_LABEL);
this._contextMenuActions = [
this._instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.SHORT_LABEL),
this._instantiationService.createInstance(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.SHORT_LABEL),
new Separator(),
this._copyContextMenuAction,
this._instantiationService.createInstance(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.SHORT_LABEL),
this._instantiationService.createInstance(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL),
new Separator(),
this._instantiationService.createInstance(ClearTerminalAction, ClearTerminalAction.ID, ClearTerminalAction.LABEL),
new Separator(),
this._instantiationService.createInstance(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.PANEL_LABEL)
];
this._contextMenuActions.forEach(a => {
this._register(a);
});
}
const activeInstance = this._terminalService.getActiveInstance();
this._copyContextMenuAction.enabled = !!activeInstance && activeInstance.hasSelection();
return this._contextMenuActions;
}
public getActionItem(action: Action): IActionItem | null {
if (action.id === SwitchTerminalAction.ID) {
return this._instantiationService.createInstance(SwitchTerminalActionItem, action);
}
return super.getActionItem(action);
}
public focus(): void {
const activeInstance = this._terminalService.getActiveInstance();
if (activeInstance) {
activeInstance.focusWhenReady(true);
}
}
public focusFindWidget() {
const activeInstance = this._terminalService.getActiveInstance();
if (activeInstance && activeInstance.hasSelection() && activeInstance.selection!.indexOf('\n') === -1) {
this._findWidget.reveal(activeInstance.selection);
} else {
this._findWidget.reveal();
}
}
public hideFindWidget() {
this._findWidget.hide();
}
public showFindWidget() {
const activeInstance = this._terminalService.getActiveInstance();
if (activeInstance && activeInstance.hasSelection() && activeInstance.selection!.indexOf('\n') === -1) {
this._findWidget.show(activeInstance.selection);
} else {
this._findWidget.show();
}
}
public getFindWidget(): TerminalFindWidget {
return this._findWidget;
}
private _attachEventListeners(): void {
this._register(dom.addDisposableListener(this._parentDomElement, 'mousedown', (event: MouseEvent) => {
if (this._terminalService.terminalInstances.length === 0) {
return;
}
if (event.which === 2 && platform.isLinux) {
// Drop selection and focus terminal on Linux to enable middle button paste when click
// occurs on the selection itself.
const terminal = this._terminalService.getActiveInstance();
if (terminal) {
terminal.focus();
}
} else if (event.which === 3) {
if (this._terminalService.configHelper.config.rightClickBehavior === 'copyPaste') {
const terminal = this._terminalService.getActiveInstance();
if (!terminal) {
return;
}
if (terminal.hasSelection()) {
terminal.copySelection();
terminal.clearSelection();
} else {
terminal.paste();
}
// Clear selection after all click event bubbling is finished on Mac to prevent
// right-click selecting a word which is seemed cannot be disabled. There is a
// flicker when pasting but this appears to give the best experience if the
// setting is enabled.
if (platform.isMacintosh) {
setTimeout(() => {
terminal.clearSelection();
}, 0);
}
this._cancelContextMenu = true;
}
}
}));
this._register(dom.addDisposableListener(this._parentDomElement, 'mouseup', (event: MouseEvent) => {
if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) {
if (this._terminalService.terminalInstances.length === 0) {
return;
}
if (event.which === 1) {
const terminal = this._terminalService.getActiveInstance();
if (terminal && terminal.hasSelection()) {
terminal.copySelection();
}
}
}
}));
this._register(dom.addDisposableListener(this._parentDomElement, 'contextmenu', (event: MouseEvent) => {
if (!this._cancelContextMenu) {
const standardEvent = new StandardMouseEvent(event);
const anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy };
this._contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => this._getContextMenuActions(),
getActionsContext: () => this._parentDomElement
});
} else {
event.stopImmediatePropagation();
}
this._cancelContextMenu = false;
}));
this._register(dom.addDisposableListener(document, 'keydown', (event: KeyboardEvent) => {
this._terminalContainer.classList.toggle('alt-active', !!event.altKey);
}));
this._register(dom.addDisposableListener(document, 'keyup', (event: KeyboardEvent) => {
this._terminalContainer.classList.toggle('alt-active', !!event.altKey);
}));
this._register(dom.addDisposableListener(this._parentDomElement, 'keyup', (event: KeyboardEvent) => {
if (event.keyCode === 27) {
// Keep terminal open on escape
event.stopPropagation();
}
}));
this._register(dom.addDisposableListener(this._parentDomElement, dom.EventType.DROP, async (e: DragEvent) => {
if (e.target === this._parentDomElement || dom.isAncestor(e.target as HTMLElement, this._parentDomElement)) {
if (!e.dataTransfer) {
return;
}
// Check if files were dragged from the tree explorer
let path: string | undefined;
const resources = e.dataTransfer.getData(DataTransfers.RESOURCES);
if (resources) {
path = URI.parse(JSON.parse(resources)[0]).fsPath;
} else if (e.dataTransfer.files.length > 0) {
// Check if the file was dragged from the filesystem
path = URI.file(e.dataTransfer.files[0].path).fsPath;
}
if (!path) {
return;
}
const terminal = this._terminalService.getActiveInstance();
if (terminal) {
return this._terminalService.preparePathForTerminalAsync(path, terminal.shellLaunchConfig.executable, terminal.title).then(preparedPath => {
terminal.sendText(preparedPath, false);
});
}
}
}));
}
private _updateTheme(theme?: ITheme): void {
if (!theme) {
theme = this.themeService.getTheme();
}
this._findWidget.updateTheme(theme);
}
private _updateFont(): void {
if (this._terminalService.terminalInstances.length === 0) {
return;
}
// TODO: Can we support ligatures?
// dom.toggleClass(this._parentDomElement, 'enable-ligatures', this._terminalService.configHelper.config.fontLigatures);
this.layout(new dom.Dimension(this._parentDomElement.offsetWidth, this._parentDomElement.offsetHeight));
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR);
collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-outer-container { background-color: ${backgroundColor ? backgroundColor.toString() : ''}; }`);
const borderColor = theme.getColor(TERMINAL_BORDER_COLOR);
if (borderColor) {
collector.addRule(`.monaco-workbench .panel.integrated-terminal .split-view-view:not(:first-child) { border-color: ${borderColor.toString()}; }`);
}
// Borrow the editor's hover background for now
const hoverBackground = theme.getColor(editorHoverBackground);
if (hoverBackground) {
collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { background-color: ${hoverBackground}; }`);
}
const hoverBorder = theme.getColor(editorHoverBorder);
if (hoverBorder) {
collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { border: 1px solid ${hoverBorder}; }`);
}
const hoverForeground = theme.getColor(editorForeground);
if (hoverForeground) {
collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { color: ${hoverForeground}; }`);
}
});

View File

@@ -0,0 +1,261 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as platform from 'vs/base/common/platform';
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal';
import { ILogService } from 'vs/platform/log/common/log';
import { Emitter, Event } from 'vs/base/common/event';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { Schemas } from 'vs/base/common/network';
import { REMOTE_HOST_SCHEME, getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { sanitizeProcessEnvironment } from 'vs/base/common/processes';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IProductService } from 'vs/platform/product/common/product';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IRemoteEnvironmentService } from 'vs/workbench/services/remote/common/remoteEnvironmentService';
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;
/**
* Holds all state related to the creation and management of terminal processes.
*
* Internal definitions:
* - Process: The process launched with the terminalProcess.ts file, or the pty as a whole
* - Pty Process: The pseudoterminal master process (or the winpty agent process)
* - Shell Process: The pseudoterminal slave process (ie. the shell)
*/
export class TerminalProcessManager implements ITerminalProcessManager {
public processState: ProcessState = ProcessState.UNINITIALIZED;
public ptyProcessReady: Promise<void>;
public shellProcessId: number;
public remoteAuthority: string | undefined;
public os: platform.OperatingSystem | undefined;
public userHome: string | undefined;
private _process: ITerminalChildProcess | null = null;
private _preLaunchInputQueue: string[] = [];
private _disposables: IDisposable[] = [];
private readonly _onProcessReady = new Emitter<void>();
public get onProcessReady(): Event<void> { return this._onProcessReady.event; }
private readonly _onProcessData = new Emitter<string>();
public get onProcessData(): Event<string> { return this._onProcessData.event; }
private readonly _onProcessTitle = new Emitter<string>();
public get onProcessTitle(): Event<string> { return this._onProcessTitle.event; }
private readonly _onProcessExit = new Emitter<number>();
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
constructor(
private readonly _terminalId: number,
private readonly _configHelper: ITerminalConfigHelper,
@IHistoryService private readonly _historyService: IHistoryService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService,
@IWindowService private readonly _windowService: IWindowService,
@IConfigurationService private readonly _workspaceConfigurationService: IConfigurationService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IProductService private readonly _productService: IProductService,
@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
@IRemoteEnvironmentService private readonly _remoteEnvironmentService: IRemoteEnvironmentService
) {
this.ptyProcessReady = new Promise<void>(c => {
this.onProcessReady(() => {
this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`);
c(undefined);
});
});
}
public dispose(immediate: boolean = false): void {
if (this._process) {
// If the process was still connected this dispose came from
// within VS Code, not the process, so mark the process as
// killed by the user.
this.processState = ProcessState.KILLED_BY_USER;
this._process.shutdown(immediate);
this._process = null;
}
this._disposables.forEach(d => d.dispose());
this._disposables.length = 0;
}
public addDisposable(disposable: IDisposable) {
this._disposables.push(disposable);
}
public createProcess(
shellLaunchConfig: IShellLaunchConfig,
cols: number,
rows: number
): void {
const forceExtHostProcess = (this._configHelper.config as any).extHostProcess;
if (shellLaunchConfig.cwd && typeof shellLaunchConfig.cwd === 'object') {
this.remoteAuthority = getRemoteAuthority(shellLaunchConfig.cwd);
} else {
this.remoteAuthority = this._windowService.getConfiguration().remoteAuthority;
}
const hasRemoteAuthority = !!this.remoteAuthority;
let launchRemotely = hasRemoteAuthority || forceExtHostProcess;
this.userHome = this._environmentService.userHome;
this.os = platform.OS;
if (launchRemotely) {
if (hasRemoteAuthority) {
this._remoteEnvironmentService.remoteEnvironment.then(env => {
if (!env) {
return;
}
this.userHome = env.userHome.path;
this.os = env.os;
});
}
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(hasRemoteAuthority ? REMOTE_HOST_SCHEME : undefined);
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
} else {
if (!shellLaunchConfig.executable) {
this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
}
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, this._environmentService.userHome, activeWorkspaceRootUri, this._configHelper.config.cwd);
// Compel type system as process.env should not have any undefined entries
let env: platform.IProcessEnvironment = {};
if (shellLaunchConfig.strictEnv) {
// Only base the terminal process environment on this environment and add the
// various mixins when strictEnv is false
env = { ...shellLaunchConfig.env } as any;
} else {
// Merge process env with the env from config and from shellLaunchConfig
env = { ...process.env } as any;
// Resolve env vars from config and shell
const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null;
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions();
const envFromConfigValue = this._workspaceConfigurationService.inspect<{ [key: string]: string }>(`terminal.integrated.env.${platformKey}`);
const allowedEnvFromConfig = (isWorkspaceShellAllowed ? envFromConfigValue.value : envFromConfigValue.user);
const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...allowedEnvFromConfig }, lastActiveWorkspaceRoot);
const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot);
shellLaunchConfig.env = envFromShell;
terminalEnvironment.mergeEnvironments(env, envFromConfig);
terminalEnvironment.mergeEnvironments(env, shellLaunchConfig.env);
// Sanitize the environment, removing any undesirable VS Code and Electron environment
// variables
sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI');
// Adding other env keys necessary to create the process
terminalEnvironment.addTerminalEnvironmentKeys(env, this._productService.version, platform.locale, this._configHelper.config.setLocaleVariables);
}
this._logService.debug(`Terminal process launching`, shellLaunchConfig, initialCwd, cols, rows, env);
this._process = this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, this._configHelper.config.windowsEnableConpty);
}
this.processState = ProcessState.LAUNCHING;
// The process is non-null, but TS isn't clever enough to know
const p = this._process!;
p.onProcessData(data => {
this._onProcessData.fire(data);
});
p.onProcessIdReady(pid => {
this.shellProcessId = pid;
this._onProcessReady.fire();
// Send any queued data that's waiting
if (this._preLaunchInputQueue.length > 0) {
p.input(this._preLaunchInputQueue.join(''));
this._preLaunchInputQueue.length = 0;
}
});
p.onProcessTitleChanged(title => this._onProcessTitle.fire(title));
p.onProcessExit(exitCode => this._onExit(exitCode));
setTimeout(() => {
if (this.processState === ProcessState.LAUNCHING) {
this.processState = ProcessState.RUNNING;
}
}, LAUNCHING_DURATION);
}
public setDimensions(cols: number, rows: number): void {
if (!this._process) {
return;
}
// The child process could already be terminated
try {
this._process.resize(cols, rows);
} catch (error) {
// We tried to write to a closed pipe / channel.
if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') {
throw (error);
}
}
}
public write(data: string): void {
if (this.shellProcessId) {
if (this._process) {
// Send data if the pty is ready
this._process.input(data);
}
} else {
// If the pty is not ready, queue the data received to send later
this._preLaunchInputQueue.push(data);
}
}
public getInitialCwd(): Promise<string> {
if (!this._process) {
return Promise.resolve('');
}
return this._process.getInitialCwd();
}
public getCwd(): Promise<string> {
if (!this._process) {
return Promise.resolve('');
}
return this._process.getCwd();
}
private _onExit(exitCode: number): void {
this._process = null;
// If the process is marked as launching then mark the process as killed
// during launch. This typically means that there is a problem with the
// shell and args.
if (this.processState === ProcessState.LAUNCHING) {
this.processState = ProcessState.KILLED_DURING_LAUNCH;
}
// If TerminalInstance did not know about the process exit then it was
// triggered by the process, not on VS Code's side.
if (this.processState === ProcessState.RUNNING) {
this.processState = ProcessState.KILLED_BY_PROCESS;
}
this._onProcessExit.fire(exitCode);
}
}

View File

@@ -0,0 +1,137 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/common/terminal';
import { ContributableActionProvider } from 'vs/workbench/browser/actions';
import { stripWildcards } from 'vs/base/common/strings';
import { matchesFuzzy } from 'vs/base/common/filters';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CancellationToken } from 'vs/base/common/cancellation';
export class TerminalEntry extends QuickOpenEntry {
constructor(
public instance: ITerminalInstance,
private label: string,
private terminalService: ITerminalService
) {
super();
}
public getLabel(): string {
return this.label;
}
public getAriaLabel(): string {
return nls.localize('termEntryAriaLabel', "{0}, terminal picker", this.getLabel());
}
public run(mode: Mode, context: IEntryRunContext): boolean {
if (mode === Mode.OPEN) {
setTimeout(() => {
this.terminalService.setActiveInstance(this.instance);
this.terminalService.showPanel(true);
}, 0);
return true;
}
return super.run(mode, context);
}
}
export class CreateTerminal extends QuickOpenEntry {
constructor(
private label: string,
private commandService: ICommandService
) {
super();
}
public getLabel(): string {
return this.label;
}
public getAriaLabel(): string {
return nls.localize('termCreateEntryAriaLabel', "{0}, create new terminal", this.getLabel());
}
public run(mode: Mode, context: IEntryRunContext): boolean {
if (mode === Mode.OPEN) {
setTimeout(() => this.commandService.executeCommand('workbench.action.terminal.new'), 0);
return true;
}
return super.run(mode, context);
}
}
export class TerminalPickerHandler extends QuickOpenHandler {
public static readonly ID = 'workbench.picker.terminals';
constructor(
@ITerminalService private readonly terminalService: ITerminalService,
@ICommandService private readonly commandService: ICommandService,
) {
super();
}
public getResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
searchValue = searchValue.trim();
const normalizedSearchValueLowercase = stripWildcards(searchValue).toLowerCase();
const terminalEntries: QuickOpenEntry[] = this.getTerminals();
terminalEntries.push(new CreateTerminal(nls.localize("workbench.action.terminal.newplus", "$(plus) Create New Integrated Terminal"), this.commandService));
const entries = terminalEntries.filter(e => {
if (!searchValue) {
return true;
}
const label = e.getLabel();
if (!label) {
return false;
}
const highlights = matchesFuzzy(normalizedSearchValueLowercase, label, true);
if (!highlights) {
return false;
}
e.setHighlights(highlights);
return true;
});
return Promise.resolve(new QuickOpenModel(entries, new ContributableActionProvider()));
}
private getTerminals(): TerminalEntry[] {
return this.terminalService.terminalTabs.reduce((terminals, tab, tabIndex) => {
const terminalsInTab = tab.terminalInstances.map((terminal, terminalIndex) => {
const label = `${tabIndex + 1}.${terminalIndex + 1}: ${terminal.title}`;
return new TerminalEntry(terminal, label, this.terminalService);
});
return [...terminals, ...terminalsInTab];
}, []);
}
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
return {
autoFocusFirstEntry: !!searchValue || !!context.quickNavigateConfiguration
};
}
public getEmptyLabel(searchString: string): string {
if (searchString.length > 0) {
return nls.localize('noTerminalsMatching', "No terminals matching");
}
return nls.localize('noTerminalsFound', "No terminals open");
}
}

View File

@@ -0,0 +1,182 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalService as CommonTerminalService } from 'vs/workbench/contrib/terminal/common/terminalService';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal';
export abstract class TerminalService extends CommonTerminalService implements ITerminalService {
protected _configHelper: IBrowserTerminalConfigHelper;
constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@IPanelService panelService: IPanelService,
@IWorkbenchLayoutService private _layoutService: IWorkbenchLayoutService,
@ILifecycleService lifecycleService: ILifecycleService,
@IStorageService storageService: IStorageService,
@INotificationService notificationService: INotificationService,
@IDialogService dialogService: IDialogService,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IWindowService private _windowService: IWindowService,
@IExtensionService extensionService: IExtensionService,
@IFileService fileService: IFileService,
) {
super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService);
}
protected abstract _getDefaultShell(p: platform.Platform): string;
public createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance {
const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig);
this._onInstanceCreated.fire(instance);
return instance;
}
public createTerminal(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance {
const terminalTab = this._instantiationService.createInstance(TerminalTab,
this._terminalFocusContextKey,
this.configHelper,
this._terminalContainer,
shell);
this._terminalTabs.push(terminalTab);
const instance = terminalTab.terminalInstances[0];
terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed));
terminalTab.addDisposable(terminalTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged));
this._initInstanceListeners(instance);
if (this.terminalInstances.length === 1) {
// It's the first instance so it should be made active automatically
this.setActiveInstanceByIndex(0);
}
this._onInstancesChanged.fire();
this._suggestShellChange(wasNewTerminalAction);
return instance;
}
private _suggestShellChange(wasNewTerminalAction?: boolean): void {
// Only suggest on Windows since $SHELL works great for macOS/Linux
if (!platform.isWindows) {
return;
}
if (this._windowService.getConfiguration().remoteAuthority) {
// Don't suggest if the opened workspace is remote
return;
}
// Only suggest when the terminal instance is being created by an explicit user action to
// launch a terminal, as opposed to something like tasks, debug, panel restore, etc.
if (!wasNewTerminalAction) {
return;
}
if (this._windowService.getConfiguration().remoteAuthority) {
// Don't suggest if the opened workspace is remote
return;
}
// Don't suggest if the user has explicitly opted out
const neverSuggest = this._storageService.getBoolean(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, StorageScope.GLOBAL, false);
if (neverSuggest) {
return;
}
// Never suggest if the setting is non-default already (ie. they set the setting manually)
if (this.configHelper.config.shell.windows !== this._getDefaultShell(platform.Platform.Windows)) {
this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL);
return;
}
this._notificationService.prompt(
Severity.Info,
nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button."),
[{
label: nls.localize('customize', "Customize"),
run: () => {
this.selectDefaultWindowsShell().then(shell => {
if (!shell) {
return Promise.resolve(null);
}
// Launch a new instance with the newly selected shell
const instance = this.createTerminal({
executable: shell,
args: this.configHelper.config.shellArgs.windows
});
if (instance) {
this.setActiveInstance(instance);
}
return Promise.resolve(null);
});
}
},
{
label: nls.localize('never again', "Don't Show Again"),
isSecondary: true,
run: () => this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL)
}]
);
}
public focusFindWidget(): Promise<void> {
return this.showPanel(false).then(() => {
const panel = this._panelService.getActivePanel() as TerminalPanel;
panel.focusFindWidget();
this._findWidgetVisible.set(true);
});
}
public hideFindWidget(): void {
const panel = this._panelService.getActivePanel() as TerminalPanel;
if (panel && panel.getId() === TERMINAL_PANEL_ID) {
panel.hideFindWidget();
this._findWidgetVisible.reset();
panel.focus();
}
}
public findNext(): void {
const panel = this._panelService.getActivePanel() as TerminalPanel;
if (panel && panel.getId() === TERMINAL_PANEL_ID) {
panel.showFindWidget();
panel.getFindWidget().find(false);
}
}
public findPrevious(): void {
const panel = this._panelService.getActivePanel() as TerminalPanel;
if (panel && panel.getId() === TERMINAL_PANEL_ID) {
panel.showFindWidget();
panel.getFindWidget().find(true);
}
}
public setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void {
this._configHelper.panelContainer = panelContainer;
this._terminalContainer = terminalContainer;
this._terminalTabs.forEach(tab => tab.attachToElement(this._terminalContainer));
}
public hidePanel(): void {
const panel = this._panelService.getActivePanel();
if (panel && panel.getId() === TERMINAL_PANEL_ID) {
this._layoutService.setPanelHidden(true);
}
}
}

View File

@@ -0,0 +1,437 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as aria from 'vs/base/browser/ui/aria/aria';
import * as nls from 'vs/nls';
import { ITerminalInstance, IShellLaunchConfig, ITerminalTab, Direction, ITerminalService, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService';
const SPLIT_PANE_MIN_SIZE = 120;
const TERMINAL_MIN_USEFUL_SIZE = 250;
class SplitPaneContainer {
private _height: number;
private _width: number;
private _splitView: SplitView;
private _splitViewDisposables: IDisposable[];
private _children: SplitPane[] = [];
private _onDidChange: Event<number | undefined> = Event.None;
public get onDidChange(): Event<number | undefined> { return this._onDidChange; }
constructor(
private _container: HTMLElement,
public orientation: Orientation
) {
this._width = this._container.offsetWidth;
this._height = this._container.offsetHeight;
this._createSplitView();
this._splitView.layout(this.orientation === Orientation.HORIZONTAL ? this._width : this._height);
}
private _createSplitView(): void {
this._splitView = new SplitView(this._container, { orientation: this.orientation });
this._splitViewDisposables = [];
this._splitViewDisposables.push(this._splitView.onDidSashReset(() => this._splitView.distributeViewSizes()));
}
public split(instance: ITerminalInstance, index: number = this._children.length): void {
this._addChild(instance, index);
}
public resizePane(index: number, direction: Direction, amount: number): void {
// TODO: Should resize pane up/down resize the panel?
// Only resize the correct dimension
const isHorizontal = direction === Direction.Left || direction === Direction.Right;
if (isHorizontal && this.orientation !== Orientation.HORIZONTAL ||
!isHorizontal && this.orientation !== Orientation.VERTICAL) {
return;
}
// Only resize when there is mor ethan one pane
if (this._children.length <= 1) {
return;
}
// Get sizes
const sizes: number[] = [];
for (let i = 0; i < this._splitView.length; i++) {
sizes.push(this._splitView.getViewSize(i));
}
// Remove size from right pane, unless index is the last pane in which case use left pane
const isSizingEndPane = index !== this._children.length - 1;
const indexToChange = isSizingEndPane ? index + 1 : index - 1;
if (isSizingEndPane && direction === Direction.Left) {
amount *= -1;
} else if (!isSizingEndPane && direction === Direction.Right) {
amount *= -1;
} else if (isSizingEndPane && direction === Direction.Up) {
amount *= -1;
} else if (!isSizingEndPane && direction === Direction.Down) {
amount *= -1;
}
// Ensure the size is not reduced beyond the minimum, otherwise weird things can happen
if (sizes[index] + amount < SPLIT_PANE_MIN_SIZE) {
amount = SPLIT_PANE_MIN_SIZE - sizes[index];
} else if (sizes[indexToChange] - amount < SPLIT_PANE_MIN_SIZE) {
amount = sizes[indexToChange] - SPLIT_PANE_MIN_SIZE;
}
// Apply the size change
sizes[index] += amount;
sizes[indexToChange] -= amount;
for (let i = 0; i < this._splitView.length - 1; i++) {
this._splitView.resizeView(i, sizes[i]);
}
}
private _addChild(instance: ITerminalInstance, index: number): void {
const child = new SplitPane(instance, this.orientation === Orientation.HORIZONTAL ? this._height : this._width);
child.orientation = this.orientation;
if (typeof index === 'number') {
this._children.splice(index, 0, child);
} else {
this._children.push(child);
}
this._withDisabledLayout(() => this._splitView.addView(child, Sizing.Distribute, index));
this._onDidChange = Event.any(...this._children.map(c => c.onDidChange));
}
public remove(instance: ITerminalInstance): void {
let index: number | null = null;
for (let i = 0; i < this._children.length; i++) {
if (this._children[i].instance === instance) {
index = i;
}
}
if (index !== null) {
this._children.splice(index, 1);
this._splitView.removeView(index, Sizing.Distribute);
}
}
public layout(width: number, height: number): void {
this._width = width;
this._height = height;
if (this.orientation === Orientation.HORIZONTAL) {
this._children.forEach(c => c.orthogonalLayout(height));
this._splitView.layout(width);
} else {
this._children.forEach(c => c.orthogonalLayout(width));
this._splitView.layout(height);
}
}
public setOrientation(orientation: Orientation): void {
if (this.orientation === orientation) {
return;
}
this.orientation = orientation;
// Remove old split view
while (this._container.children.length > 0) {
this._container.removeChild(this._container.children[0]);
}
this._splitViewDisposables.forEach(d => d.dispose());
this._splitViewDisposables = [];
this._splitView.dispose();
// Create new split view with updated orientation
this._createSplitView();
this._withDisabledLayout(() => {
this._children.forEach(child => {
child.orientation = orientation;
this._splitView.addView(child, 1);
});
});
}
private _withDisabledLayout(innerFunction: () => void): void {
// Whenever manipulating views that are going to be changed immediately, disabling
// layout/resize events in the terminal prevent bad dimensions going to the pty.
this._children.forEach(c => c.instance.disableLayout = true);
innerFunction();
this._children.forEach(c => c.instance.disableLayout = false);
}
}
class SplitPane implements IView {
public minimumSize: number = SPLIT_PANE_MIN_SIZE;
public maximumSize: number = Number.MAX_VALUE;
public orientation: Orientation | undefined;
protected _size: number;
private _onDidChange: Event<number | undefined> = Event.None;
public get onDidChange(): Event<number | undefined> { return this._onDidChange; }
readonly element: HTMLElement;
constructor(
readonly instance: ITerminalInstance,
public orthogonalSize: number
) {
this.element = document.createElement('div');
this.element.className = 'terminal-split-pane';
this.instance.attachToElement(this.element);
}
public layout(size: number): void {
// Only layout when both sizes are known
this._size = size;
if (!this._size || !this.orthogonalSize) {
return;
}
if (this.orientation === Orientation.VERTICAL) {
this.instance.layout({ width: this.orthogonalSize, height: this._size });
} else {
this.instance.layout({ width: this._size, height: this.orthogonalSize });
}
}
public orthogonalLayout(size: number): void {
this.orthogonalSize = size;
}
}
export class TerminalTab extends Disposable implements ITerminalTab {
private _terminalInstances: ITerminalInstance[] = [];
private _splitPaneContainer: SplitPaneContainer | undefined;
private _tabElement: HTMLElement | null;
private _panelPosition: Position = Position.BOTTOM;
private _activeInstanceIndex: number;
public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; }
private readonly _onDisposed: Emitter<ITerminalTab>;
public get onDisposed(): Event<ITerminalTab> { return this._onDisposed.event; }
private readonly _onInstancesChanged: Emitter<void>;
public get onInstancesChanged(): Event<void> { return this._onInstancesChanged.event; }
constructor(
terminalFocusContextKey: IContextKey<boolean>,
configHelper: ITerminalConfigHelper,
private _container: HTMLElement,
shellLaunchConfig: IShellLaunchConfig,
@ITerminalService private readonly _terminalService: ITerminalService,
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService
) {
super();
this._onDisposed = new Emitter<ITerminalTab>();
this._onInstancesChanged = new Emitter<void>();
const instance = this._terminalService.createInstance(
terminalFocusContextKey,
configHelper,
undefined,
shellLaunchConfig,
true);
this._terminalInstances.push(instance);
this._initInstanceListeners(instance);
this._activeInstanceIndex = 0;
if (this._container) {
this.attachToElement(this._container);
}
}
public dispose(): void {
super.dispose();
if (this._tabElement) {
this._container.removeChild(this._tabElement);
this._tabElement = null;
}
this._terminalInstances = [];
this._onInstancesChanged.fire();
}
public get activeInstance(): ITerminalInstance | null {
if (this._terminalInstances.length === 0) {
return null;
}
return this._terminalInstances[this._activeInstanceIndex];
}
private _initInstanceListeners(instance: ITerminalInstance): void {
instance.addDisposable(instance.onDisposed(instance => this._onInstanceDisposed(instance)));
instance.addDisposable(instance.onFocused(instance => {
aria.alert(nls.localize('terminalFocus', "Terminal {0}", this._terminalService.activeTabIndex + 1));
this._setActiveInstance(instance);
}));
}
private _onInstanceDisposed(instance: ITerminalInstance): void {
// Get the index of the instance and remove it from the list
const index = this._terminalInstances.indexOf(instance);
const wasActiveInstance = instance === this.activeInstance;
if (index !== -1) {
this._terminalInstances.splice(index, 1);
}
// Adjust focus if the instance was active
if (wasActiveInstance && this._terminalInstances.length > 0) {
const newIndex = index < this._terminalInstances.length ? index : this._terminalInstances.length - 1;
this.setActiveInstanceByIndex(newIndex);
// TODO: Only focus the new instance if the tab had focus?
if (this.activeInstance) {
this.activeInstance.focus(true);
}
}
// Remove the instance from the split pane if it has been created
if (this._splitPaneContainer) {
this._splitPaneContainer.remove(instance);
}
// Fire events and dispose tab if it was the last instance
this._onInstancesChanged.fire();
if (this._terminalInstances.length === 0) {
this._onDisposed.fire(this);
this.dispose();
}
}
private _setActiveInstance(instance: ITerminalInstance): void {
this.setActiveInstanceByIndex(this._getIndexFromId(instance.id));
}
private _getIndexFromId(terminalId: number): number {
let terminalIndex = -1;
this.terminalInstances.forEach((terminalInstance, i) => {
if (terminalInstance.id === terminalId) {
terminalIndex = i;
}
});
if (terminalIndex === -1) {
throw new Error(`Terminal with ID ${terminalId} does not exist (has it already been disposed?)`);
}
return terminalIndex;
}
public setActiveInstanceByIndex(index: number): void {
// Check for invalid value
if (index < 0 || index >= this._terminalInstances.length) {
return;
}
const didInstanceChange = this._activeInstanceIndex !== index;
this._activeInstanceIndex = index;
if (didInstanceChange) {
this._onInstancesChanged.fire();
}
}
public attachToElement(element: HTMLElement): void {
this._container = element;
this._tabElement = document.createElement('div');
this._tabElement.classList.add('terminal-tab');
this._container.appendChild(this._tabElement);
if (!this._splitPaneContainer) {
this._panelPosition = this._layoutService.getPanelPosition();
const orientation = this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL;
const newLocal = new SplitPaneContainer(this._tabElement, orientation);
this._splitPaneContainer = newLocal;
this.terminalInstances.forEach(instance => this._splitPaneContainer!.split(instance));
}
}
public get title(): string {
let title = this.terminalInstances[0].title;
for (let i = 1; i < this.terminalInstances.length; i++) {
if (this.terminalInstances[i].title) {
title += `, ${this.terminalInstances[i].title}`;
}
}
return title;
}
public setVisible(visible: boolean): void {
if (this._tabElement) {
this._tabElement.style.display = visible ? '' : 'none';
}
this.terminalInstances.forEach(i => i.setVisible(visible));
}
public split(
terminalFocusContextKey: IContextKey<boolean>,
configHelper: ITerminalConfigHelper,
shellLaunchConfig: IShellLaunchConfig
): ITerminalInstance | undefined {
const newTerminalSize = ((this._panelPosition === Position.BOTTOM ? this._container.clientWidth : this._container.clientHeight) / (this._terminalInstances.length + 1));
if (newTerminalSize < TERMINAL_MIN_USEFUL_SIZE) {
return undefined;
}
const instance = this._terminalService.createInstance(
terminalFocusContextKey,
configHelper,
undefined,
shellLaunchConfig,
true);
this._terminalInstances.splice(this._activeInstanceIndex + 1, 0, instance);
this._initInstanceListeners(instance);
this._setActiveInstance(instance);
if (this._splitPaneContainer) {
this._splitPaneContainer.split(instance, this._activeInstanceIndex);
}
return instance;
}
public addDisposable(disposable: IDisposable): void {
this._register(disposable);
}
public layout(width: number, height: number): void {
if (this._splitPaneContainer) {
// Check if the panel position changed and rotate panes if so
const newPanelPosition = this._layoutService.getPanelPosition();
const panelPositionChanged = newPanelPosition !== this._panelPosition;
if (panelPositionChanged) {
const newOrientation = newPanelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL;
this._splitPaneContainer.setOrientation(newOrientation);
this._panelPosition = newPanelPosition;
}
this._splitPaneContainer.layout(width, height);
}
}
public focusPreviousPane(): void {
const newIndex = this._activeInstanceIndex === 0 ? this._terminalInstances.length - 1 : this._activeInstanceIndex - 1;
this.setActiveInstanceByIndex(newIndex);
}
public focusNextPane(): void {
const newIndex = this._activeInstanceIndex === this._terminalInstances.length - 1 ? 0 : this._activeInstanceIndex + 1;
this.setActiveInstanceByIndex(newIndex);
}
public resizePane(direction: Direction): void {
if (!this._splitPaneContainer) {
return;
}
const isHorizontal = (direction === Direction.Left || direction === Direction.Right);
const font = this._terminalService.configHelper.getFont();
// TODO: Support letter spacing and line height
const amount = isHorizontal ? font.charWidth : font.charHeight;
if (amount) {
this._splitPaneContainer.resizePane(this._activeInstanceIndex, direction, amount);
}
}
}

View File

@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
const WIDGET_HEIGHT = 29;
export class TerminalWidgetManager implements IDisposable {
private _container: HTMLElement | null;
private _xtermViewport: HTMLElement | null;
private _messageWidget: MessageWidget;
private _messageListeners: IDisposable[] = [];
constructor(
terminalWrapper: HTMLElement
) {
this._container = document.createElement('div');
this._container.classList.add('terminal-widget-overlay');
terminalWrapper.appendChild(this._container);
this._initTerminalHeightWatcher(terminalWrapper);
}
public dispose(): void {
if (this._container && this._container.parentElement) {
this._container.parentElement.removeChild(this._container);
this._container = null;
}
this._xtermViewport = null;
}
private _initTerminalHeightWatcher(terminalWrapper: HTMLElement) {
// Watch the xterm.js viewport for style changes and do a layout if it changes
this._xtermViewport = <HTMLElement>terminalWrapper.querySelector('.xterm-viewport');
if (!this._xtermViewport) {
return;
}
const mutationObserver = new MutationObserver(() => this._refreshHeight());
mutationObserver.observe(this._xtermViewport, { attributes: true, attributeFilter: ['style'] });
}
public showMessage(left: number, top: number, text: string): void {
if (!this._container) {
return;
}
dispose(this._messageWidget);
this._messageListeners = dispose(this._messageListeners);
this._messageWidget = new MessageWidget(this._container, left, top, text);
}
public closeMessage(): void {
this._messageListeners = dispose(this._messageListeners);
if (this._messageWidget) {
this._messageListeners.push(MessageWidget.fadeOut(this._messageWidget));
}
}
private _refreshHeight(): void {
if (!this._container || !this._xtermViewport) {
return;
}
this._container.style.height = this._xtermViewport.style.height;
}
}
class MessageWidget {
private _domNode: HTMLDivElement;
public get left(): number { return this._left; }
public get top(): number { return this._top; }
public get text(): string { return this._text; }
public get domNode(): HTMLElement { return this._domNode; }
public static fadeOut(messageWidget: MessageWidget): IDisposable {
let handle: any;
const dispose = () => {
messageWidget.dispose();
clearTimeout(handle);
messageWidget.domNode.removeEventListener('animationend', dispose);
};
handle = setTimeout(dispose, 110);
messageWidget.domNode.addEventListener('animationend', dispose);
messageWidget.domNode.classList.add('fadeOut');
return { dispose };
}
constructor(
private _container: HTMLElement,
private _left: number,
private _top: number,
private _text: string
) {
this._domNode = document.createElement('div');
this._domNode.style.position = 'absolute';
this._domNode.style.left = `${_left}px`;
this._domNode.style.bottom = `${_container.offsetHeight - Math.max(_top, WIDGET_HEIGHT)}px`;
this._domNode.classList.add('terminal-message-widget', 'fadeIn');
this._domNode.textContent = _text;
this._container.appendChild(this._domNode);
}
public dispose(): void {
if (this.domNode.parentElement === this._container) {
this._container.removeChild(this.domNode);
}
}
}