Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 (#6381)

* Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973

* disable strict null check
This commit is contained in:
Anthony Dresser
2019-07-15 22:35:46 -07:00
committed by GitHub
parent f720ec642f
commit 0b7e7ddbf9
2406 changed files with 59140 additions and 35464 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 927 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 927 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 2H9V3H6V2ZM11 3H10V2C10 1.44772 9.55228 1 9 1H6C5.44772 1 5 1.44772 5 2V3H4H3H2V4H3V13L4 14H11L12 13V4H13V3H12H11ZM4 4V13H11V4H4ZM6 5H5V12H6V5ZM7 5H8V12H7V5ZM10 5H9V12H10V5Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 2H9V3H6V2ZM11 3H10V2C10 1.44772 9.55228 1 9 1H6C5.44772 1 5 1.44772 5 2V3H4H3H2V4H3V13L4 14H11L12 13V4H13V3H12H11ZM4 4V13H11V4H4ZM6 5H5V12H6V5ZM7 5H8V12H7V5ZM10 5H9V12H10V5Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 345 B

View File

@@ -1,10 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 592 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 2H9V3H6V2ZM11 3H10V2C10 1.44772 9.55228 1 9 1H6C5.44772 1 5 1.44772 5 2V3H4H3H2V4H3V13L4 14H11L12 13V4H13V3H12H11ZM4 4V13H11V4H4ZM6 5H5V12H6V5ZM7 5H8V12H7V5ZM10 5H9V12H10V5Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -1,10 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 592 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 7V8H8V14H7V8H1V7H7V1H8V7H14Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 163 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 7V8H8V14H7V8H1V7H7V1H8V7H14Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 161 B

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 208 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 7V8H8V14H7V8H1V7H7V1H8V7H14Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 163 B

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 185 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 2L14 1L3 0.999999L2 2L2 13L3 14L14 14L15 13L15 2ZM9 2L14 2L14 13L9 13L9 2ZM8 2L3 2L3 13L8 13L8 2Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 272 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 2L14 1L3 0.999999L2 2L2 13L3 14L14 14L15 13L15 2ZM9 2L14 2L14 13L9 13L9 2ZM8 2L3 2L3 13L8 13L8 2Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 270 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 2L14 1L3 0.999999L2 2L2 13L3 14L14 14L15 13L15 2ZM9 2L14 2L14 13L9 13L9 2ZM8 2L3 2L3 13L8 13L8 2Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 272 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 1L2 2V13L3 14H14L15 13V2L14 1H3ZM3 7V2H14V7H3ZM3 8V13H14V8H3Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 235 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 1L2 2V13L3 14H14L15 13V2L14 1H3ZM3 7V2H14V7H3ZM3 8V13H14V8H3Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 233 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 1L2 2V13L3 14H14L15 13V2L14 1H3ZM3 7V2H14V7H3ZM3 8V13H14V8H3Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 235 B

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 578 B

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 578 B

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 218 B

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 218 B

View File

@@ -147,15 +147,22 @@
}
/* 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; }
.monaco-workbench .terminal-action.kill { background: url('kill-light.svg') center center no-repeat; }
.monaco-workbench .terminal-action.new { background: url('new-light.svg') center center no-repeat; }
.monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-light.svg') center center no-repeat; }
.monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-light.svg') center center no-repeat; }
/* Dark theme */
.vs-dark .monaco-workbench .terminal-action.kill, .hc-black .monaco-workbench .terminal-action.kill { background: url('kill-dark.svg') center center no-repeat; }
.vs-dark .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-dark.svg') center center no-repeat; }
.vs-dark .monaco-workbench .terminal-action.split, .hc-black .monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-dark.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-editor-vertical-dark.svg') center center no-repeat; }
/* HC theme */
.hc-black .monaco-workbench .terminal-action.kill, .hc-black .monaco-workbench .terminal-action.kill { background: url('kill-hc.svg') center center no-repeat; }
.hc-black .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-hc.svg') center center no-repeat; }
.hc-black .monaco-workbench .terminal-action.split, .hc-black .monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-hc.svg') center center no-repeat; }
.hc-black .monaco-workbench .panel.right .terminal-action.split, .hc-black .monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-hc.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) {
@@ -163,10 +170,13 @@
}
.monaco-workbench .quick-open-terminal-configure {
background-image: url('configure.svg');
background-image: url('configure-light.svg');
}
.vs-dark .monaco-workbench .quick-open-terminal-configure,
.hc-black .monaco-workbench .quick-open-terminal-configure {
background-image: url('configure-inverse.svg');
.vs-dark .monaco-workbench .quick-open-terminal-configure {
background-image: url('configure-dark.svg');
}
.hc-black .monaco-workbench .quick-open-terminal-configure {
background-image: url('configure-hc.svg');
}

View File

@@ -43,6 +43,8 @@
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
@@ -57,7 +59,7 @@
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 10;
z-index: 5;
}
.xterm .xterm-helper-textarea {
@@ -71,7 +73,7 @@
top: 0;
width: 0;
height: 0;
z-index: -10;
z-index: -5;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
@@ -127,13 +129,22 @@
line-height: normal;
}
.xterm {
cursor: text;
}
.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-cursor-pointer {
cursor: pointer;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
@@ -143,14 +154,10 @@
top: 0;
bottom: 0;
right: 0;
z-index: 100;
z-index: 10;
color: transparent;
}
.xterm .xterm-accessibility-tree:focus [id^="xterm-active-item-"] {
outline: 1px solid #F80;
}
.xterm .live-region {
position: absolute;
left: -9999px;
@@ -159,11 +166,10 @@
overflow: hidden;
}
.xterm-cursor-pointer {
cursor: pointer !important;
.xterm-dim {
opacity: 0.5;
}
.xterm.xterm-cursor-crosshair {
/* Column selection mode */
cursor: crosshair !important;
.xterm-underline {
text-decoration: underline;
}

View File

@@ -23,13 +23,22 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor
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 { 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, ITerminalService, TERMINAL_ACTION_CATEGORY } 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';
import { TerminalService } from 'vs/workbench/contrib/terminal/browser/terminalService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { registerShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalShellConfig';
registerSingleton(ITerminalService, TerminalService, true);
if (platform.isWeb) {
registerShellConfiguration();
}
const quickOpenRegistry = (Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen));
@@ -219,6 +228,11 @@ configurationRegistry.registerConfiguration({
},
default: []
},
'terminal.integrated.inheritEnv': {
markdownDescription: nls.localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from VS Code. This is not supported on Windows."),
type: 'boolean',
default: true
},
'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',
@@ -260,15 +274,10 @@ configurationRegistry.registerConfiguration({
default: 'inherited'
},
'terminal.integrated.windowsEnableConpty': {
description: nls.localize('terminal.integrated.windowsEnableConpty', "Whether to use ConPTY for Windows terminal process communication. Winpty will be used if this is false. Note that ConPTY will be disabled regardless of this setting when the Windows 10 build number is lower than 18309 or when you're running the 32-bit VS Code client under 64-bit Windows."),
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: true
},
'terminal.integrated.enableLatencyMitigation': {
description: nls.localize('terminal.integrated.enableLatencyMitigation', "Whether to enable the latency mitigation feature for high-latency terminals."),
type: 'boolean',
default: false
},
'terminal.integrated.experimentalRefreshOnResume': {
description: nls.localize('terminal.integrated.experimentalRefreshOnResume', "An experimental setting that will refresh the terminal renderer when the system is resumed."),
type: 'boolean',
@@ -290,9 +299,10 @@ actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTerm
40,
TERMINAL_COMMAND_ID.TOGGLE
));
Registry.as<panel.PanelRegistry>(panel.Extensions.Panels).setDefaultPanelId(TERMINAL_PANEL_ID);
// On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl
const category = nls.localize('terminalCategory', "Terminal");
const category = TERMINAL_ACTION_CATEGORY;
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, {
@@ -306,7 +316,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewTermina
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);
}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE)), 'Terminal: Clear 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);
@@ -360,18 +370,13 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAct
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(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]
@@ -397,13 +402,12 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(MoveToLineEndTer
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],
primary: 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);
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split Terminal', 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,
@@ -461,27 +465,27 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEscapeSequ
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');
}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using 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);
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using 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');
}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using 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);
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using 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');
}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using case sensitive');
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);
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using case sensitive', 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] }

View File

@@ -3,21 +3,35 @@
* 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 { Terminal as XTermTerminal } from 'xterm';
import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links';
import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
import { ITerminalInstance, IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest } from 'vs/workbench/contrib/terminal/common/terminal';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProcessEnvironment, Platform } from 'vs/base/common/platform';
import { Event } from 'vs/base/common/event';
export const ITerminalInstanceService = createDecorator<ITerminalInstanceService>('terminalInstanceService');
/**
* A service used by TerminalInstance (and components owned by it) that allows it to break its
* dependency on electron-browser and node layers, while at the same time avoiding a cyclic
* dependency on ITerminalService.
*/
export interface ITerminalInstanceService {
_serviceBrand: any;
// These events are optional as the requests they make are only needed on the browser side
onRequestDefaultShellAndArgs?: Event<IDefaultShellAndArgsRequest>;
getXtermConstructor(): Promise<typeof XTermTerminal>;
getXtermWebLinksConstructor(): Promise<typeof XTermWebLinksAddon>;
getXtermSearchConstructor(): Promise<typeof XTermSearchAddon>;
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;
getDefaultShell(p: Platform): string;
getDefaultShellAndArgs(platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>;
getMainProcessParentEnv(): Promise<IProcessEnvironment>;
}
export interface IBrowserTerminalConfigHelper extends ITerminalConfigHelper {

View File

@@ -35,7 +35,6 @@ import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export const TERMINAL_PICKER_PREFIX = 'term ';
@@ -160,10 +159,10 @@ export class CopyTerminalSelectionAction extends Action {
super(id, label);
}
public run(event?: any): Promise<any> {
public async run(event?: any): Promise<any> {
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.copySelection();
await terminalInstance.copySelection();
}
return Promise.resolve(undefined);
}
@@ -239,7 +238,7 @@ export class DeleteWordRightTerminalAction extends BaseSendTextTerminalAction {
export class DeleteToLineStartTerminalAction extends BaseSendTextTerminalAction {
public static readonly ID = TERMINAL_COMMAND_ID.DELETE_TO_LINE_START;
public static readonly LABEL = nls.localize('workbench.action.terminal.deleteToLineStart', "Delete to Line Start");
public static readonly LABEL = nls.localize('workbench.action.terminal.deleteToLineStart', "Delete To Line Start");
constructor(
id: string,
@@ -624,13 +623,13 @@ export class SelectDefaultShellWindowsTerminalAction extends Action {
constructor(
id: string, label: string,
@ITerminalService private readonly terminalService: ITerminalService
@ITerminalService private readonly _terminalService: ITerminalService
) {
super(id, label);
}
public run(event?: any): Promise<any> {
return this.terminalService.selectDefaultWindowsShell();
return this._terminalService.selectDefaultWindowsShell();
}
}
@@ -743,24 +742,20 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem {
action: IAction,
@ITerminalService private readonly terminalService: ITerminalService,
@IThemeService themeService: IThemeService,
@IContextViewService contextViewService: IContextViewService,
@IWorkbenchEnvironmentService private workbenchEnvironmentService: IWorkbenchEnvironmentService
@IContextViewService contextViewService: IContextViewService
) {
super(null, action, terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label }), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') });
this.toDispose.push(terminalService.onInstancesChanged(this._updateItems, this));
this.toDispose.push(terminalService.onActiveTabChanged(this._updateItems, this));
this.toDispose.push(terminalService.onInstanceTitleChanged(this._updateItems, this));
this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService));
this._register(terminalService.onInstancesChanged(this._updateItems, this));
this._register(terminalService.onActiveTabChanged(this._updateItems, this));
this._register(terminalService.onInstanceTitleChanged(this._updateItems, this));
this._register(attachSelectBoxStyler(this.selectBox, themeService));
}
private _updateItems(): void {
const items = this.terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label });
let enableSelectDefaultShell = this.workbenchEnvironmentService.configuration.remoteAuthority ? false : isWindows;
if (enableSelectDefaultShell) {
items.push({ text: SwitchTerminalActionViewItem.SEPARATOR });
items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL });
}
items.push({ text: SwitchTerminalActionViewItem.SEPARATOR, isDisabled: true });
items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL });
this.setOptions(items, this.terminalService.activeTabIndex);
}
}
@@ -1041,7 +1036,7 @@ export class QuickOpenActionTermContributor extends ActionBarContributor {
super();
}
public getActions(context: any): IAction[] {
public getActions(context: any): ReadonlyArray<IAction> {
const actions: Action[] = [];
if (context.element instanceof TerminalEntry) {
actions.push(this.instantiationService.createInstance(RenameTerminalQuickOpenAction, RenameTerminalQuickOpenAction.ID, RenameTerminalQuickOpenAction.LABEL, context.element));

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Terminal, IMarker } from 'vscode-xterm';
import { Terminal, IMarker } from 'xterm';
import { ITerminalCommandTracker } from 'vs/workbench/contrib/terminal/common/terminal';
import { IDisposable } from 'vs/base/common/lifecycle';
@@ -112,7 +112,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa
private _scrollToMarker(marker: IMarker, position: ScrollPosition): void {
let line = marker.line;
if (position === ScrollPosition.Middle) {
line = Math.max(line - this._xterm.rows / 2, 0);
line = Math.max(line - Math.floor(this._xterm.rows / 2), 0);
}
this._xterm.scrollToLine(line);
}

View File

@@ -8,12 +8,12 @@ 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 { ITerminalConfiguration, ITerminalFont, 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 { Terminal as XTermTerminal } from 'xterm';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal';
import { mergeDefaultShellPathAndArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { Emitter, Event } from 'vs/base/common/event';
const MINIMUM_FONT_SIZE = 6;
const MAXIMUM_FONT_SIZE = 25;
@@ -29,6 +29,9 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
private _lastFontMeasurement: ITerminalFont;
public config: ITerminalConfiguration;
private readonly _onWorkspacePermissionsChanged = new Emitter<boolean>();
public get onWorkspacePermissionsChanged(): Event<boolean> { return this._onWorkspacePermissionsChanged.event; }
public constructor(
private readonly _linuxDistro: LinuxDistro,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@@ -143,14 +146,14 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
// Get the character dimensions from xterm if it's available
if (xterm) {
if (xterm._core.charMeasure && xterm._core.charMeasure.width && xterm._core.charMeasure.height) {
if (xterm._core._charSizeService && xterm._core._charSizeService.width && xterm._core._charSizeService.height) {
return {
fontFamily,
fontSize,
letterSpacing,
lineHeight,
charHeight: xterm._core.charMeasure.height,
charWidth: xterm._core.charMeasure.width
charHeight: xterm._core._charSizeService.height,
charWidth: xterm._core._charSizeService.width
};
}
}
@@ -160,6 +163,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
}
public setWorkspaceShellAllowed(isAllowed: boolean): void {
this._onWorkspacePermissionsChanged.fire(isAllowed);
this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, isAllowed, StorageScope.WORKSPACE);
}
@@ -172,7 +176,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
const platformKey = osOverride === platform.OperatingSystem.Windows ? 'windows' : osOverride === platform.OperatingSystem.Macintosh ? '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}`);
const envConfigValue = this._workspaceConfigurationService.inspect<{ [key: string]: string }>(`terminal.integrated.env.${platformKey}`);
// Check if workspace setting exists and whether it's whitelisted
let isWorkspaceShellAllowed: boolean | undefined = false;
@@ -227,11 +231,6 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
return !!isWorkspaceShellAllowed;
}
public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, defaultShell: string, platformOverride: platform.Platform = platform.platform): void {
const isWorkspaceShellAllowed = this.checkWorkspaceShellPermissions(platformOverride === platform.Platform.Windows ? platform.OperatingSystem.Windows : (platformOverride === platform.Platform.Mac ? platform.OperatingSystem.Macintosh : platform.OperatingSystem.Linux));
mergeDefaultShellPathAndArgs(shell, (key) => this._workspaceConfigurationService.inspect(key), isWorkspaceShellAllowed, defaultShell, platformOverride);
}
private _toInteger(source: any, minimum: number, maximum: number, fallback: number): number {
let r = parseInt(source, 10);
if (isNaN(r)) {

View File

@@ -50,8 +50,9 @@ export class TerminalFindWidget extends SimpleFindWidget {
// 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 });
return instance.findNext(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue(), incremental: true });
}
return false;
}
protected onFocusTrackerFocus() {

View File

@@ -25,23 +25,24 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { PANEL_BACKGROUND } from 'vs/workbench/common/theme';
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager';
import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal';
import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal';
import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler';
import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ISearchOptions, Terminal as XTermTerminal, IBuffer } from 'vscode-xterm';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager';
import { Terminal as XTermTerminal, IBuffer } from 'xterm';
import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
// How long in milliseconds should an average frame take to render for a notification to appear
// which suggests the fallback DOM-based renderer
const SLOW_CANVAS_RENDER_THRESHOLD = 50;
const NUMBER_OF_FRAMES_TO_MEASURE = 20;
export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [
TERMINAL_COMMAND_ID.CLEAR_SELECTION,
TERMINAL_COMMAND_ID.CLEAR,
@@ -148,10 +149,23 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [
'workbench.action.toggleMaximizedPanel'
];
let xtermConstructor: Promise<typeof XTermTerminal> | undefined;
interface ICanvasDimensions {
width: number;
height: number;
}
interface IGridDimensions {
cols: number;
rows: number;
}
export class TerminalInstance implements ITerminalInstance {
private static readonly EOL_REGEX = /\r?\n/g;
private static _lastKnownDimensions: dom.Dimension | null = null;
private static _lastKnownCanvasDimensions: ICanvasDimensions | undefined;
private static _lastKnownGridDimensions: IGridDimensions | undefined;
private static _idCounter = 1;
private _processManager: ITerminalProcessManager | undefined;
@@ -166,17 +180,18 @@ export class TerminalInstance implements ITerminalInstance {
private _title: string;
private _wrapperElement: HTMLDivElement;
private _xterm: XTermTerminal;
private _xtermSearch: SearchAddon | undefined;
private _xtermElement: HTMLDivElement;
private _terminalHasTextContextKey: IContextKey<boolean>;
private _cols: number;
private _rows: number;
private _dimensionsOverride: ITerminalDimensions;
private _dimensionsOverride: ITerminalDimensions | undefined;
private _windowsShellHelper: IWindowsShellHelper | undefined;
private _xtermReadyPromise: Promise<void>;
private _titleReadyPromise: Promise<string>;
private _titleReadyComplete: (title: string) => any;
private _disposables: lifecycle.IDisposable[];
private readonly _disposables = new lifecycle.DisposableStore();
private _messageTitleDisposable: lifecycle.IDisposable | undefined;
private _widgetManager: TerminalWidgetManager;
@@ -197,6 +212,8 @@ export class TerminalInstance implements ITerminalInstance {
}
return this._rows;
}
public get maxCols(): number { return this._cols; }
public get maxRows(): number { return this._rows; }
// TODO: Ideally processId would be merged into processReady
public get processId(): number | undefined { return this._processManager ? this._processManager.shellProcessId : undefined; }
// TODO: How does this work with detached processes?
@@ -228,6 +245,8 @@ export class TerminalInstance implements ITerminalInstance {
public get onRequestExtHostProcess(): Event<ITerminalInstance> { return this._onRequestExtHostProcess.event; }
private readonly _onDimensionsChanged = new Emitter<void>();
public get onDimensionsChanged(): Event<void> { return this._onDimensionsChanged.event; }
private readonly _onMaximumDimensionsChanged = new Emitter<void>();
public get onMaximumDimensionsChanged(): Event<void> { return this._onMaximumDimensionsChanged.event; }
private readonly _onFocus = new Emitter<ITerminalInstance>();
public get onFocus(): Event<ITerminalInstance> { return this._onFocus.event; }
@@ -249,7 +268,6 @@ export class TerminalInstance implements ITerminalInstance {
@IStorageService private readonly _storageService: IStorageService,
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService
) {
this._disposables = [];
this._skipTerminalCommands = [];
this._isExiting = false;
this._hadFocusOnExit = false;
@@ -296,7 +314,7 @@ export class TerminalInstance implements ITerminalInstance {
}
public addDisposable(disposable: lifecycle.IDisposable): void {
this._disposables.push(disposable);
this._disposables.add(disposable);
}
private _initDimensions(): void {
@@ -320,16 +338,19 @@ export class TerminalInstance implements ITerminalInstance {
private _evaluateColsAndRows(width: number, height: number): number | null {
// Ignore if dimensions are undefined or 0
if (!width || !height) {
this._setLastKnownColsAndRows();
return null;
}
const dimension = this._getDimension(width, height);
if (!dimension) {
this._setLastKnownColsAndRows();
return null;
}
const font = this._configHelper.getFont(this._xterm);
if (!font.charWidth || !font.charHeight) {
this._setLastKnownColsAndRows();
return null;
}
@@ -345,26 +366,44 @@ export class TerminalInstance implements ITerminalInstance {
} else {
scaledCharWidth = Math.floor(font.charWidth * window.devicePixelRatio) + font.letterSpacing;
}
this._cols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1);
const newCols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1);
const scaledHeightAvailable = dimension.height * window.devicePixelRatio;
const scaledCharHeight = Math.ceil(font.charHeight * window.devicePixelRatio);
const scaledLineHeight = Math.floor(scaledCharHeight * font.lineHeight);
this._rows = Math.max(Math.floor(scaledHeightAvailable / scaledLineHeight), 1);
const newRows = Math.max(Math.floor(scaledHeightAvailable / scaledLineHeight), 1);
if (this._cols !== newCols || this._rows !== newRows) {
this._cols = newCols;
this._rows = newRows;
this._fireMaximumDimensionsChanged();
}
return dimension.width;
}
private _getDimension(width: number, height: number): dom.Dimension | null {
private _setLastKnownColsAndRows(): void {
if (TerminalInstance._lastKnownGridDimensions) {
this._cols = TerminalInstance._lastKnownGridDimensions.cols;
this._rows = TerminalInstance._lastKnownGridDimensions.rows;
}
}
@debounce(50)
private _fireMaximumDimensionsChanged(): void {
this._onMaximumDimensionsChanged.fire();
}
private _getDimension(width: number, height: number): ICanvasDimensions | undefined {
// The font needs to have been initialized
const font = this._configHelper.getFont(this._xterm);
if (!font || !font.charWidth || !font.charHeight) {
return null;
return undefined;
}
// The panel is minimized
if (!this._isVisible) {
return TerminalInstance._lastKnownDimensions;
return TerminalInstance._lastKnownCanvasDimensions;
} else {
// Trigger scroll event manually so that the viewport's scroll area is synced. This
// needs to happen otherwise its scrollTop value is invalid when the panel is toggled as
@@ -376,7 +415,7 @@ export class TerminalInstance implements ITerminalInstance {
}
if (!this._wrapperElement) {
return null;
return undefined;
}
const wrapperElementStyle = getComputedStyle(this._wrapperElement);
@@ -387,15 +426,29 @@ export class TerminalInstance implements ITerminalInstance {
const innerWidth = width - marginLeft - marginRight;
const innerHeight = height - bottom;
TerminalInstance._lastKnownDimensions = new dom.Dimension(innerWidth, innerHeight);
return TerminalInstance._lastKnownDimensions;
TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension(innerWidth, innerHeight);
return TerminalInstance._lastKnownCanvasDimensions;
}
private async _getXtermConstructor(): Promise<typeof XTermTerminal> {
if (xtermConstructor) {
return xtermConstructor;
}
xtermConstructor = new Promise<typeof XTermTerminal>(async (resolve) => {
const Terminal = await this._terminalInstanceService.getXtermConstructor();
// Localize strings
Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input');
Terminal.strings.tooMuchOutput = nls.localize('terminal.integrated.a11yTooMuchOutput', 'Too much output to announce, navigate to rows manually to read');
resolve(Terminal);
});
return xtermConstructor;
}
/**
* Create xterm.js instance and attach data listeners.
*/
protected async _createXterm(): Promise<void> {
const Terminal = await this._terminalInstanceService.getXtermConstructor();
const Terminal = await this._getXtermConstructor();
const font = this._configHelper.getFont(undefined, true);
const config = this._configHelper.config;
this._xterm = new Terminal({
@@ -414,9 +467,11 @@ export class TerminalInstance implements ITerminalInstance {
macOptionClickForcesSelection: config.macOptionClickForcesSelection,
rightClickSelectsWord: config.rightClickBehavior === 'selectWord',
// TODO: Guess whether to use canvas or dom better
rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType,
// TODO: Remove this once the setting is removed upstream
experimentalCharAtlas: 'dynamic'
rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType
});
this._terminalInstanceService.getXtermSearchConstructor().then(Addon => {
this._xtermSearch = new Addon();
this._xterm.loadAddon(this._xtermSearch);
});
if (this._shellLaunchConfig.initialText) {
this._xterm.writeln(this._shellLaunchConfig.initialText);
@@ -447,10 +502,10 @@ export class TerminalInstance implements ITerminalInstance {
return false;
});
}
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._processManager);
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, this._processManager);
});
} else if (this.shellLaunchConfig.isRendererOnly) {
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, undefined, undefined);
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, undefined);
}
// Register listener to trigger the onInput ext API if the terminal is a renderer only
@@ -459,7 +514,7 @@ export class TerminalInstance implements ITerminalInstance {
}
this._commandTracker = new TerminalCommandTracker(this._xterm);
this._disposables.push(this._themeService.onThemeChange(theme => this._updateTheme(theme)));
this._disposables.add(this._themeService.onThemeChange(theme => this._updateTheme(theme)));
}
private _isScreenReaderOptimized(): boolean {
@@ -541,7 +596,7 @@ export class TerminalInstance implements ITerminalInstance {
return true;
});
this._disposables.push(dom.addDisposableListener(this._xterm.element, 'mousedown', () => {
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'mousedown', () => {
// We need to listen to the mouseup event on the document since the user may release
// the mouse button anywhere outside of _xterm.element.
const listener = dom.addDisposableListener(document, 'mouseup', () => {
@@ -553,7 +608,7 @@ export class TerminalInstance implements ITerminalInstance {
}));
// xterm.js currently drops selection on keyup as we need to handle this case.
this._disposables.push(dom.addDisposableListener(this._xterm.element, 'keyup', () => {
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'keyup', () => {
// Wait until keyup has propagated through the DOM before evaluating
// the new selection state.
setTimeout(() => this._refreshSelectionContextKey(), 0);
@@ -563,7 +618,7 @@ export class TerminalInstance implements ITerminalInstance {
const focusTrap: HTMLElement = document.createElement('div');
focusTrap.setAttribute('tabindex', '0');
dom.addClass(focusTrap, 'focus-trap');
this._disposables.push(dom.addDisposableListener(focusTrap, 'focus', () => {
this._disposables.add(dom.addDisposableListener(focusTrap, 'focus', () => {
let currentElement = focusTrap;
while (!dom.hasClass(currentElement, 'part')) {
currentElement = currentElement.parentElement!;
@@ -573,18 +628,18 @@ export class TerminalInstance implements ITerminalInstance {
}));
xtermHelper.insertBefore(focusTrap, this._xterm.textarea);
this._disposables.push(dom.addDisposableListener(this._xterm.textarea, 'focus', () => {
this._disposables.add(dom.addDisposableListener(this._xterm.textarea, 'focus', () => {
this._terminalFocusContextKey.set(true);
this._onFocused.fire(this);
}));
this._disposables.push(dom.addDisposableListener(this._xterm.textarea, 'blur', () => {
this._disposables.add(dom.addDisposableListener(this._xterm.textarea, 'blur', () => {
this._terminalFocusContextKey.reset();
this._refreshSelectionContextKey();
}));
this._disposables.push(dom.addDisposableListener(this._xterm.element, 'focus', () => {
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'focus', () => {
this._terminalFocusContextKey.set(true);
}));
this._disposables.push(dom.addDisposableListener(this._xterm.element, 'blur', () => {
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'blur', () => {
this._terminalFocusContextKey.reset();
this._refreshSelectionContextKey();
}));
@@ -595,19 +650,6 @@ export class TerminalInstance implements ITerminalInstance {
if (this._processManager) {
this._widgetManager = new TerminalWidgetManager(this._wrapperElement);
this._processManager.onProcessReady(() => this._linkHandler.setWidgetManager(this._widgetManager));
this._processManager.onProcessReady(() => {
if (this._configHelper.config.enableLatencyMitigation) {
if (!this._processManager) {
return;
}
this._processManager.getLatency().then(latency => {
if (latency > 20 && (this._xterm as any).typeAheadInit) {
(this._xterm as any).typeAheadInit(this._processManager, this._themeService);
}
});
}
});
} else if (this._shellLaunchConfig.isRendererOnly) {
this._widgetManager = new TerminalWidgetManager(this._wrapperElement);
this._linkHandler.setWidgetManager(this._widgetManager);
@@ -635,7 +677,7 @@ export class TerminalInstance implements ITerminalInstance {
private _measureRenderTime(): void {
const frameTimes: number[] = [];
const textRenderLayer = this._xterm._core._renderCoordinator._renderer._renderLayers[0];
const textRenderLayer = this._xterm._core._renderService._renderer._renderLayers[0];
const originalOnGridChanged = textRenderLayer.onGridChanged;
const evaluateCanvasRenderer = () => {
@@ -691,9 +733,9 @@ export class TerminalInstance implements ITerminalInstance {
return this._xterm && this._xterm.hasSelection();
}
public copySelection(): void {
public async copySelection(): Promise<void> {
if (this.hasSelection()) {
this._clipboardService.writeText(this._xterm.getSelection());
await this._clipboardService.writeText(this._xterm.getSelection());
} else {
this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy'));
}
@@ -714,11 +756,17 @@ export class TerminalInstance implements ITerminalInstance {
}
public findNext(term: string, searchOptions: ISearchOptions): boolean {
return this._xterm.findNext(term, searchOptions);
if (!this._xtermSearch) {
return false;
}
return this._xtermSearch.findNext(term, searchOptions);
}
public findPrevious(term: string, searchOptions: ISearchOptions): boolean {
return this._xterm.findPrevious(term, searchOptions);
if (!this._xtermSearch) {
return false;
}
return this._xtermSearch.findPrevious(term, searchOptions);
}
public notifyFindWidgetFocusChanged(isFocused: boolean): void {
@@ -770,12 +818,12 @@ export class TerminalInstance implements ITerminalInstance {
this._isDisposed = true;
this._onDisposed.fire(this);
}
this._disposables = lifecycle.dispose(this._disposables);
this._disposables.dispose();
}
public rendererExit(exitCode: number): void {
// The use of this API is for cases where there is no backing process behind a terminal
// instance (eg. a custom execution task).
// instance (e.g. a custom execution task).
if (!this.shellLaunchConfig.isRendererOnly) {
throw new Error('rendererExit is only expected to be called on a renderer only terminal');
}
@@ -915,10 +963,11 @@ export class TerminalInstance implements ITerminalInstance {
}
protected _createProcess(): void {
this._processManager = this._terminalInstanceService.createTerminalProcessManager(this._id, this._configHelper);
this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._id, this._configHelper);
this._processManager.onProcessReady(() => this._onProcessIdReady.fire(this));
this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode));
this._processManager.onProcessData(data => this._onData.fire(data));
this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e));
if (this._shellLaunchConfig.name) {
this.setTitle(this._shellLaunchConfig.name, false);
@@ -944,7 +993,7 @@ export class TerminalInstance implements ITerminalInstance {
// Create the process asynchronously to allow the terminal's container
// to be created so dimensions are accurate
setTimeout(() => {
this._processManager!.createProcess(this._shellLaunchConfig, this._cols, this._rows);
this._processManager!.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized());
}, 0);
}
@@ -959,7 +1008,7 @@ export class TerminalInstance implements ITerminalInstance {
/**
* Called when either a process tied to a terminal has exited or when a terminal renderer
* simulates a process exiting (eg. custom execution task).
* simulates a process exiting (e.g. custom execution task).
* @param exitCode The exit code of the process, this is undefined when the terminal was exited
* through user action.
*/
@@ -977,7 +1026,11 @@ export class TerminalInstance implements ITerminalInstance {
// Create exit code message
if (exitCode) {
if (exitCode === SHELL_PATH_INVALID_EXIT_CODE) {
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPath', 'The terminal shell path does not exist: {0}', this._shellLaunchConfig.executable);
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPath', 'The terminal shell path "{0}" does not exist', this._shellLaunchConfig.executable);
} else if (exitCode === SHELL_PATH_DIRECTORY_EXIT_CODE) {
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPathDirectory', 'The terminal shell path "{0}" is a directory', this._shellLaunchConfig.executable);
} else if (exitCode === SHELL_CWD_INVALID_EXIT_CODE && this._shellLaunchConfig.cwd) {
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidCWD', 'The terminal shell CWD "{0}" does not exist', this._shellLaunchConfig.cwd.toString());
} else if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
let args = '';
if (typeof this._shellLaunchConfig.args === 'string') {
@@ -1123,9 +1176,17 @@ export class TerminalInstance implements ITerminalInstance {
}
private _sendLineData(buffer: IBuffer, lineIndex: number): void {
let lineData = buffer.getLine(lineIndex)!.translateToString(true);
while (lineIndex >= 0 && buffer.getLine(lineIndex--)!.isWrapped) {
lineData = buffer.getLine(lineIndex)!.translateToString(false) + lineData;
let line = buffer.getLine(lineIndex);
if (!line) {
return;
}
let lineData = line.translateToString(true);
while (lineIndex > 0 && line.isWrapped) {
line = buffer.getLine(--lineIndex);
if (!line) {
break;
}
lineData = line.translateToString(false) + lineData;
}
this._onLineData.fire(lineData);
}
@@ -1216,7 +1277,6 @@ export class TerminalInstance implements ITerminalInstance {
return;
}
const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height);
if (!terminalWidth) {
return;
@@ -1249,18 +1309,24 @@ export class TerminalInstance implements ITerminalInstance {
this._safeSetOption('drawBoldTextInBrightColors', config.drawBoldTextInBrightColors);
}
if (isNaN(cols) || isNaN(rows)) {
return;
}
if (cols !== this._xterm.cols || rows !== this._xterm.rows) {
this._onDimensionsChanged.fire();
}
this._xterm.resize(cols, rows);
TerminalInstance._lastKnownGridDimensions = { cols, rows };
if (this._isVisible) {
// HACK: Force the renderer to unpause by simulating an IntersectionObserver event.
// This is to fix an issue where dragging the window to the top of the screen to
// maximize on Windows/Linux would fire an event saying that the terminal was not
// visible.
if (this._xterm.getOption('rendererType') === 'canvas') {
this._xterm._core._renderCoordinator._onIntersectionChange({ intersectionRatio: 1 });
this._xterm._core._renderService._onIntersectionChange({ intersectionRatio: 1 });
// HACK: Force a refresh of the screen to ensure links are refresh corrected.
// This can probably be removed when the above hack is fixed in Chromium.
this._xterm.refresh(0, this._xterm.rows - 1);
@@ -1308,7 +1374,7 @@ export class TerminalInstance implements ITerminalInstance {
return this._titleReadyPromise;
}
public setDimensions(dimensions: ITerminalDimensions): void {
public setDimensions(dimensions: ITerminalDimensions | undefined): void {
this._dimensionsOverride = dimensions;
this._resize();
}

View File

@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IWindowsShellHelper, ITerminalChildProcess, IDefaultShellAndArgsRequest } from 'vs/workbench/contrib/terminal/common/terminal';
import { Terminal as XTermTerminal } from 'xterm';
import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links';
import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { Emitter, Event } from 'vs/base/common/event';
let Terminal: typeof XTermTerminal;
let WebLinksAddon: typeof XTermWebLinksAddon;
let SearchAddon: typeof XTermSearchAddon;
export class TerminalInstanceService implements ITerminalInstanceService {
public _serviceBrand: any;
private readonly _onRequestDefaultShellAndArgs = new Emitter<IDefaultShellAndArgsRequest>();
public get onRequestDefaultShellAndArgs(): Event<IDefaultShellAndArgsRequest> { return this._onRequestDefaultShellAndArgs.event; }
constructor() { }
public async getXtermConstructor(): Promise<typeof XTermTerminal> {
if (!Terminal) {
Terminal = (await import('xterm')).Terminal;
}
return Terminal;
}
public async getXtermWebLinksConstructor(): Promise<typeof XTermWebLinksAddon> {
if (!WebLinksAddon) {
WebLinksAddon = (await import('xterm-addon-web-links')).WebLinksAddon;
}
return WebLinksAddon;
}
public async getXtermSearchConstructor(): Promise<typeof XTermSearchAddon> {
if (!SearchAddon) {
SearchAddon = (await import('xterm-addon-search')).SearchAddon;
}
return SearchAddon;
}
public createWindowsShellHelper(): IWindowsShellHelper {
throw new Error('Not implemented');
}
public createTerminalProcess(): ITerminalChildProcess {
throw new Error('Not implemented');
}
public getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }> {
return new Promise(r => this._onRequestDefaultShellAndArgs.fire((shell, args) => r({ shell, args })));
}
public async getMainProcessParentEnv(): Promise<IProcessEnvironment> {
return {};
}
}

View File

@@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/
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 { dispose, IDisposable, DisposableStore } 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';
@@ -14,9 +13,11 @@ import { ITerminalService, ITerminalProcessManager } from 'vs/workbench/contrib/
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 { ILinkMatcherOptions } from 'xterm';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { posix, win32 } from 'vs/base/common/path';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { OperatingSystem, isMacintosh } from 'vs/base/common/platform';
const pathPrefix = '(\\.\\.?|\\~)';
const pathSeparatorClause = '\\/';
@@ -64,22 +65,23 @@ interface IPath {
}
export class TerminalLinkHandler {
private _hoverDisposables: IDisposable[] = [];
private readonly _hoverDisposables = new DisposableStore();
private _mouseMoveDisposable: IDisposable;
private _widgetManager: TerminalWidgetManager;
private _processCwd: string;
private _gitDiffPreImagePattern: RegExp;
private _gitDiffPostImagePattern: RegExp;
private readonly _tooltipCallback: (event: MouseEvent, uri: string) => boolean | void;
private readonly _leaveCallback: () => void;
constructor(
private _xterm: any,
private _platform: platform.Platform | undefined,
private readonly _processManager: ITerminalProcessManager | undefined,
@IOpenerService private readonly _openerService: IOpenerService,
@IEditorService private readonly _editorService: IEditorService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ITerminalService private readonly _terminalService: ITerminalService,
@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
@IFileService private readonly _fileService: IFileService
) {
// Matches '--- a/src/file1', capturing 'src/file1' in group 1
@@ -98,9 +100,14 @@ export class TerminalLinkHandler {
this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString());
}
};
this._leaveCallback = () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
};
this.registerWebLinkHandler();
if (this._platform) {
if (this._processManager) {
this.registerLocalLinkHandler();
this.registerGitDiffLinkHandlers();
}
@@ -118,11 +125,7 @@ export class TerminalLinkHandler {
const options: ILinkMatcherOptions = {
matchIndex,
tooltipCallback: this._tooltipCallback,
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
leaveCallback: this._leaveCallback,
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: CUSTOM_LINK_PRIORITY
};
@@ -133,18 +136,19 @@ export class TerminalLinkHandler {
}
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: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e)
this._terminalInstanceService.getXtermWebLinksConstructor().then((WebLinksAddon) => {
if (!this._xterm) {
return;
}
const wrappedHandler = this._wrapLinkHandler(uri => {
this._handleHypertextLink(uri);
});
this._xterm.loadAddon(new WebLinksAddon(wrappedHandler, {
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateWebLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: this._leaveCallback,
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e)
}));
});
}
@@ -155,11 +159,7 @@ export class TerminalLinkHandler {
this._xterm.registerLinkMatcher(this._localLinkRegex, wrappedHandler, {
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
leaveCallback: this._leaveCallback,
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: LOCAL_LINK_PRIORITY
});
@@ -173,11 +173,7 @@ export class TerminalLinkHandler {
matchIndex: 1,
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback),
tooltipCallback: this._tooltipCallback,
leaveCallback: () => {
if (this._widgetManager) {
this._widgetManager.closeMessage();
}
},
leaveCallback: this._leaveCallback,
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
priority: LOCAL_LINK_PRIORITY
};
@@ -187,7 +183,8 @@ export class TerminalLinkHandler {
public dispose(): void {
this._xterm = null;
this._hoverDisposables = dispose(this._hoverDisposables);
this._hoverDisposables.dispose();
this._mouseMoveDisposable = dispose(this._mouseMoveDisposable);
}
@@ -210,7 +207,7 @@ export class TerminalLinkHandler {
if (!this._processManager) {
throw new Error('Process manager is required');
}
const baseLocalLinkClause = this._processManager.os === platform.OperatingSystem.Windows ? winLocalLinkClause : unixLocalLinkClause;
const baseLocalLinkClause = this._processManager.os === OperatingSystem.Windows ? winLocalLinkClause : unixLocalLinkClause;
// Append line and column number regex
return new RegExp(`${baseLocalLinkClause}(${lineAndColumnClause})`);
}
@@ -255,19 +252,19 @@ export class TerminalLinkHandler {
if (editorConf.multiCursorModifier === 'ctrlCmd') {
return !!event.altKey;
}
return platform.isMacintosh ? event.metaKey : event.ctrlKey;
return isMacintosh ? event.metaKey : event.ctrlKey;
}
private _getLinkHoverString(): string {
const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor');
if (editorConf.multiCursorModifier === 'ctrlCmd') {
if (platform.isMacintosh) {
if (isMacintosh) {
return nls.localize('terminalLinkHandler.followLinkAlt.mac', "Option + click to follow link");
} else {
return nls.localize('terminalLinkHandler.followLinkAlt', "Alt + click to follow link");
}
}
if (platform.isMacintosh) {
if (isMacintosh) {
return nls.localize('terminalLinkHandler.followLinkCmd', "Cmd + click to follow link");
}
return nls.localize('terminalLinkHandler.followLinkCtrl', "Ctrl + click to follow link");
@@ -277,7 +274,7 @@ export class TerminalLinkHandler {
if (!this._processManager) {
throw new Error('Process manager is required');
}
if (this._processManager.os === platform.OperatingSystem.Windows) {
if (this._processManager.os === OperatingSystem.Windows) {
return win32;
}
return posix;
@@ -295,7 +292,7 @@ export class TerminalLinkHandler {
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 (this._processManager.os === OperatingSystem.Windows) {
if (!link.match('^' + winDrivePrefix)) {
if (!this._processCwd) {
// Abort if no workspace is open
@@ -364,17 +361,18 @@ export class TerminalLinkHandler {
* @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) {
if (!matches || !this._processManager) {
return lineColumnInfo;
}
const lineAndColumnMatchIndex = this._platform === platform.Platform.Windows ? winLineAndColumnMatchIndex : unixLineAndColumnMatchIndex;
const lineAndColumnMatchIndex = this._processManager.os === OperatingSystem.Windows ? winLineAndColumnMatchIndex : unixLineAndColumnMatchIndex;
for (let i = 0; i < lineAndColumnClause.length; i++) {
const lineMatchIndex = lineAndColumnMatchIndex + (lineAndColumnClauseGroupCount * i);
const rowNumber = matches[lineMatchIndex];

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import { ITerminalNativeService, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal';
import { Emitter, Event } from 'vs/base/common/event';
export class TerminalNativeService implements ITerminalNativeService {
public _serviceBrand: any;
public get linuxDistro(): LinuxDistro { return LinuxDistro.Unknown; }
private readonly _onOpenFileRequest = new Emitter<IOpenFileRequest>();
public get onOpenFileRequest(): Event<IOpenFileRequest> { return this._onOpenFileRequest.event; }
private readonly _onOsResume = new Emitter<void>();
public get onOsResume(): Event<void> { return this._onOsResume.event; }
constructor() { }
public whenFileDeleted(): Promise<void> {
throw new Error('Not implemented');
}
public getWslPath(): Promise<string> {
throw new Error('Not implemented');
}
public getWindowsBuildNumber(): number {
throw new Error('Not implemented');
}
}

View File

@@ -203,7 +203,7 @@ export class TerminalPanel extends Panel {
}
private _attachEventListeners(): void {
this._register(dom.addDisposableListener(this._parentDomElement, 'mousedown', (event: MouseEvent) => {
this._register(dom.addDisposableListener(this._parentDomElement, 'mousedown', async (event: MouseEvent) => {
if (this._terminalService.terminalInstances.length === 0) {
return;
}
@@ -222,7 +222,7 @@ export class TerminalPanel extends Panel {
return;
}
if (terminal.hasSelection()) {
terminal.copySelection();
await terminal.copySelection();
terminal.clearSelection();
} else {
terminal.paste();
@@ -240,7 +240,7 @@ export class TerminalPanel extends Panel {
}
}
}));
this._register(dom.addDisposableListener(this._parentDomElement, 'mouseup', (event: MouseEvent) => {
this._register(dom.addDisposableListener(this._parentDomElement, 'mouseup', async (event: MouseEvent) => {
if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) {
if (this._terminalService.terminalInstances.length === 0) {
return;
@@ -249,7 +249,7 @@ export class TerminalPanel extends Panel {
if (event.which === 1) {
const terminal = this._terminalService.getActiveInstance();
if (terminal && terminal.hasSelection()) {
terminal.copySelection();
await terminal.copySelection();
}
}
}

View File

@@ -5,8 +5,7 @@
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, IBeforeProcessDataEvent, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal';
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalDimensions } 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';
@@ -30,6 +29,11 @@ const LAUNCHING_DURATION = 500;
*/
const LATENCY_MEASURING_INTERVAL = 1000;
enum ProcessType {
Process,
VirtualProcess
}
/**
* Holds all state related to the creation and management of terminal processes.
*
@@ -47,11 +51,12 @@ export class TerminalProcessManager implements ITerminalProcessManager {
public userHome: string | undefined;
private _process: ITerminalChildProcess | null = null;
private _processType: ProcessType = ProcessType.Process;
private _preLaunchInputQueue: string[] = [];
private _disposables: IDisposable[] = [];
private _latency: number = -1;
private _latencyRequest: Promise<number>;
private _latencyLastMeasured: number = 0;
private _initialCwd: string;
private readonly _onProcessReady = new Emitter<void>();
public get onProcessReady(): Event<void> { return this._onProcessReady.event; }
@@ -63,6 +68,8 @@ export class TerminalProcessManager implements ITerminalProcessManager {
public get onProcessTitle(): Event<string> { return this._onProcessTitle.event; }
private readonly _onProcessExit = new Emitter<number>();
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
private readonly _onProcessOverrideDimensions = new Emitter<ITerminalDimensions | undefined>();
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { return this._onProcessOverrideDimensions.event; }
constructor(
private readonly _terminalId: number,
@@ -96,45 +103,45 @@ export class TerminalProcessManager implements ITerminalProcessManager {
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(
public async 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);
rows: number,
isScreenReaderModeEnabled: boolean
): Promise<void> {
if (shellLaunchConfig.isVirtualProcess) {
this._processType = ProcessType.VirtualProcess;
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, undefined, cols, rows, this._configHelper);
} else {
this.remoteAuthority = this._environmentService.configuration.remoteAuthority;
}
const hasRemoteAuthority = !!this.remoteAuthority;
let launchRemotely = hasRemoteAuthority || forceExtHostProcess;
this.userHome = this._environmentService.userHome;
this.os = platform.OS;
if (launchRemotely) {
if (hasRemoteAuthority) {
this._remoteAgentService.getEnvironment().then(env => {
if (!env) {
return;
}
this.userHome = env.userHome.path;
this.os = env.os;
});
const forceExtHostProcess = (this._configHelper.config as any).extHostProcess;
if (shellLaunchConfig.cwd && typeof shellLaunchConfig.cwd === 'object') {
this.remoteAuthority = getRemoteAuthority(shellLaunchConfig.cwd);
} else {
this.remoteAuthority = this._environmentService.configuration.remoteAuthority;
}
const hasRemoteAuthority = !!this.remoteAuthority;
let launchRemotely = hasRemoteAuthority || forceExtHostProcess;
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot();
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
} else {
this._process = this._launchProcess(shellLaunchConfig, cols, rows);
this.userHome = this._environmentService.userHome;
this.os = platform.OS;
if (launchRemotely) {
if (hasRemoteAuthority) {
this._remoteAgentService.getEnvironment().then(env => {
if (!env) {
return;
}
this.userHome = env.userHome.path;
this.os = env.os;
});
}
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot();
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
} else {
this._process = await this._launchProcess(shellLaunchConfig, cols, rows, isScreenReaderModeEnabled);
}
}
this.processState = ProcessState.LAUNCHING;
@@ -146,8 +153,9 @@ export class TerminalProcessManager implements ITerminalProcessManager {
}
});
this._process.onProcessIdReady(pid => {
this.shellProcessId = pid;
this._process.onProcessReady((e: { pid: number, cwd: string }) => {
this.shellProcessId = e.pid;
this._initialCwd = e.cwd;
this._onProcessReady.fire();
// Send any queued data that's waiting
@@ -159,6 +167,9 @@ export class TerminalProcessManager implements ITerminalProcessManager {
this._process.onProcessTitleChanged(title => this._onProcessTitle.fire(title));
this._process.onProcessExit(exitCode => this._onExit(exitCode));
if (this._process.onProcessOverrideDimensions) {
this._process.onProcessOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e));
}
setTimeout(() => {
if (this.processState === ProcessState.LAUNCHING) {
@@ -167,9 +178,16 @@ export class TerminalProcessManager implements ITerminalProcessManager {
}, LAUNCHING_DURATION);
}
private _launchProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): ITerminalChildProcess {
private async _launchProcess(
shellLaunchConfig: IShellLaunchConfig,
cols: number,
rows: number,
isScreenReaderModeEnabled: boolean
): Promise<ITerminalChildProcess> {
if (!shellLaunchConfig.executable) {
this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig, this._terminalInstanceService.getDefaultShell(platform.platform));
const defaultConfig = await this._terminalInstanceService.getDefaultShellAndArgs();
shellLaunchConfig.executable = defaultConfig.shell;
shellLaunchConfig.args = defaultConfig.args;
}
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
@@ -179,9 +197,10 @@ export class TerminalProcessManager implements ITerminalProcessManager {
const lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null;
const envFromConfigValue = this._workspaceConfigurationService.inspect<ITerminalEnvironment | undefined>(`terminal.integrated.env.${platformKey}`);
const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions();
const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.setLocaleVariables);
const baseEnv = this._configHelper.config.inheritEnv ? process.env as platform.IProcessEnvironment : await this._terminalInstanceService.getMainProcessParentEnv();
const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.setLocaleVariables, baseEnv);
const useConpty = this._configHelper.config.windowsEnableConpty;
const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled;
return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty);
}
@@ -202,7 +221,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
}
public write(data: string): void {
if (this.shellProcessId) {
if (this.shellProcessId || this._processType === ProcessType.VirtualProcess) {
if (this._process) {
// Send data if the pty is ready
this._process.input(data);
@@ -214,10 +233,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
}
public getInitialCwd(): Promise<string> {
if (!this._process) {
return Promise.resolve('');
}
return this._process.getInitialCwd();
return Promise.resolve(this._initialCwd);
}
public getCwd(): Promise<string> {

View File

@@ -3,8 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as platform from 'vs/base/common/platform';
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService } 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';
@@ -21,9 +20,14 @@ 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';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export abstract class TerminalService extends CommonTerminalService implements ITerminalService {
protected _configHelper: IBrowserTerminalConfigHelper;
export class TerminalService extends CommonTerminalService implements ITerminalService {
private _configHelper: IBrowserTerminalConfigHelper;
public get configHelper(): ITerminalConfigHelper { return this._configHelper; }
constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@@ -36,13 +40,15 @@ export abstract class TerminalService extends CommonTerminalService implements I
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IExtensionService extensionService: IExtensionService,
@IFileService fileService: IFileService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@ITerminalNativeService readonly terminalNativeService: ITerminalNativeService,
@IQuickInputService readonly quickInputService: IQuickInputService,
@IConfigurationService readonly configurationService: IConfigurationService
) {
super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService, remoteAgentService);
super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService, remoteAgentService, terminalNativeService, quickInputService, configurationService);
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, this.terminalNativeService.linuxDistro);
}
public 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);
@@ -50,6 +56,16 @@ export abstract class TerminalService extends CommonTerminalService implements I
}
public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance {
if (shell.hideFromUser) {
const instance = this.createInstance(this._terminalFocusContextKey,
this.configHelper,
undefined,
shell,
true);
this._backgroundedTerminalInstances.push(instance);
this._initInstanceListeners(instance);
return instance;
}
const terminalTab = this._instantiationService.createInstance(TerminalTab,
this._terminalFocusContextKey,
this.configHelper,
@@ -68,6 +84,24 @@ export abstract class TerminalService extends CommonTerminalService implements I
return instance;
}
protected _showBackgroundTerminal(instance: ITerminalInstance): void {
this._backgroundedTerminalInstances.splice(this._backgroundedTerminalInstances.indexOf(instance), 1);
instance.shellLaunchConfig.hideFromUser = false;
const terminalTab = this._instantiationService.createInstance(TerminalTab,
this._terminalFocusContextKey,
this.configHelper,
this._terminalContainer,
instance);
this._terminalTabs.push(terminalTab);
terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed));
terminalTab.addDisposable(terminalTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged));
if (this.terminalInstances.length === 1) {
// It's the first instance so it should be made active automatically
this.setActiveInstanceByIndex(0);
}
this._onInstancesChanged.fire();
}
public focusFindWidget(): Promise<void> {
return this.showPanel(false).then(() => {
const panel = this._panelService.getActivePanel() as TerminalPanel;

View File

@@ -7,28 +7,30 @@ 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 { IDisposable, Disposable, DisposableStore } 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';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
const SPLIT_PANE_MIN_SIZE = 120;
const TERMINAL_MIN_USEFUL_SIZE = 250;
class SplitPaneContainer {
class SplitPaneContainer extends Disposable {
private _height: number;
private _width: number;
private _splitView: SplitView;
private _splitViewDisposables: IDisposable[];
private readonly _splitViewDisposables = this._register(new DisposableStore());
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
public orientation: Orientation,
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService
) {
super();
this._width = this._container.offsetWidth;
this._height = this._container.offsetHeight;
this._createSplitView();
@@ -37,8 +39,8 @@ class SplitPaneContainer {
private _createSplitView(): void {
this._splitView = new SplitView(this._container, { orientation: this.orientation });
this._splitViewDisposables = [];
this._splitViewDisposables.push(this._splitView.onDidSashReset(() => this._splitView.distributeViewSizes()));
this._splitViewDisposables.clear();
this._splitViewDisposables.add(this._splitView.onDidSashReset(() => this._splitView.distributeViewSizes()));
}
public split(instance: ITerminalInstance, index: number = this._children.length): void {
@@ -46,16 +48,21 @@ class SplitPaneContainer {
}
public resizePane(index: number, direction: Direction, amount: number): void {
// TODO: Should resize pane up/down resize the panel?
const isHorizontal = (direction === Direction.Left) || (direction === Direction.Right);
// Only resize the correct dimension
const isHorizontal = direction === Direction.Left || direction === Direction.Right;
if (isHorizontal && this.orientation !== Orientation.HORIZONTAL ||
!isHorizontal && this.orientation !== Orientation.VERTICAL) {
if ((isHorizontal && this.orientation !== Orientation.HORIZONTAL) ||
(!isHorizontal && this.orientation !== Orientation.VERTICAL)) {
// Resize the entire pane as a whole
if ((this.orientation === Orientation.HORIZONTAL && direction === Direction.Down) ||
(this.orientation === Orientation.VERTICAL && direction === Direction.Right)) {
amount *= -1;
}
this._layoutService.resizePart(Parts.PANEL_PART, amount);
return;
}
// Only resize when there is mor ethan one pane
// Resize left/right in horizontal or up/down in vertical
// Only resize when there is more than one pane
if (this._children.length <= 1) {
return;
}
@@ -143,8 +150,7 @@ class SplitPaneContainer {
while (this._container.children.length > 0) {
this._container.removeChild(this._container.children[0]);
}
this._splitViewDisposables.forEach(d => d.dispose());
this._splitViewDisposables = [];
this._splitViewDisposables.clear();
this._splitView.dispose();
// Create new split view with updated orientation
@@ -216,29 +222,33 @@ export class TerminalTab extends Disposable implements ITerminalTab {
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; }
private readonly _onDisposed: Emitter<ITerminalTab> = new Emitter<ITerminalTab>();
public readonly onDisposed: Event<ITerminalTab> = this._onDisposed.event;
private readonly _onInstancesChanged: Emitter<void> = new Emitter<void>();
public readonly onInstancesChanged: Event<void> = this._onInstancesChanged.event;
constructor(
terminalFocusContextKey: IContextKey<boolean>,
configHelper: ITerminalConfigHelper,
private _container: HTMLElement,
shellLaunchConfig: IShellLaunchConfig,
shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance,
@ITerminalService private readonly _terminalService: ITerminalService,
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super();
this._onDisposed = new Emitter<ITerminalTab>();
this._onInstancesChanged = new Emitter<void>();
const instance = this._terminalService.createInstance(
terminalFocusContextKey,
configHelper,
undefined,
shellLaunchConfig,
true);
let instance: ITerminalInstance;
if ('id' in shellLaunchConfigOrInstance) {
instance = shellLaunchConfigOrInstance;
} else {
instance = this._terminalService.createInstance(
terminalFocusContextKey,
configHelper,
undefined,
shellLaunchConfigOrInstance,
true);
}
this._terminalInstances.push(instance);
this._initInstanceListeners(instance);
this._activeInstanceIndex = 0;
@@ -343,7 +353,7 @@ export class TerminalTab extends Disposable implements ITerminalTab {
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);
const newLocal = this._instantiationService.createInstance(SplitPaneContainer, this._tabElement, orientation);
this._splitPaneContainer = newLocal;
this.terminalInstances.forEach(instance => this._splitPaneContainer!.split(instance));
}

View File

@@ -1,136 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
import { IThemeService } from 'vs/platform/theme/common/themeService';
export interface ITerminalCore {
buffer: any;
}
export interface ITypeAheadAddonTerminal {
_core: ITerminalCore;
on: any;
write(data: string): void;
cols: number;
__typeAheadQueue: string[];
__typeAheadState: TypeAheadState;
__typeAheadCurrentYBase: number;
__typeAheadCurrentY: number;
}
enum TypeAheadState {
/**
* The normal state, ready to type if it starts.
*/
Normal,
/**
* Something happens such that we cannot make a good guess on what to print,
* wait until the cursor row changes before proceeding.
*/
AwaitingRowChange
}
function isCharPrintable(data: string): boolean {
const code = data.charCodeAt(0);
return data.length === 1 && code >= 32 && code <= 126;
}
function init(terminal: any, processManager: ITerminalProcessManager, themeService: IThemeService): void {
const t = terminal as ITypeAheadAddonTerminal;
t.__typeAheadQueue = [];
t.__typeAheadState = TypeAheadState.Normal;
t.__typeAheadCurrentYBase = 0;
t.__typeAheadCurrentY = 0;
function typeAhead(data: string): void {
for (let i = 0; i < data.length; i++) {
t.__typeAheadQueue.push(data[i]);
}
t.write(data);
}
t.on('cursormove', () => {
// Reset if the cursor row changed
if (t._core.buffer.ybase !== t.__typeAheadCurrentYBase || t._core.buffer.y !== t.__typeAheadCurrentY) {
t.__typeAheadCurrentYBase = t._core.buffer.ybase;
t.__typeAheadCurrentY = t._core.buffer.y;
t.__typeAheadState = TypeAheadState.Normal;
}
});
t.on('data', (data: string) => {
// Exit if we're waiting for a row change
if (t.__typeAheadState === TypeAheadState.AwaitingRowChange) {
return;
}
// Only enable in the normal buffer
if (!t._core.buffer._hasScrollback) {
return;
}
// // Handle enter
// if (data === '\r') {
// typeAhead('\r\n');
// return;
// }
// // Left arrow
// if (data === '\x1b[D') {
// // TODO: How to stop it from going beyond prompt?
// typeAhead(String.fromCharCode(8));
// }
// // Right arrow
// if (data === '\x1b[C') {
// // TODO: How to stop it from going beyond prompt?
// typeAhead('\x1b[C');
// }
// // Backspace (DEL)
// if (data.charCodeAt(0) === 127) {
// // TODO: This would require knowing the prompt length to be able to shift everything
// }
if (!isCharPrintable(data)) {
t.__typeAheadState = TypeAheadState.AwaitingRowChange;
return;
}
if (t._core.buffer.x === t.cols - 1) {
// TODO: Does the space get added on Windows/Linux too?
data += ' \r';
}
typeAhead(data);
});
processManager.onBeforeProcessData(event => {
let consumeCount = 0;
for (let i = 0; i < event.data.length; i++) {
if (t.__typeAheadQueue[0] === event.data[i]) {
t.__typeAheadQueue.shift();
consumeCount++;
} else {
t.__typeAheadQueue.length = 0;
break;
}
}
if (consumeCount === event.data.length) {
event.data = '';
} else if (consumeCount > 0) {
event.data = event.data.substr(consumeCount);
}
});
}
export function apply(terminalConstructor: typeof XTermTerminal) {
(<any>terminalConstructor.prototype).typeAheadInit = function (processManager: ITerminalProcessManager, themeService: IThemeService): void {
init(this, processManager, themeService);
};
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
const WIDGET_HEIGHT = 29;
@@ -12,7 +12,7 @@ export class TerminalWidgetManager implements IDisposable {
private _xtermViewport: HTMLElement | null;
private _messageWidget: MessageWidget;
private _messageListeners: IDisposable[] = [];
private readonly _messageListeners = new DisposableStore();
constructor(
terminalWrapper: HTMLElement
@@ -30,6 +30,7 @@ export class TerminalWidgetManager implements IDisposable {
this._container = null;
}
this._xtermViewport = null;
this._messageListeners.dispose();
}
private _initTerminalHeightWatcher(terminalWrapper: HTMLElement) {
@@ -47,14 +48,14 @@ export class TerminalWidgetManager implements IDisposable {
return;
}
dispose(this._messageWidget);
this._messageListeners = dispose(this._messageListeners);
this._messageListeners.clear();
this._messageWidget = new MessageWidget(this._container, left, top, text);
}
public closeMessage(): void {
this._messageListeners = dispose(this._messageListeners);
this._messageListeners.clear();
if (this._messageWidget) {
this._messageListeners.push(MessageWidget.fadeOut(this._messageWidget));
this._messageListeners.add(MessageWidget.fadeOut(this._messageWidget));
}
}

View File

@@ -3,13 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { RawContextKey, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { OperatingSystem } from 'vs/base/common/platform';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
export const TERMINAL_PANEL_ID = 'workbench.panel.terminal';
@@ -45,6 +47,7 @@ export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverM
export const EXT_HOST_CREATION_DELAY = 100;
export const ITerminalService = createDecorator<ITerminalService>('terminalService');
export const ITerminalNativeService = createDecorator<ITerminalNativeService>('terminalNativeService');
export const TerminalCursorStyle = {
BLOCK: 'block',
@@ -54,10 +57,14 @@ export const TerminalCursorStyle = {
export const TERMINAL_CONFIG_SECTION = 'terminal.integrated';
export const TERMINAL_ACTION_CATEGORY = nls.localize('terminalCategory', "Terminal");
export const DEFAULT_LETTER_SPACING = 0;
export const MINIMUM_LETTER_SPACING = -5;
export const DEFAULT_LINE_HEIGHT = 1;
export const SHELL_PATH_INVALID_EXIT_CODE = -1;
export const SHELL_PATH_DIRECTORY_EXIT_CODE = -2;
export const SHELL_CWD_INVALID_EXIT_CODE = -3;
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
@@ -92,6 +99,7 @@ export interface ITerminalConfiguration {
cwd: string;
confirmOnExit: boolean;
enableBell: boolean;
inheritEnv: boolean;
env: {
linux: { [key: string]: string };
osx: { [key: string]: string };
@@ -101,21 +109,19 @@ export interface ITerminalConfiguration {
experimentalBufferImpl: 'JsArray' | 'TypedArray';
splitCwd: 'workspaceRoot' | 'initial' | 'inherited';
windowsEnableConpty: boolean;
enableLatencyMitigation: boolean;
experimentalRefreshOnResume: boolean;
}
export interface ITerminalConfigHelper {
config: ITerminalConfiguration;
onWorkspacePermissionsChanged: Event<boolean>;
configFontIsMonospace(): boolean;
getFont(): ITerminalFont;
/**
* Merges the default shell path and args into the provided launch configuration
*/
mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, defaultShell: string, platformOverride?: platform.Platform): void;
/** Sets whether a workspace shell configuration is allowed or not */
setWorkspaceShellAllowed(isAllowed: boolean): void;
checkWorkspaceShellPermissions(osOverride?: platform.OperatingSystem): boolean;
checkWorkspaceShellPermissions(osOverride?: OperatingSystem): boolean;
}
export interface ITerminalFont {
@@ -162,7 +168,7 @@ export interface IShellLaunchConfig {
env?: ITerminalEnvironment;
/**
* Whether to ignore a custom cwd from the `terminal.integrated.cwd` settings key (eg. if the
* Whether to ignore a custom cwd from the `terminal.integrated.cwd` settings key (e.g. if the
* shell is being launched by an extension).
*/
ignoreConfigurationCwd?: boolean;
@@ -179,11 +185,15 @@ export interface IShellLaunchConfig {
initialText?: string;
/**
* When true the terminal will be created with no process. This is primarily used to give
* extensions full control over the terminal.
* @deprecated use `isVirtualProcess`
*/
isRendererOnly?: boolean;
/**
* When true an extension is acting as the terminal's process.
*/
isVirtualProcess?: boolean;
/**
* Whether the terminal process environment should be exactly as provided in
* `TerminalOptions.env`. When this is false (default), the environment will be based on the
@@ -192,6 +202,15 @@ export interface IShellLaunchConfig {
* provided as nothing will be inherited from the process or any configuration.
*/
strictEnv?: boolean;
/**
* When enabled the terminal will run the process as normal but not be surfaced to the user
* until `Terminal.show` is called. The typical usage for this is when you need to run
* something that may need interactivity but only want to tell the user about it when
* interaction is needed. Note that the terminals will still be exposed to all extensions
* as normal.
*/
hideFromUser?: boolean;
}
export interface ITerminalService {
@@ -199,18 +218,22 @@ export interface ITerminalService {
activeTabIndex: number;
configHelper: ITerminalConfigHelper;
terminalInstances: ITerminalInstance[];
terminalTabs: ITerminalTab[];
onActiveTabChanged: Event<void>;
onTabDisposed: Event<ITerminalTab>;
onInstanceCreated: Event<ITerminalInstance>;
onInstanceDisposed: Event<ITerminalInstance>;
onInstanceProcessIdReady: Event<ITerminalInstance>;
onInstanceDimensionsChanged: Event<ITerminalInstance>;
onInstanceMaximumDimensionsChanged: Event<ITerminalInstance>;
onInstanceRequestExtHostProcess: Event<ITerminalProcessExtHostRequest>;
onInstanceRequestVirtualProcess: Event<ITerminalVirtualProcessRequest>;
onInstancesChanged: Event<void>;
onInstanceTitleChanged: Event<ITerminalInstance>;
onActiveInstanceChanged: Event<ITerminalInstance | undefined>;
terminalInstances: ITerminalInstance[];
terminalTabs: ITerminalTab[];
onRequestAvailableShells: Event<IAvailableShellsRequest>;
/**
* Creates a terminal.
@@ -228,7 +251,7 @@ export interface ITerminalService {
* Creates a raw terminal instance, this should not be used outside of the terminal part.
*/
createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance;
getInstanceFromId(terminalId: number): ITerminalInstance;
getInstanceFromId(terminalId: number): ITerminalInstance | undefined;
getInstanceFromIndex(terminalIndex: number): ITerminalInstance;
getTabLabels(): string[];
getActiveInstance(): ITerminalInstance | null;
@@ -256,9 +279,9 @@ export interface ITerminalService {
findNext(): void;
findPrevious(): void;
selectDefaultWindowsShell(): Promise<void>;
setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
getDefaultShell(p: platform.Platform): string;
selectDefaultWindowsShell(): Promise<string | undefined>;
setWorkspaceShellAllowed(isAllowed: boolean): void;
/**
@@ -274,6 +297,28 @@ export interface ITerminalService {
extHostReady(remoteAuthority: string): void;
requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void;
requestVirtualProcess(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void;
}
/**
* Provides access to native or electron APIs to other terminal services.
*/
export interface ITerminalNativeService {
_serviceBrand: any;
readonly linuxDistro: LinuxDistro;
readonly onOpenFileRequest: Event<IOpenFileRequest>;
readonly onOsResume: Event<void>;
getWindowsBuildNumber(): number;
whenFileDeleted(path: URI): Promise<void>;
getWslPath(path: string): Promise<string>;
}
export interface IShellDefinition {
label: string;
path: string;
}
export const enum Direction {
@@ -341,6 +386,8 @@ export interface ITerminalInstance {
readonly cols: number;
readonly rows: number;
readonly maxCols: number;
readonly maxRows: number;
/**
* The process ID of the shell process, this is undefined when there is no process associated
@@ -359,12 +406,10 @@ export interface ITerminalInstance {
onDisposed: Event<ITerminalInstance>;
onFocused: Event<ITerminalInstance>;
onProcessIdReady: Event<ITerminalInstance>;
onRequestExtHostProcess: Event<ITerminalInstance>;
onDimensionsChanged: Event<void>;
onMaximumDimensionsChanged: Event<void>;
onFocus: Event<ITerminalInstance>;
@@ -426,7 +471,7 @@ export interface ITerminalInstance {
/**
* Whether to disable layout for the terminal. This is useful when the size of the terminal is
* being manipulating (eg. adding a split pane) and we want the terminal to ignore particular
* being manipulating (e.g. adding a split pane) and we want the terminal to ignore particular
* resize events.
*/
disableLayout: boolean;
@@ -488,7 +533,7 @@ export interface ITerminalInstance {
/**
* Copies the terminal selection to the clipboard.
*/
copySelection(): void;
copySelection(): Promise<void>;
/**
* Current selection in the terminal.
@@ -654,7 +699,7 @@ export interface ITerminalProcessManager extends IDisposable {
readonly ptyProcessReady: Promise<void>;
readonly shellProcessId: number;
readonly remoteAuthority: string | undefined;
readonly os: platform.OperatingSystem | undefined;
readonly os: OperatingSystem | undefined;
readonly userHome: string | undefined;
readonly onProcessReady: Event<void>;
@@ -662,10 +707,10 @@ export interface ITerminalProcessManager extends IDisposable {
readonly onProcessData: Event<string>;
readonly onProcessTitle: Event<string>;
readonly onProcessExit: Event<number>;
readonly onProcessOverrideDimensions: Event<ITerminalDimensions | undefined>;
addDisposable(disposable: IDisposable): void;
dispose(immediate?: boolean): void;
createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): void;
createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise<void>;
write(data: string): void;
setDimensions(cols: number, rows: number): void;
@@ -698,8 +743,9 @@ export interface ITerminalProcessExtHostProxy extends IDisposable {
emitData(data: string): void;
emitTitle(title: string): void;
emitPid(pid: number): void;
emitReady(pid: number, cwd: string): void;
emitExit(exitCode: number): void;
emitOverrideDimensions(dimensions: ITerminalDimensions | undefined): void;
emitInitialCwd(initialCwd: string): void;
emitCwd(cwd: string): void;
emitLatency(latency: number): void;
@@ -721,6 +767,20 @@ export interface ITerminalProcessExtHostRequest {
isWorkspaceShellAllowed: boolean;
}
export interface ITerminalVirtualProcessRequest {
proxy: ITerminalProcessExtHostProxy;
cols: number;
rows: number;
}
export interface IAvailableShellsRequest {
(shells: IShellDefinition[]): void;
}
export interface IDefaultShellAndArgsRequest {
(shell: string, args: string[] | string | undefined): void;
}
export enum LinuxDistro {
Fedora,
Ubuntu,
@@ -738,8 +798,9 @@ export interface IWindowsShellHelper extends IDisposable {
export interface ITerminalChildProcess {
onProcessData: Event<string>;
onProcessExit: Event<number>;
onProcessIdReady: Event<number>;
onProcessReady: Event<{ pid: number, cwd: string }>;
onProcessTitleChanged: Event<string>;
onProcessOverrideDimensions?: Event<ITerminalDimensions | undefined>;
/**
* Shutdown the terminal process.

View File

@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import { registerColor, ColorIdentifier } from 'vs/platform/theme/common/colorRegistry';
import { registerColor, ColorIdentifier, ColorDefaults } from 'vs/platform/theme/common/colorRegistry';
import { PANEL_BORDER, PANEL_BACKGROUND } from 'vs/workbench/common/theme';
/**
@@ -37,7 +37,7 @@ export const TERMINAL_BORDER_COLOR = registerColor('terminal.border', {
hc: PANEL_BORDER
}, nls.localize('terminal.border', 'The color of the border that separates split panes within the terminal. This defaults to panel.border.'));
const ansiColorMap = {
export const ansiColorMap: { [key: string]: { index: number, defaults: ColorDefaults } } = {
'terminal.ansiBlack': {
index: 0,
defaults: {

View File

@@ -22,6 +22,7 @@ export const enum TERMINAL_COMMAND_ID {
MOVE_TO_LINE_START = 'workbench.action.terminal.moveToLineStart',
MOVE_TO_LINE_END = 'workbench.action.terminal.moveToLineEnd',
NEW = 'workbench.action.terminal.new',
NEW_LOCAL = 'workbench.action.terminal.newLocal',
NEW_IN_ACTIVE_WORKSPACE = 'workbench.action.terminal.newInActiveWorkspace',
SPLIT = 'workbench.action.terminal.split',
SPLIT_IN_ACTIVE_WORKSPACE = 'workbench.action.terminal.splitInActiveWorkspace',

View File

@@ -58,6 +58,7 @@ export function addTerminalEnvironmentKeys(env: platform.IProcessEnvironment, ve
if (setLocaleVariables) {
env['LANG'] = _getLangEnvVariable(locale);
}
env['COLORTERM'] = 'truecolor';
}
function mergeNonNullKeys(env: platform.IProcessEnvironment, other: ITerminalEnvironment | NodeJS.ProcessEnv | undefined) {
@@ -92,7 +93,8 @@ function _getLangEnvVariable(locale?: string) {
if (n === 1) {
// app.getLocale can return just a language without a variant, fill in the variant for
// supported languages as many shells expect a 2-part locale.
const languageVariants = {
const languageVariants: { [key: string]: string } = {
cs: 'CZ',
de: 'DE',
en: 'US',
es: 'ES',
@@ -161,34 +163,45 @@ export function escapeNonWindowsPath(path: string): string {
return newPath;
}
export function mergeDefaultShellPathAndArgs(
shell: IShellLaunchConfig,
export function getDefaultShell(
fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined },
isWorkspaceShellAllowed: boolean,
defaultShell: string,
isWoW64: boolean,
windir: string | undefined,
platformOverride: platform.Platform = platform.platform
): void {
): string {
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
const shellConfigValue = fetchSetting(`terminal.integrated.shell.${platformKey}`);
const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`);
shell.executable = (isWorkspaceShellAllowed ? <string>shellConfigValue.value : <string>shellConfigValue.user) || (<string | null>shellConfigValue.default || defaultShell);
shell.args = (isWorkspaceShellAllowed ? <string[]>shellArgsConfigValue.value : <string[]>shellArgsConfigValue.user) || <string[]>shellArgsConfigValue.default;
let executable = (isWorkspaceShellAllowed ? <string>shellConfigValue.value : <string>shellConfigValue.user) || (<string | null>shellConfigValue.default || defaultShell);
// 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 && shell.executable.toLowerCase().indexOf(sysnativePath) === 0) {
shell.executable = path.join(process.env.windir, 'System32', shell.executable.substr(sysnativePath.length));
if ((platformOverride === platform.Platform.Windows) && !isWoW64 && windir) {
const sysnativePath = path.join(windir, 'Sysnative').toLowerCase();
if (executable && executable.toLowerCase().indexOf(sysnativePath) === 0) {
executable = path.join(windir, 'System32', executable.substr(sysnativePath.length));
}
}
// Convert / to \ on Windows for convenience
if (shell.executable && platformOverride === platform.Platform.Windows) {
shell.executable = shell.executable.replace(/\//g, '\\');
if (executable && platformOverride === platform.Platform.Windows) {
executable = executable.replace(/\//g, '\\');
}
return executable;
}
export function getDefaultShellArgs(
fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined },
isWorkspaceShellAllowed: boolean,
platformOverride: platform.Platform = platform.platform
): string[] {
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`);
const args = (isWorkspaceShellAllowed ? <string[]>shellArgsConfigValue.value : <string[]>shellArgsConfigValue.user) || <string[]>shellArgsConfigValue.default;
return args;
}
export function createTerminalEnvironment(
@@ -198,7 +211,8 @@ export function createTerminalEnvironment(
configurationResolverService: IConfigurationResolverService | undefined,
isWorkspaceShellAllowed: boolean,
version: string | undefined,
setLocaleVariables: boolean
setLocaleVariables: boolean,
baseEnv: platform.IProcessEnvironment
): platform.IProcessEnvironment {
// Create a terminal environment based on settings, launch config and permissions
let env: platform.IProcessEnvironment = {};
@@ -207,7 +221,7 @@ export function createTerminalEnvironment(
mergeNonNullKeys(env, shellLaunchConfig.env);
} else {
// Merge process env with the env from config and from shellLaunchConfig
mergeNonNullKeys(env, process.env);
mergeNonNullKeys(env, baseEnv);
// const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
// const envFromConfigValue = this._workspaceConfigurationService.inspect<ITerminalEnvironment | undefined>(`terminal.integrated.env.${platformKey}`);
@@ -235,4 +249,4 @@ export function createTerminalEnvironment(
addTerminalEnvironmentKeys(env, version, platform.locale, setLocaleVariables);
}
return env;
}
}

View File

@@ -4,38 +4,39 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import * as nls from 'vs/nls';
let hasReceivedResponse: boolean = false;
export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerminalProcessExtHostProxy {
private _disposables: IDisposable[] = [];
export class TerminalProcessExtHostProxy extends Disposable implements ITerminalChildProcess, ITerminalProcessExtHostProxy {
private readonly _onProcessData = new Emitter<string>();
public get onProcessData(): Event<string> { return this._onProcessData.event; }
private readonly _onProcessExit = new Emitter<number>();
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
private readonly _onProcessIdReady = new Emitter<number>();
public get onProcessIdReady(): Event<number> { return this._onProcessIdReady.event; }
private readonly _onProcessTitleChanged = new Emitter<string>();
public get onProcessTitleChanged(): Event<string> { return this._onProcessTitleChanged.event; }
private readonly _onProcessData = this._register(new Emitter<string>());
public readonly onProcessData: Event<string> = this._onProcessData.event;
private readonly _onProcessExit = this._register(new Emitter<number>());
public readonly onProcessExit: Event<number> = this._onProcessExit.event;
private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>();
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
private readonly _onProcessTitleChanged = this._register(new Emitter<string>());
public readonly onProcessTitleChanged: Event<string> = this._onProcessTitleChanged.event;
private readonly _onProcessOverrideDimensions = new Emitter<ITerminalDimensions | undefined>();
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { return this._onProcessOverrideDimensions.event; }
private readonly _onInput = new Emitter<string>();
public get onInput(): Event<string> { return this._onInput.event; }
private readonly _onResize: Emitter<{ cols: number, rows: number }> = new Emitter<{ cols: number, rows: number }>();
public get onResize(): Event<{ cols: number, rows: number }> { return this._onResize.event; }
private readonly _onShutdown = new Emitter<boolean>();
public get onShutdown(): Event<boolean> { return this._onShutdown.event; }
private readonly _onRequestInitialCwd = new Emitter<void>();
public get onRequestInitialCwd(): Event<void> { return this._onRequestInitialCwd.event; }
private readonly _onRequestCwd = new Emitter<void>();
public get onRequestCwd(): Event<void> { return this._onRequestCwd.event; }
private readonly _onRequestLatency = new Emitter<void>();
public get onRequestLatency(): Event<void> { return this._onRequestLatency.event; }
private readonly _onInput = this._register(new Emitter<string>());
public readonly onInput: Event<string> = this._onInput.event;
private readonly _onResize: Emitter<{ cols: number, rows: number }> = this._register(new Emitter<{ cols: number, rows: number }>());
public readonly onResize: Event<{ cols: number, rows: number }> = this._onResize.event;
private readonly _onShutdown = this._register(new Emitter<boolean>());
public readonly onShutdown: Event<boolean> = this._onShutdown.event;
private readonly _onRequestInitialCwd = this._register(new Emitter<void>());
public readonly onRequestInitialCwd: Event<void> = this._onRequestInitialCwd.event;
private readonly _onRequestCwd = this._register(new Emitter<void>());
public readonly onRequestCwd: Event<void> = this._onRequestCwd.event;
private readonly _onRequestLatency = this._register(new Emitter<void>());
public readonly onRequestLatency: Event<void> = this._onRequestLatency.event;
private _pendingInitialCwdRequests: ((value?: string | Thenable<string>) => void)[] = [];
private _pendingCwdRequests: ((value?: string | Thenable<string>) => void)[] = [];
@@ -51,20 +52,23 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm
@ITerminalService private readonly _terminalService: ITerminalService,
@IRemoteAgentService readonly remoteAgentService: IRemoteAgentService
) {
remoteAgentService.getEnvironment().then(env => {
if (!env) {
throw new Error('Could not fetch environment');
}
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper.checkWorkspaceShellPermissions(env.os));
});
if (!hasReceivedResponse) {
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
}
}
super();
public dispose(): void {
this._disposables.forEach(d => d.dispose());
this._disposables.length = 0;
// Request a process if needed, if this is a virtual process this step can be skipped as
// there is no real "process" and we know it's ready on the ext host already.
if (shellLaunchConfig.isVirtualProcess) {
this._terminalService.requestVirtualProcess(this, cols, rows);
} else {
remoteAgentService.getEnvironment().then(env => {
if (!env) {
throw new Error('Could not fetch environment');
}
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper.checkWorkspaceShellPermissions(env.os));
});
if (!hasReceivedResponse) {
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
}
}
}
public emitData(data: string): void {
@@ -76,8 +80,8 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm
this._onProcessTitleChanged.fire(title);
}
public emitPid(pid: number): void {
this._onProcessIdReady.fire(pid);
public emitReady(pid: number, cwd: string): void {
this._onProcessReady.fire({ pid, cwd });
}
public emitExit(exitCode: number): void {
@@ -85,6 +89,10 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm
this.dispose();
}
public emitOverrideDimensions(dimensions: ITerminalDimensions | undefined): void {
this._onProcessOverrideDimensions.fire(dimensions);
}
public emitInitialCwd(initialCwd: string): void {
while (this._pendingInitialCwdRequests.length > 0) {
this._pendingInitialCwdRequests.pop()!(initialCwd);

View File

@@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalNativeService, IShellDefinition, IAvailableShellsRequest, ITerminalVirtualProcessRequest } from 'vs/workbench/contrib/terminal/common/terminal';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { URI } from 'vs/base/common/uri';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
@@ -17,10 +17,13 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { isWindows, Platform } from 'vs/base/common/platform';
import { isWindows, isMacintosh, OperatingSystem } from 'vs/base/common/platform';
import { basename } from 'vs/base/common/path';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { timeout } from 'vs/base/common/async';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import { IPickOptions, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
export abstract class TerminalService implements ITerminalService {
public _serviceBrand: any;
@@ -30,6 +33,7 @@ export abstract class TerminalService implements ITerminalService {
protected _findWidgetVisible: IContextKey<boolean>;
protected _terminalContainer: HTMLElement;
protected _terminalTabs: ITerminalTab[] = [];
protected _backgroundedTerminalInstances: ITerminalInstance[] = [];
protected get _terminalInstances(): ITerminalInstance[] {
return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), <ITerminalInstance[]>[]);
}
@@ -51,8 +55,12 @@ export abstract class TerminalService implements ITerminalService {
public get onInstanceProcessIdReady(): Event<ITerminalInstance> { return this._onInstanceProcessIdReady.event; }
protected readonly _onInstanceRequestExtHostProcess = new Emitter<ITerminalProcessExtHostRequest>();
public get onInstanceRequestExtHostProcess(): Event<ITerminalProcessExtHostRequest> { return this._onInstanceRequestExtHostProcess.event; }
protected readonly _onInstanceRequestVirtualProcess = new Emitter<ITerminalVirtualProcessRequest>();
public get onInstanceRequestVirtualProcess(): Event<ITerminalVirtualProcessRequest> { return this._onInstanceRequestVirtualProcess.event; }
protected readonly _onInstanceDimensionsChanged = new Emitter<ITerminalInstance>();
public get onInstanceDimensionsChanged(): Event<ITerminalInstance> { return this._onInstanceDimensionsChanged.event; }
protected readonly _onInstanceMaximumDimensionsChanged = new Emitter<ITerminalInstance>();
public get onInstanceMaximumDimensionsChanged(): Event<ITerminalInstance> { return this._onInstanceMaximumDimensionsChanged.event; }
protected readonly _onInstancesChanged = new Emitter<void>();
public get onInstancesChanged(): Event<void> { return this._onInstancesChanged.event; }
protected readonly _onInstanceTitleChanged = new Emitter<ITerminalInstance>();
@@ -61,6 +69,8 @@ export abstract class TerminalService implements ITerminalService {
public get onActiveInstanceChanged(): Event<ITerminalInstance | undefined> { return this._onActiveInstanceChanged.event; }
protected readonly _onTabDisposed = new Emitter<ITerminalTab>();
public get onTabDisposed(): Event<ITerminalTab> { return this._onTabDisposed.event; }
protected readonly _onRequestAvailableShells = new Emitter<IAvailableShellsRequest>();
public get onRequestAvailableShells(): Event<IAvailableShellsRequest> { return this._onRequestAvailableShells.event; }
public abstract get configHelper(): ITerminalConfigHelper;
@@ -73,13 +83,18 @@ export abstract class TerminalService implements ITerminalService {
@IDialogService private readonly _dialogService: IDialogService,
@IExtensionService private readonly _extensionService: IExtensionService,
@IFileService protected readonly _fileService: IFileService,
@IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService
@IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService,
@ITerminalNativeService private readonly _terminalNativeService: ITerminalNativeService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
this._activeTabIndex = 0;
this._isShuttingDown = false;
this._findState = new FindReplaceState();
lifecycleService.onBeforeShutdown(event => event.veto(this._onBeforeShutdown()));
lifecycleService.onShutdown(() => this._onShutdown());
this._terminalNativeService.onOpenFileRequest(e => this._onOpenFileRequest(e));
this._terminalNativeService.onOsResume(() => this._onOsResume());
this._terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this._contextKeyService);
this._findWidgetVisible = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService);
this.onTabDisposed(tab => this._removeTab(tab));
@@ -101,14 +116,10 @@ export abstract class TerminalService implements ITerminalService {
this.onInstancesChanged(() => updateTerminalContextKeys());
}
protected abstract _getWslPath(path: string): Promise<string>;
protected abstract _getWindowsBuildNumber(): number;
protected abstract _showBackgroundTerminal(instance: ITerminalInstance): void;
public abstract refreshActiveTab(): void;
public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
public abstract createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance;
public abstract getDefaultShell(platform: Platform): string;
public abstract selectDefaultWindowsShell(): Promise<string | undefined>;
public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
public createTerminalRenderer(name: string): ITerminalInstance {
@@ -133,6 +144,11 @@ export abstract class TerminalService implements ITerminalService {
});
}
public requestVirtualProcess(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void {
// Don't need to wait on extensions here as this can only be triggered by an extension
this._onInstanceRequestVirtualProcess.fire({ proxy, cols, rows });
}
public extHostReady(remoteAuthority: string): void {
this._extHostsReady[remoteAuthority] = true;
}
@@ -163,6 +179,31 @@ export abstract class TerminalService implements ITerminalService {
this.terminalInstances.forEach(instance => instance.dispose(true));
}
private _onOpenFileRequest(request: IOpenFileRequest): void {
// if the request to open files is coming in from the integrated terminal (identified though
// the termProgram variable) and we are instructed to wait for editors close, wait for the
// marker file to get deleted and then focus back to the integrated terminal.
if (request.termProgram === 'vscode' && request.filesToWait) {
const waitMarkerFileUri = URI.revive(request.filesToWait.waitMarkerFileUri);
this._terminalNativeService.whenFileDeleted(waitMarkerFileUri).then(() => {
if (this.terminalInstances.length > 0) {
const terminal = this.getActiveInstance();
if (terminal) {
terminal.focus();
}
}
});
}
}
private _onOsResume(): void {
const activeTab = this.getActiveTab();
if (!activeTab) {
return;
}
activeTab.terminalInstances.forEach(instance => instance.forceRedraw());
}
public getTabLabels(): string[] {
return this._terminalTabs.filter(tab => tab.terminalInstances.length > 0).map((tab, index) => `${index + 1}: ${tab.title ? tab.title : ''}`);
}
@@ -206,6 +247,11 @@ export abstract class TerminalService implements ITerminalService {
}
}
public refreshActiveTab(): void {
// Fire active instances changed
this._onActiveTabChanged.fire();
}
public getActiveTab(): ITerminalTab | null {
if (this._activeTabIndex < 0 || this._activeTabIndex >= this._terminalTabs.length) {
return null;
@@ -221,8 +267,21 @@ export abstract class TerminalService implements ITerminalService {
return tab.activeInstance;
}
public getInstanceFromId(terminalId: number): ITerminalInstance {
return this.terminalInstances[this._getIndexFromId(terminalId)];
public getInstanceFromId(terminalId: number): ITerminalInstance | undefined {
let bgIndex = -1;
this._backgroundedTerminalInstances.forEach((terminalInstance, i) => {
if (terminalInstance.id === terminalId) {
bgIndex = i;
}
});
if (bgIndex !== -1) {
return this._backgroundedTerminalInstances[bgIndex];
}
try {
return this.terminalInstances[this._getIndexFromId(terminalId)];
} catch {
return undefined;
}
}
public getInstanceFromIndex(terminalIndex: number): ITerminalInstance {
@@ -230,6 +289,11 @@ export abstract class TerminalService implements ITerminalService {
}
public setActiveInstance(terminalInstance: ITerminalInstance): void {
// If this was a hideFromUser terminal created by the API this was triggered by show,
// in which case we need to create the terminal tab
if (terminalInstance.shellLaunchConfig.hideFromUser) {
this._showBackgroundTerminal(terminalInstance);
}
this.setActiveInstanceByIndex(this._getIndexFromId(terminalInstance.id));
}
@@ -329,6 +393,7 @@ export abstract class TerminalService implements ITerminalService {
instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged));
instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady));
instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onInstanceMaximumDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged));
}
@@ -441,15 +506,14 @@ export abstract class TerminalService implements ITerminalService {
public preparePathForTerminalAsync(originalPath: string, executable: string, title: string): Promise<string> {
return new Promise<string>(c => {
const exe = executable;
if (!exe) {
if (!executable) {
c(originalPath);
return;
}
const hasSpace = originalPath.indexOf(' ') !== -1;
const pathBasename = basename(exe, '.exe');
const pathBasename = basename(executable, '.exe');
const isPowerShell = pathBasename === 'pwsh' ||
title === 'pwsh' ||
pathBasename === 'powershell' ||
@@ -463,8 +527,10 @@ export abstract class TerminalService implements ITerminalService {
if (isWindows) {
// 17063 is the build number where wsl path was introduced.
// Update Windows uriPath to be executed in WSL.
if (((exe.indexOf('wsl') !== -1) || ((exe.indexOf('bash.exe') !== -1) && (exe.indexOf('git') === -1))) && (this._getWindowsBuildNumber() >= 17063)) {
c(this._getWslPath(originalPath));
const lowerExecutable = executable.toLowerCase();
if (this._terminalNativeService.getWindowsBuildNumber() >= 17063 &&
(lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1))) {
c(this._terminalNativeService.getWslPath(originalPath));
return;
} else if (hasSpace) {
c('"' + originalPath + '"');
@@ -476,4 +542,34 @@ export abstract class TerminalService implements ITerminalService {
c(escapeNonWindowsPath(originalPath));
});
}
public selectDefaultWindowsShell(): Promise<void> {
return this._detectWindowsShells().then(shells => {
const options: IPickOptions<IQuickPickItem> = {
placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings")
};
const quickPickItems = shells.map(s => {
return { label: s.label, description: s.path };
});
return this._quickInputService.pick(quickPickItems, options).then(async value => {
if (!value) {
return undefined;
}
const shell = value.description;
const env = await this._remoteAgentService.getEnvironment();
let platformKey: string;
if (env) {
platformKey = env.os === OperatingSystem.Windows ? 'windows' : (env.os === OperatingSystem.Macintosh ? 'osx' : 'linux');
} else {
platformKey = isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux');
}
await this._configurationService.updateValue(`terminal.integrated.shell.${platformKey}`, shell, ConfigurationTarget.USER).then(() => shell);
return Promise.resolve();
});
});
}
private _detectWindowsShells(): Promise<IShellDefinition[]> {
return new Promise(r => this._onRequestAvailableShells.fire(r));
}
}

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* 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 { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { Platform } from 'vs/base/common/platform';
export function registerShellConfiguration(getSystemShell?: (p: Platform) => string): void {
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
configurationRegistry.registerConfiguration({
id: 'terminal',
order: 100,
title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
type: 'object',
properties: {
'terminal.integrated.shell.linux': {
markdownDescription:
getSystemShell
? nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Linux))
: nls.localize('terminal.integrated.shell.linux.noDefault', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
type: ['string', 'null'],
default: null
},
'terminal.integrated.shell.osx': {
markdownDescription:
getSystemShell
? nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Mac))
: nls.localize('terminal.integrated.shell.osx.noDefault', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
type: ['string', 'null'],
default: null
},
'terminal.integrated.shell.windows': {
markdownDescription:
getSystemShell
? nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Windows))
: nls.localize('terminal.integrated.shell.windows.noDefault', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
type: ['string', 'null'],
default: null
}
}
});
}

View File

@@ -3,41 +3,14 @@
* 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 nls from 'vs/nls';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Registry } from 'vs/platform/registry/common/platform';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/electron-browser/terminalInstanceService';
import { TerminalService } from 'vs/workbench/contrib/terminal/electron-browser/terminalService';
import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal';
import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal';
import { registerShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalShellConfig';
import { TerminalNativeService } from 'vs/workbench/contrib/terminal/electron-browser/terminalNativeService';
import { ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal';
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
configurationRegistry.registerConfiguration({
id: 'terminal',
order: 100,
title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
type: 'object',
properties: {
'terminal.integrated.shell.linux': {
markdownDescription: nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(platform.Platform.Linux)),
type: ['string', 'null'],
default: null
},
'terminal.integrated.shell.osx': {
markdownDescription: nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(platform.Platform.Mac)),
type: ['string', 'null'],
default: null
},
'terminal.integrated.shell.windows': {
markdownDescription: nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(platform.Platform.Windows)),
type: ['string', 'null'],
default: null
}
}
});
registerSingleton(ITerminalService, TerminalService, true);
registerShellConfiguration(getSystemShell);
registerSingleton(ITerminalNativeService, TerminalNativeService, true);
registerSingleton(ITerminalInstanceService, TerminalInstanceService, true);

View File

@@ -3,61 +3,87 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { Terminal as XTermTerminal } from 'vscode-xterm';
import { ITerminalInstance, IWindowsShellHelper, ITerminalConfigHelper, ITerminalProcessManager, IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalInstance, IWindowsShellHelper, IShellLaunchConfig, ITerminalChildProcess, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY } from 'vs/workbench/contrib/terminal/common/terminal';
import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/node/windowsShellHelper';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager';
import { IProcessEnvironment, Platform } from 'vs/base/common/platform';
import { IProcessEnvironment, platform, Platform } from 'vs/base/common/platform';
import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess';
import * as typeAheadAddon from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon';
import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal';
import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal';
import { Terminal as XTermTerminal } from 'xterm';
import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links';
import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { getDefaultShell, getDefaultShellArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage';
import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
let Terminal: typeof XTermTerminal;
let WebLinksAddon: typeof XTermWebLinksAddon;
let SearchAddon: typeof XTermSearchAddon;
/**
* A service used by TerminalInstance (and components owned by it) that allows it to break its
* dependency on electron-browser and node layers, while at the same time avoiding a cyclic
* dependency on ITerminalService.
*/
export class TerminalInstanceService implements ITerminalInstanceService {
public _serviceBrand: any;
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IStorageService private readonly _storageService: IStorageService
) {
}
public async getXtermConstructor(): Promise<typeof XTermTerminal> {
if (!Terminal) {
Terminal = (await import('vscode-xterm')).Terminal;
// Enable xterm.js addons
Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/search/search'));
Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/webLinks/webLinks'));
Terminal.applyAddon(typeAheadAddon);
// Localize strings
Terminal.strings.blankLine = nls.localize('terminal.integrated.a11yBlankLine', 'Blank line');
Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input');
Terminal.strings.tooMuchOutput = nls.localize('terminal.integrated.a11yTooMuchOutput', 'Too much output to announce, navigate to rows manually to read');
Terminal = (await import('xterm')).Terminal;
}
return Terminal;
}
public async getXtermWebLinksConstructor(): Promise<typeof XTermWebLinksAddon> {
if (!WebLinksAddon) {
WebLinksAddon = (await import('xterm-addon-web-links')).WebLinksAddon;
}
return WebLinksAddon;
}
public async getXtermSearchConstructor(): Promise<typeof XTermSearchAddon> {
if (!SearchAddon) {
SearchAddon = (await import('xterm-addon-search')).SearchAddon;
}
return SearchAddon;
}
public createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper {
return new WindowsShellHelper(shellProcessId, instance, xterm);
}
public createTerminalProcessManager(id: number, configHelper: ITerminalConfigHelper): ITerminalProcessManager {
return this._instantiationService.createInstance(TerminalProcessManager, id, configHelper);
}
public createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess {
return this._instantiationService.createInstance(TerminalProcess, shellLaunchConfig, cwd, cols, rows, env, windowsEnableConpty);
}
public getDefaultShell(p: Platform): string {
return getDefaultShell(p);
private _isWorkspaceShellAllowed(): boolean {
return this._storageService.getBoolean(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, StorageScope.WORKSPACE, false);
}
public getDefaultShellAndArgs(platformOverride: Platform = platform): Promise<{ shell: string, args: string[] | undefined }> {
const isWorkspaceShellAllowed = this._isWorkspaceShellAllowed();
const shell = getDefaultShell(
(key) => this._configurationService.inspect(key),
isWorkspaceShellAllowed,
getSystemShell(platformOverride),
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
process.env.windir,
platformOverride
);
const args = getDefaultShellArgs(
(key) => this._configurationService.inspect(key),
isWorkspaceShellAllowed,
platformOverride
);
return Promise.resolve({ shell, args });
}
public getMainProcessParentEnv(): Promise<IProcessEnvironment> {
return getMainProcessParentEnv();
}
}

View File

@@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcRenderer as ipc } from 'electron';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import { ITerminalNativeService, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { getWindowsBuildNumber, linuxDistro } from 'vs/workbench/contrib/terminal/node/terminal';
import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { execFile } from 'child_process';
import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { registerRemoteContributions } from 'vs/workbench/contrib/terminal/node/terminalRemote';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export class TerminalNativeService implements ITerminalNativeService {
public _serviceBrand: any;
public get linuxDistro(): LinuxDistro { return linuxDistro; }
private readonly _onOpenFileRequest = new Emitter<IOpenFileRequest>();
public get onOpenFileRequest(): Event<IOpenFileRequest> { return this._onOpenFileRequest.event; }
private readonly _onOsResume = new Emitter<void>();
public get onOsResume(): Event<void> { return this._onOsResume.event; }
constructor(
@IFileService private readonly _fileService: IFileService,
@IInstantiationService readonly instantiationService: IInstantiationService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) {
ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => this._onOpenFileRequest.fire(request));
ipc.on('vscode:osResume', () => this._onOsResume.fire());
const connection = remoteAgentService.getConnection();
if (connection && connection.remoteAuthority) {
registerRemoteContributions();
}
}
public whenFileDeleted(path: URI): Promise<void> {
// Complete when wait marker file is deleted
return new Promise<void>(resolve => {
let running = false;
const interval = setInterval(() => {
if (!running) {
running = true;
this._fileService.exists(path).then(exists => {
running = false;
if (!exists) {
clearInterval(interval);
resolve(undefined);
}
});
}
}, 1000);
});
}
/**
* Converts a path to a path on WSL using the wslpath utility.
* @param path The original path.
*/
public getWslPath(path: string): Promise<string> {
if (getWindowsBuildNumber() < 17063) {
throw new Error('wslpath does not exist on Windows build < 17063');
}
return new Promise<string>(c => {
execFile('bash.exe', ['-c', 'echo $(wslpath ' + escapeNonWindowsPath(path) + ')'], {}, (error, stdout, stderr) => {
c(escapeNonWindowsPath(stdout.trim()));
});
});
}
public getWindowsBuildNumber(): number {
return getWindowsBuildNumber();
}
}

View File

@@ -1,203 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ITerminalService, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalService as BrowserTerminalService } from 'vs/workbench/contrib/terminal/browser/terminalService';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { getDefaultShell, linuxDistro, getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ipcRenderer as ipc } from 'electron';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput';
import { coalesce } from 'vs/base/common/arrays';
import { IFileService } from 'vs/platform/files/common/files';
import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { execFile } from 'child_process';
import { URI } from 'vs/base/common/uri';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export class TerminalService extends BrowserTerminalService implements ITerminalService {
public get configHelper(): ITerminalConfigHelper { return this._configHelper; }
constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@IPanelService panelService: IPanelService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IStorageService storageService: IStorageService,
@ILifecycleService lifecycleService: ILifecycleService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IInstantiationService instantiationService: IInstantiationService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@INotificationService notificationService: INotificationService,
@IDialogService dialogService: IDialogService,
@IExtensionService extensionService: IExtensionService,
@IFileService fileService: IFileService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) {
super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, extensionService, fileService, remoteAgentService);
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, linuxDistro);
ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => {
// if the request to open files is coming in from the integrated terminal (identified though
// the termProgram variable) and we are instructed to wait for editors close, wait for the
// marker file to get deleted and then focus back to the integrated terminal.
if (request.termProgram === 'vscode' && request.filesToWait) {
const waitMarkerFileUri = URI.revive(request.filesToWait.waitMarkerFileUri);
this.whenDeleted(waitMarkerFileUri).then(() => {
if (this.terminalInstances.length > 0) {
const terminal = this.getActiveInstance();
if (terminal) {
terminal.focus();
}
}
});
}
});
ipc.on('vscode:osResume', () => {
const activeTab = this.getActiveTab();
if (!activeTab) {
return;
}
activeTab.terminalInstances.forEach(instance => instance.forceRedraw());
});
}
private whenDeleted(path: URI): Promise<void> {
// Complete when wait marker file is deleted
return new Promise<void>(resolve => {
let running = false;
const interval = setInterval(() => {
if (!running) {
running = true;
this._fileService.exists(path).then(exists => {
running = false;
if (!exists) {
clearInterval(interval);
resolve(undefined);
}
});
}
}, 1000);
});
}
public getDefaultShell(p: platform.Platform): string {
return getDefaultShell(p);
}
public refreshActiveTab(): void {
// Fire active instances changed
this._onActiveTabChanged.fire();
}
public selectDefaultWindowsShell(): Promise<string | undefined> {
return this._detectWindowsShells().then(shells => {
const options: IPickOptions<IQuickPickItem> = {
placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings")
};
return this._quickInputService.pick(shells, options).then(value => {
if (!value) {
return undefined;
}
const shell = value.description;
return this._configurationService.updateValue('terminal.integrated.shell.windows', shell, ConfigurationTarget.USER).then(() => shell);
});
});
}
/**
* Get the executable file path of shell from registry.
* @param shellName The shell name to get the executable file path
* @returns `[]` or `[ 'path' ]`
*/
private async _getShellPathFromRegistry(shellName: string): Promise<string[]> {
const Registry = await import('vscode-windows-registry');
try {
const shellPath = Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${shellName}.exe`, '');
if (shellPath === undefined) {
return [];
}
return [shellPath];
} catch (error) {
return [];
}
}
private async _detectWindowsShells(): Promise<IQuickPickItem[]> {
// Determine the correct System32 path. We want to point to Sysnative
// when the 32-bit version of VS Code is running on a 64-bit machine.
// The reason for this is because PowerShell's important PSReadline
// module doesn't work if this is not the case. See #27915.
const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`;
let useWSLexe = false;
if (getWindowsBuildNumber() >= 16299) {
useWSLexe = true;
}
const expectedLocations = {
'Command Prompt': [`${system32Path}\\cmd.exe`],
PowerShell: [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`],
'PowerShell Core': await this._getShellPathFromRegistry('pwsh'),
'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`],
'Git Bash': [
`${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`,
`${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`,
`${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`,
`${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`,
`${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`,
]
};
const promises: PromiseLike<[string, string]>[] = [];
Object.keys(expectedLocations).forEach(key => promises.push(this._validateShellPaths(key, expectedLocations[key])));
return Promise.all(promises)
.then(coalesce)
.then(results => {
return results.map(result => {
return <IQuickPickItem>{
label: result[0],
description: result[1]
};
});
});
}
protected _getWindowsBuildNumber(): number {
return getWindowsBuildNumber();
}
/**
* Converts a path to a path on WSL using the wslpath utility.
* @param path The original path.
*/
protected _getWslPath(path: string): Promise<string> {
if (getWindowsBuildNumber() < 17063) {
throw new Error('wslpath does not exist on Windows build < 17063');
}
return new Promise<string>(c => {
execFile('bash.exe', ['-c', 'echo $(wslpath ' + escapeNonWindowsPath(path) + ')'], {}, (error, stdout, stderr) => {
c(escapeNonWindowsPath(stdout.trim()));
});
});
}
}

View File

@@ -6,13 +6,20 @@
import * as os from 'os';
import * as platform from 'vs/base/common/platform';
import * as processes from 'vs/base/node/processes';
import { readFile, fileExists } from 'vs/base/node/pfs';
import { LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal';
import { readFile, fileExists, stat } from 'vs/base/node/pfs';
import { LinuxDistro, IShellDefinition } from 'vs/workbench/contrib/terminal/common/terminal';
import { coalesce } from 'vs/base/common/arrays';
import { normalize, basename } from 'vs/base/common/path';
export function getDefaultShell(p: platform.Platform): string {
/**
* Gets the detected default shell for the _system_, not to be confused with VS Code's _default_
* shell that the terminal uses by default.
* @param p The platform to detect the shell of.
*/
export function getSystemShell(p: platform.Platform): string {
if (p === platform.Platform.Windows) {
if (platform.isWindows) {
return getTerminalDefaultShellWindows();
return getSystemShellWindows();
}
// Don't detect Windows shell when not on Windows
return processes.getWindowsShell();
@@ -21,11 +28,11 @@ export function getDefaultShell(p: platform.Platform): string {
if (platform.isLinux && p === platform.Platform.Mac || platform.isMacintosh && p === platform.Platform.Linux) {
return '/bin/bash';
}
return getTerminalDefaultShellUnixLike();
return getSystemShellUnixLike();
}
let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string | null = null;
function getTerminalDefaultShellUnixLike(): string {
function getSystemShellUnixLike(): string {
if (!_TERMINAL_DEFAULT_SHELL_UNIX_LIKE) {
let unixLikeTerminal = 'sh';
if (!platform.isWindows && process.env.SHELL) {
@@ -44,7 +51,7 @@ function getTerminalDefaultShellUnixLike(): string {
}
let _TERMINAL_DEFAULT_SHELL_WINDOWS: string | null = null;
function getTerminalDefaultShellWindows(): string {
function getSystemShellWindows(): string {
if (!_TERMINAL_DEFAULT_SHELL_WINDOWS) {
const isAtLeastWindows10 = platform.isWindows && parseFloat(os.release()) >= 10;
const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
@@ -82,3 +89,85 @@ export function getWindowsBuildNumber(): number {
}
return buildNumber;
}
export function detectAvailableShells(): Promise<IShellDefinition[]> {
return platform.isWindows ? detectAvailableWindowsShells() : detectAvailableUnixShells();
}
async function detectAvailableWindowsShells(): Promise<IShellDefinition[]> {
// Determine the correct System32 path. We want to point to Sysnative
// when the 32-bit version of VS Code is running on a 64-bit machine.
// The reason for this is because PowerShell's important PSReadline
// module doesn't work if this is not the case. See #27915.
const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`;
let useWSLexe = false;
if (getWindowsBuildNumber() >= 16299) {
useWSLexe = true;
}
const expectedLocations: { [key: string]: string[] } = {
'Command Prompt': [`${system32Path}\\cmd.exe`],
PowerShell: [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`],
'PowerShell Core': [await getShellPathFromRegistry('pwsh')],
'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`],
'Git Bash': [
`${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`,
`${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`,
`${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`,
`${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`,
`${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`,
],
Cygwin: [
`${process.env['HOMEDRIVE']}\\cygwin64\\bin\\bash.exe`,
`${process.env['HOMEDRIVE']}\\cygwin\\bin\\bash.exe`
]
};
const promises: PromiseLike<IShellDefinition | undefined>[] = [];
Object.keys(expectedLocations).forEach(key => promises.push(validateShellPaths(key, expectedLocations[key])));
return Promise.all(promises).then(coalesce);
}
async function detectAvailableUnixShells(): Promise<IShellDefinition[]> {
const contents = await readFile('/etc/shells', 'utf8');
const shells = contents.split('\n').filter(e => e.trim().indexOf('#') !== 0 && e.trim().length > 0);
return shells.map(e => {
return {
label: basename(e),
path: e
};
});
}
async function validateShellPaths(label: string, potentialPaths: string[]): Promise<IShellDefinition | undefined> {
if (potentialPaths.length === 0) {
return Promise.resolve(undefined);
}
const current = potentialPaths.shift()!;
if (current! === '') {
return validateShellPaths(label, potentialPaths);
}
try {
const result = await stat(normalize(current));
if (result.isFile || result.isSymbolicLink) {
return {
label,
path: current
};
}
} catch { /* noop */ }
return validateShellPaths(label, potentialPaths);
}
async function getShellPathFromRegistry(shellName: string): Promise<string> {
const Registry = await import('vscode-windows-registry');
try {
const shellPath = Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${shellName}.exe`, '');
return shellPath ? shellPath : '';
} catch (error) {
return '';
}
}

View File

@@ -0,0 +1,132 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { readFile, exists } from 'vs/base/node/pfs';
import * as path from 'vs/base/common/path';
import { isString } from 'vs/base/common/types';
let mainProcessParentEnv: IProcessEnvironment | undefined;
export async function getMainProcessParentEnv(): Promise<IProcessEnvironment> {
if (mainProcessParentEnv) {
return mainProcessParentEnv;
}
// For Linux use /proc/<pid>/status to get the parent of the main process and then fetch its
// env using /proc/<pid>/environ.
if (isLinux) {
const mainProcessId = process.ppid;
const codeProcessName = path.basename(process.argv[0]);
let pid: number = 0;
let ppid: number = mainProcessId;
let name: string = codeProcessName;
do {
pid = ppid;
const status = await readFile(`/proc/${pid}/status`, 'utf8');
const splitByLine = status.split('\n');
splitByLine.forEach(line => {
if (line.indexOf('Name:') === 0) {
name = line.replace(/^Name:\s+/, '');
}
if (line.indexOf('PPid:') === 0) {
ppid = parseInt(line.replace(/^PPid:\s+/, ''));
}
});
} while (name === codeProcessName);
const rawEnv = await readFile(`/proc/${pid}/environ`, 'utf8');
const env: IProcessEnvironment = {};
rawEnv.split('\0').forEach(e => {
const i = e.indexOf('=');
env[e.substr(0, i)] = e.substr(i + 1);
});
mainProcessParentEnv = env;
}
// For macOS we want the "root" environment as shells by default run as login shells. It
// doesn't appear to be possible to get the "root" environment as `ps eww -o command` for
// PID 1 (the parent of the main process when launched from the dock/finder) returns no
// environment, because of this we will fill in the root environment using a whitelist of
// environment variables that we have.
if (isMacintosh) {
mainProcessParentEnv = {};
// This list was generated by diffing launching a terminal with {} and the system
// terminal launched from finder.
const rootEnvVars = [
'SHELL',
'SSH_AUTH_SOCK',
'Apple_PubSub_Socket_Render',
'XPC_FLAGS',
'XPC_SERVICE_NAME',
'HOME',
'LOGNAME',
'TMPDIR'
];
rootEnvVars.forEach(k => {
if (process.env[k]) {
mainProcessParentEnv![k] = process.env[k]!;
}
});
}
// TODO: Windows should return a fresh environment block, might need native code?
if (isWindows) {
mainProcessParentEnv = process.env as IProcessEnvironment;
}
return mainProcessParentEnv!;
}
export async function findExecutable(command: string, cwd?: string, paths?: string[]): Promise<string | undefined> {
// If we have an absolute path then we take it.
if (path.isAbsolute(command)) {
return await exists(command) ? command : undefined;
}
if (cwd === undefined) {
cwd = process.cwd();
}
const dir = path.dirname(command);
if (dir !== '.') {
// We have a directory and the directory is relative (see above). Make the path absolute
// to the current working directory.
const fullPath = path.join(cwd, command);
return await exists(fullPath) ? fullPath : undefined;
}
if (paths === undefined && isString(process.env.PATH)) {
paths = process.env.PATH.split(path.delimiter);
}
// No PATH environment. Make path absolute to the cwd.
if (paths === undefined || paths.length === 0) {
const fullPath = path.join(cwd, command);
return await exists(fullPath) ? fullPath : undefined;
}
// We have a simple file name. We get the path variable from the env
// and try to find the executable on the path.
for (let pathEntry of paths) {
// The path entry is absolute.
let fullPath: string;
if (path.isAbsolute(pathEntry)) {
fullPath = path.join(pathEntry, command);
} else {
fullPath = path.join(cwd, pathEntry, command);
}
if (await exists(fullPath)) {
return fullPath;
}
if (isWindows) {
let withExtension = fullPath + '.com';
if (await exists(withExtension)) {
return withExtension;
}
withExtension = fullPath + '.exe';
if (await exists(withExtension)) {
return withExtension;
}
}
}
const fullPath = path.join(cwd, command);
return await exists(fullPath) ? fullPath : undefined;
}

View File

@@ -11,9 +11,12 @@ import * as fs from 'fs';
import { Event, Emitter } from 'vs/base/common/event';
import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal';
import { IShellLaunchConfig, ITerminalChildProcess, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal';
import { exec } from 'child_process';
import { ILogService } from 'vs/platform/log/common/log';
import { stat } from 'vs/base/node/pfs';
import { findExecutable } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
import { URI } from 'vs/base/common/uri';
export class TerminalProcess implements ITerminalChildProcess, IDisposable {
private _exitCode: number;
@@ -29,8 +32,8 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
public get onProcessData(): Event<string> { return this._onProcessData.event; }
private readonly _onProcessExit = new Emitter<number>();
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
private readonly _onProcessIdReady = new Emitter<number>();
public get onProcessIdReady(): Event<number> { return this._onProcessIdReady.event; }
private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>();
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
private readonly _onProcessTitleChanged = new Emitter<string>();
public get onProcessTitleChanged(): Event<string> { return this._onProcessTitleChanged.event; }
@@ -54,14 +57,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
this._initialCwd = cwd;
// Only use ConPTY when the client is non WoW64 (see #72190) and the Windows build number is at least 18309 (for
// stability/performance reasons)
const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
const useConpty = windowsEnableConpty &&
process.platform === 'win32' &&
!is32ProcessOn64Windows &&
getWindowsBuildNumber() >= 18309;
const useConpty = windowsEnableConpty && process.platform === 'win32' && getWindowsBuildNumber() >= 18309;
const options: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions = {
name: shellName,
cwd,
@@ -72,16 +68,43 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
conptyInheritCursor: true
};
// TODO: Need to verify whether executable is on $PATH, otherwise things like cmd.exe will break
// fs.stat(shellLaunchConfig.executable!, (err) => {
// if (err && err.code === 'ENOENT') {
// this._exitCode = SHELL_PATH_INVALID_EXIT_CODE;
// this._queueProcessExit();
// this._processStartupComplete = Promise.resolve(undefined);
// return;
// }
this.setupPtyProcess(shellLaunchConfig, options);
// });
const cwdVerification = stat(cwd).then(async stat => {
if (!stat.isDirectory()) {
return Promise.reject(SHELL_CWD_INVALID_EXIT_CODE);
}
}, async err => {
if (err && err.code === 'ENOENT') {
// So we can include in the error message the specified CWD
shellLaunchConfig.cwd = cwd;
return Promise.reject(SHELL_CWD_INVALID_EXIT_CODE);
}
});
const exectuableVerification = stat(shellLaunchConfig.executable!).then(async stat => {
if (!stat.isFile() && !stat.isSymbolicLink()) {
return Promise.reject(stat.isDirectory() ? SHELL_PATH_DIRECTORY_EXIT_CODE : SHELL_PATH_INVALID_EXIT_CODE);
}
}, async (err) => {
if (err && err.code === 'ENOENT') {
let cwd = shellLaunchConfig.cwd instanceof URI ? shellLaunchConfig.cwd.path : shellLaunchConfig.cwd!;
const executable = await findExecutable(shellLaunchConfig.executable!, cwd);
if (!executable) {
return Promise.reject(SHELL_PATH_INVALID_EXIT_CODE);
}
}
});
Promise.all([cwdVerification, exectuableVerification]).then(() => {
this.setupPtyProcess(shellLaunchConfig, options);
}).catch((exitCode: number) => {
return this._launchFailed(exitCode);
});
}
private _launchFailed(exitCode: number): void {
this._exitCode = exitCode;
this._queueProcessExit();
this._processStartupComplete = Promise.resolve(undefined);
}
private setupPtyProcess(shellLaunchConfig: IShellLaunchConfig, options: pty.IPtyForkOptions): void {
@@ -90,7 +113,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
const ptyProcess = pty.spawn(shellLaunchConfig.executable!, args, options);
this._ptyProcess = ptyProcess;
this._processStartupComplete = new Promise<void>(c => {
this.onProcessIdReady(() => c());
this.onProcessReady(() => c());
});
ptyProcess.on('data', data => {
this._onProcessData.fire(data);
@@ -118,7 +141,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
this._titleInterval = null;
this._onProcessData.dispose();
this._onProcessExit.dispose();
this._onProcessIdReady.dispose();
this._onProcessReady.dispose();
this._onProcessTitleChanged.dispose();
}
@@ -169,7 +192,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
}
private _sendProcessId(ptyProcess: pty.IPty) {
this._onProcessIdReady.fire(ptyProcess.pid);
this._onProcessReady.fire({ pid: ptyProcess.pid, cwd: this._initialCwd });
}
private _sendProcessTitle(ptyProcess: pty.IPty): void {
@@ -200,6 +223,9 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
if (this._isDisposed) {
return;
}
if (typeof cols !== 'number' || typeof rows !== 'number' || isNaN(cols) || isNaN(rows)) {
return;
}
// Ensure that cols and rows are always >= 1, this prevents a native
// exception in winpty.
if (this._ptyProcess) {

View File

@@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* 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 { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { TERMINAL_ACTION_CATEGORY, ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal';
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
import { Action } from 'vs/base/common/actions';
import { URI } from 'vs/base/common/uri';
import { homedir } from 'os';
export function registerRemoteContributions() {
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewLocalTerminalAction, CreateNewLocalTerminalAction.ID, CreateNewLocalTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (Local)', TERMINAL_ACTION_CATEGORY);
}
export class CreateNewLocalTerminalAction extends Action {
public static readonly ID = TERMINAL_COMMAND_ID.NEW_LOCAL;
public static readonly LABEL = nls.localize('workbench.action.terminal.newLocal', "Create New Integrated Terminal (Local)");
constructor(
id: string, label: string,
@ITerminalService private readonly terminalService: ITerminalService
) {
super(id, label);
}
public run(event?: any): Promise<any> {
const instance = this.terminalService.createTerminal({ cwd: URI.file(homedir()) });
if (!instance) {
return Promise.resolve(undefined);
}
// Append (Local) to the first title that comes back, the title will then become static
const disposable = instance.onTitleChanged(() => {
if (instance.title && instance.title.trim().length > 0) {
disposable.dispose();
instance.setTitle(`${instance.title} (Local)`, false);
}
});
this.terminalService.setActiveInstance(instance);
return this.terminalService.showPanel(true);
}
}

View File

@@ -6,7 +6,7 @@
import * as platform from 'vs/base/common/platform';
import { Emitter, Event } from 'vs/base/common/event';
import { ITerminalInstance, IWindowsShellHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { Terminal as XTermTerminal } from 'vscode-xterm';
import { Terminal as XTermTerminal } from 'xterm';
import WindowsProcessTreeType = require('windows-process-tree');
const SHELL_EXECUTABLES = [

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Terminal, TerminalCore } from 'vscode-xterm';
import { Terminal, TerminalCore } from 'xterm';
import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker';
import { isWindows } from 'vs/base/common/platform';

View File

@@ -4,9 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Platform, OperatingSystem } from 'vs/base/common/platform';
import { OperatingSystem } from 'vs/base/common/platform';
import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler';
import * as strings from 'vs/base/common/strings';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { Event } from 'vs/base/common/event';
class TestTerminalLinkHandler extends TerminalLinkHandler {
public get localLinkRegex(): RegExp {
@@ -24,10 +26,36 @@ class TestTerminalLinkHandler extends TerminalLinkHandler {
}
class TestXterm {
public webLinksInit() { }
public loadAddon() { }
public registerLinkMatcher() { }
}
class MockTerminalInstanceService implements ITerminalInstanceService {
onRequestDefaultShellAndArgs?: Event<any> | undefined;
getDefaultShellAndArgs(): Promise<{ shell: string; args: string | string[] | undefined; }> {
throw new Error('Method not implemented.');
}
_serviceBrand: any;
getXtermConstructor(): Promise<any> {
throw new Error('Method not implemented.');
}
async getXtermWebLinksConstructor(): Promise<any> {
return (await import('xterm-addon-web-links')).WebLinksAddon;
}
getXtermSearchConstructor(): Promise<any> {
throw new Error('Method not implemented.');
}
createWindowsShellHelper(): any {
throw new Error('Method not implemented.');
}
createTerminalProcess(): any {
throw new Error('Method not implemented.');
}
getMainProcessParentEnv(): any {
throw new Error('Method not implemented.');
}
}
interface LinkFormatInfo {
urlFormat: string;
line?: string;
@@ -37,10 +65,10 @@ interface LinkFormatInfo {
suite('Workbench - TerminalLinkHandler', () => {
suite('localLinkRegex', () => {
test('Windows', () => {
const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, {
const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), {
os: OperatingSystem.Windows,
userHome: ''
} as any, null!, null!, null!, null!, null!);
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) {
assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl);
assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl);
@@ -112,10 +140,10 @@ suite('Workbench - TerminalLinkHandler', () => {
});
test('Linux', () => {
const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, {
const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), {
os: OperatingSystem.Linux,
userHome: ''
} as any, null!, null!, null!, null!, null!);
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) {
assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl);
assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl);
@@ -179,10 +207,10 @@ suite('Workbench - TerminalLinkHandler', () => {
suite('preprocessPath', () => {
test('Windows', () => {
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, {
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), {
os: OperatingSystem.Windows,
userHome: 'C:\\Users\\Me'
} as any, null!, null!, null!, null!, null!);
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
linkHandler.processCwd = 'C:\\base';
assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base\\src\\file1');
@@ -192,10 +220,10 @@ suite('Workbench - TerminalLinkHandler', () => {
assert.equal(linkHandler.preprocessPath('C:\\absolute\\path\\file5'), 'C:\\absolute\\path\\file5');
});
test('Windows - spaces', () => {
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, {
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), {
os: OperatingSystem.Windows,
userHome: 'C:\\Users\\M e'
} as any, null!, null!, null!, null!, null!);
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
linkHandler.processCwd = 'C:\\base dir';
assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base dir\\src\\file1');
@@ -206,10 +234,10 @@ suite('Workbench - TerminalLinkHandler', () => {
});
test('Linux', () => {
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, {
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), {
os: OperatingSystem.Linux,
userHome: '/home/me'
} as any, null!, null!, null!, null!, null!);
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
linkHandler.processCwd = '/base';
assert.equal(linkHandler.preprocessPath('./src/file1'), '/base/src/file1');
@@ -219,10 +247,10 @@ suite('Workbench - TerminalLinkHandler', () => {
});
test('No Workspace', () => {
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, {
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), {
os: OperatingSystem.Linux,
userHome: '/home/me'
} as any, null!, null!, null!, null!, null!);
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
assert.equal(linkHandler.preprocessPath('./src/file1'), null);
assert.equal(linkHandler.preprocessPath('src/file2'), null);
@@ -233,10 +261,10 @@ suite('Workbench - TerminalLinkHandler', () => {
test('gitDiffLinkRegex', () => {
// The platform is irrelevant because the links generated by Git are the same format regardless of platform
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, {
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), {
os: OperatingSystem.Linux,
userHome: ''
} as any, null!, null!, null!, null!, null!);
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
function assertAreGoodMatches(matches: RegExpMatchArray | null) {
if (matches) {

View File

@@ -11,14 +11,14 @@ import { IStringDictionary } from 'vs/base/common/collections';
suite('Workbench - TerminalEnvironment', () => {
test('addTerminalEnvironmentKeys', () => {
const env = { FOO: 'bar' };
const env: { [key: string]: any } = { FOO: 'bar' };
const locale = 'en-au';
terminalEnvironment.addTerminalEnvironmentKeys(env, '1.2.3', locale, true);
assert.equal(env['TERM_PROGRAM'], 'vscode');
assert.equal(env['TERM_PROGRAM_VERSION'], '1.2.3');
assert.equal(env['LANG'], 'en_AU.UTF-8', 'LANG is equal to the requested locale with UTF-8');
const env2 = { FOO: 'bar' };
const env2: { [key: string]: any } = { FOO: 'bar' };
terminalEnvironment.addTerminalEnvironmentKeys(env2, '1.2.3', undefined, true);
assert.equal(env2['LANG'], 'en_US.UTF-8', 'LANG is equal to en_US.UTF-8 as fallback.'); // More info on issue #14586