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

Pull individual terminal logic out of terminalPanel

The character measure logic has been shuffled around a little and now is done
primarily in setVisible after the terminal is confirmed to be within the DOM.

Part of #6458
上级 a8abd900
......@@ -39,15 +39,15 @@ configurationRegistry.registerConfiguration({
'default': TERMINAL_DEFAULT_SHELL_WINDOWS
},
'terminal.integrated.fontFamily': {
'description': nls.localize('terminal.integrated.fontFamily', "The font family used by the terminal (CSS font-family format), this defaults to editor.fontFamily's value."),
'description': nls.localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to editor.fontFamily's value."),
'type': 'string'
},
'terminal.integrated.fontSize': {
'description': nls.localize('terminal.integrated.fontSize', "The font size used by the terminal (in pixels), this defaults to editor.fontSize's value."),
'description': nls.localize('terminal.integrated.fontSize', "Controls the font size of the terminal, this defaults to editor.fontSize's value."),
'type': 'number'
},
'terminal.integrated.lineHeight': {
'description': nls.localize('terminal.integrated.lineHeight', "The line height used by the terminal (in pixels), this defaults to editor.lineHeight's value."),
'description': nls.localize('terminal.integrated.lineHeight', "Controls the line height of the terminal, this defaults to editor.lineHeight's value."),
'type': 'number'
}
}
......
......@@ -8,7 +8,6 @@ import {Platform} from 'vs/base/common/platform';
import {IConfiguration} from 'vs/editor/common/config/defaultConfig';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {ITerminalConfiguration} from 'vs/workbench/parts/terminal/electron-browser/terminal';
import {IThemeService} from 'vs/workbench/services/themes/common/themeService';
import {GOLDEN_LINE_HEIGHT_RATIO} from 'vs/editor/common/config/defaultConfig';
import {Builder} from 'vs/base/browser/builder';
......@@ -83,21 +82,22 @@ export interface ITerminalFont {
*/
export class TerminalConfigHelper {
private charMeasureElement: HTMLElement;
private font: ITerminalFont;
public constructor(
private platform: Platform,
private configurationService: IConfigurationService,
private themeService: IThemeService,
private parentDomElement: HTMLElement) {
}
public getTheme(): string[] {
let baseThemeId = getBaseThemeId(this.themeService.getTheme());
public getTheme(themeId: string): string[] {
let baseThemeId = getBaseThemeId(themeId);
return DEFAULT_ANSI_COLORS[baseThemeId];
}
private neasureFont(fontFamily: string, fontSize: number, lineHeight: number): ITerminalFont {
if (!this.charMeasureElement) {
private measureFont(fontFamily: string, fontSize: number, lineHeight: number): ITerminalFont {
// Create charMeasureElement if it hasn't been created or if it was orphaned by its parent
if (!this.charMeasureElement || !this.charMeasureElement.parentElement) {
this.charMeasureElement = new Builder(this.parentDomElement, true).div().build().getHTMLElement();
}
let style = this.charMeasureElement.style;
......@@ -110,13 +110,14 @@ export class TerminalConfigHelper {
style.display = 'none';
let charWidth = Math.ceil(rect.width);
let charHeight = Math.ceil(rect.height);
return {
this.font = {
fontFamily,
fontSize,
lineHeight,
charWidth,
charHeight
};
return this.font;
}
/**
......@@ -135,7 +136,7 @@ export class TerminalConfigHelper {
lineHeight = Math.round(GOLDEN_LINE_HEIGHT_RATIO * fontSize);
}
return this.neasureFont(fontFamily, fontSize, lineHeight);
return this.measureFont(fontFamily, fontSize, lineHeight);
}
public getShell(): string {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import cp = require('child_process');
import termJs = require('term.js');
import lifecycle = require('vs/base/common/lifecycle');
import os = require('os');
import path = require('path');
import URI from 'vs/base/common/uri';
import DOM = require('vs/base/browser/dom');
import platform = require('vs/base/common/platform');
import {Dimension} from 'vs/base/browser/builder';
import {IStringDictionary} from 'vs/base/common/collections';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {ITerminalService} from 'vs/workbench/parts/terminal/electron-browser/terminal';
import {DomScrollableElement} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {ScrollbarVisibility} from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
import {ITerminalFont} from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
export class TerminalInstance {
private toDispose: lifecycle.IDisposable[];
private ptyProcess: cp.ChildProcess;
private terminal;
private terminalDomElement: HTMLDivElement;
private font: ITerminalFont;
public constructor(
private shell: string,
private parentDomElement: HTMLElement,
private contextService: IWorkspaceContextService,
private terminalService: ITerminalService,
private onExitCallback: (TerminalInstance) => void
) {
this.toDispose = [];
this.parentDomElement.innerHTML = '';
this.ptyProcess = this.createTerminalProcess();
this.terminalDomElement = document.createElement('div');
this.parentDomElement.classList.add('integrated-terminal');
let terminalScrollbar = new DomScrollableElement(this.terminalDomElement, {
canUseTranslate3d: false,
horizontal: ScrollbarVisibility.Hidden,
vertical: ScrollbarVisibility.Auto
});
this.toDispose.push(terminalScrollbar);
this.terminal = termJs({
cursorBlink: false // term.js' blinking cursor breaks selection
});
this.ptyProcess.on('message', (data) => {
this.terminal.write(data);
});
this.terminal.on('data', (data) => {
this.ptyProcess.send({
event: 'input',
data: data
});
return false;
});
this.ptyProcess.on('exit', (exitCode) => {
this.dispose();
// TODO: When multiple terminals are supported this should do something smarter. There is
// also a weird bug here at least on Ubuntu 15.10 where the new terminal text does not
// repaint correctly.
if (exitCode !== 0) {
// TODO: Allow the terminal to be relaunched after an error
console.error('Integrated terminal exited with code ' + exitCode);
}
this.onExitCallback(this);
});
this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mousedown', (event) => {
// Drop selection and focus terminal on Linux to enable middle button paste when click
// occurs on the selection itself.
if (event.which === 2 && platform.isLinux) {
this.focus(true);
}
}));
this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mouseup', (event) => {
if (event.which !== 3) {
this.focus();
}
}));
this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'keyup', (event: KeyboardEvent) => {
// Keep terminal open on escape
if (event.keyCode === 27) {
event.stopPropagation();
}
}));
this.terminal.open(this.terminalDomElement);
this.parentDomElement.appendChild(terminalScrollbar.getDomNode());
}
public layout(dimension: Dimension): void {
if (!this.font || !this.font.charWidth || !this.font.charHeight) {
return;
}
let cols = Math.floor(this.parentDomElement.offsetWidth / this.font.charWidth);
let rows = Math.floor(this.parentDomElement.offsetHeight / this.font.charHeight);
if (this.terminal) {
this.terminal.resize(cols, rows);
}
if (this.ptyProcess.connected) {
this.ptyProcess.send({
event: 'resize',
cols: cols,
rows: rows
});
}
}
private cloneEnv(): IStringDictionary<string> {
let newEnv: IStringDictionary<string> = Object.create(null);
Object.keys(process.env).forEach((key) => {
newEnv[key] = process.env[key];
});
return newEnv;
}
private createTerminalProcess(): cp.ChildProcess {
let env = this.cloneEnv();
env['PTYSHELL'] = this.shell;
env['PTYCWD'] = this.contextService.getWorkspace() ? this.contextService.getWorkspace().resource.fsPath : os.homedir();
return cp.fork('./terminalProcess', [], {
env: env,
cwd: URI.parse(path.dirname(require.toUrl('./terminalProcess'))).fsPath
});
}
public setTheme(colors: string[]): void {
if (!this.terminal) {
return;
}
this.terminal.colors = colors;
this.terminal.refresh(0, this.terminal.rows);
}
public setFont(font: ITerminalFont): void {
this.font = font;
this.terminalDomElement.style.fontFamily = this.font.fontFamily;
this.terminalDomElement.style.lineHeight = this.font.lineHeight + 'px';
this.terminalDomElement.style.fontSize = this.font.fontSize + 'px';
}
public focus(force?: boolean): void {
if (!this.terminal) {
return;
}
let text = window.getSelection().toString();
if (!text || force) {
this.terminal.focus();
if (this.terminal._textarea) {
this.terminal._textarea.focus();
}
}
}
public dispose(): void {
this.toDispose = lifecycle.dispose(this.toDispose);
this.terminal.destroy();
this.ptyProcess.kill();
}
}
\ No newline at end of file
......@@ -3,36 +3,25 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import cp = require('child_process');
import termJs = require('term.js');
import lifecycle = require('vs/base/common/lifecycle');
import os = require('os');
import path = require('path');
import URI from 'vs/base/common/uri';
import DOM = require('vs/base/browser/dom');
import platform = require('vs/base/common/platform');
import {TPromise} from 'vs/base/common/winjs.base';
import {Builder, Dimension} from 'vs/base/browser/builder';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {IStringDictionary} from 'vs/base/common/collections';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IThemeService} from 'vs/workbench/services/themes/common/themeService';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {ITerminalService, TERMINAL_PANEL_ID} from 'vs/workbench/parts/terminal/electron-browser/terminal';
import {Panel} from 'vs/workbench/browser/panel';
import {DomScrollableElement} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {ScrollbarVisibility} from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
import {TerminalConfigHelper, ITerminalFont} from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
import {TerminalConfigHelper} from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
import {TerminalInstance} from 'vs/workbench/parts/terminal/electron-browser/terminalInstance';
export class TerminalPanel extends Panel {
private toDispose: lifecycle.IDisposable[];
private ptyProcess: cp.ChildProcess;
private parentDomElement: HTMLElement;
private terminal;
private terminalDomElement: HTMLDivElement;
private configurationHelper: TerminalConfigHelper;
private font: ITerminalFont;
private terminalInstance: TerminalInstance;
constructor(
@IConfigurationService private configurationService: IConfigurationService,
......@@ -45,168 +34,84 @@ export class TerminalPanel extends Panel {
this.toDispose = [];
}
public layout(dimension: Dimension): void {
if (this.font.charWidth && this.font.charHeight) {
let cols = Math.floor(this.parentDomElement.offsetWidth / this.font.charWidth);
let rows = Math.floor(this.parentDomElement.offsetHeight / this.font.charHeight);
if (this.terminal) {
this.terminal.resize(cols, rows);
}
if (this.ptyProcess.connected) {
this.ptyProcess.send({
event: 'resize',
cols: cols,
rows: rows
});
}
public layout(dimension?: Dimension): void {
if (this.terminalInstance) {
this.terminalInstance.layout(dimension);
}
}
public create(parent: Builder): TPromise<void> {
super.create(parent);
this.parentDomElement = parent.getHTMLElement();
this.configurationHelper = new TerminalConfigHelper(platform.platform, this.configurationService, this.parentDomElement);
return this.createTerminal();
}
public setVisible(visible: boolean): TPromise<void> {
if (visible && this.terminal === null) {
return super.setVisible(visible).then(() => {
return this.createTerminal();
});
if (visible) {
if (this.terminalInstance) {
this.updateFont();
this.updateTheme();
} else {
return super.setVisible(visible).then(() => {
this.createTerminal();
this.updateFont();
this.updateTheme();
return Promise.resolve(void 0);
});
}
}
return super.setVisible(visible);
}
private cloneEnv(): IStringDictionary<string> {
let newEnv: IStringDictionary<string> = Object.create(null);
Object.keys(process.env).forEach((key) => {
newEnv[key] = process.env[key];
});
return newEnv;
}
private createTerminalProcess(): cp.ChildProcess {
let env = this.cloneEnv();
env['PTYSHELL'] = this.configurationHelper.getShell();
env['PTYCWD'] = this.contextService.getWorkspace() ? this.contextService.getWorkspace().resource.fsPath : os.homedir();
return cp.fork('./terminalProcess', [], {
env: env,
cwd: URI.parse(path.dirname(require.toUrl('./terminalProcess'))).fsPath
});
}
private createTerminal(): TPromise<void> {
return new TPromise<void>(resolve => {
this.configurationHelper = new TerminalConfigHelper(platform.platform, this.configurationService, this.themeService, this.parentDomElement);
this.parentDomElement.innerHTML = '';
this.ptyProcess = this.createTerminalProcess();
this.terminalDomElement = document.createElement('div');
this.parentDomElement.classList.add('integrated-terminal');
let terminalScrollbar = new DomScrollableElement(this.terminalDomElement, {
canUseTranslate3d: false,
horizontal: ScrollbarVisibility.Hidden,
vertical: ScrollbarVisibility.Auto
});
this.toDispose.push(terminalScrollbar);
this.terminal = termJs({
cursorBlink: false // term.js' blinking cursor breaks selection
});
this.ptyProcess.on('message', (data) => {
this.terminal.write(data);
});
this.terminal.on('data', (data) => {
this.ptyProcess.send({
event: 'input',
data: data
});
return false;
});
this.ptyProcess.on('exit', (exitCode) => {
this.toDispose = lifecycle.dispose(this.toDispose);
this.terminal.destroy();
this.terminal = null;
// TODO: When multiple terminals are supported this should do something smarter. There is
// also a weird bug here at least on Ubuntu 15.10 where the new terminal text does not
// repaint correctly.
if (exitCode !== 0) {
// TODO: Allow the terminal to be relaunched after an error
console.error('Integrated terminal exited with code ' + exitCode);
}
this.terminalService.toggle();
});
this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mousedown', (event) => {
// Drop selection and focus terminal on Linux to enable middle button paste when click
// occurs on the selection itself.
if (event.which === 2 && platform.isLinux) {
this.focusTerminal(true);
}
}));
this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mouseup', (event) => {
if (event.which !== 3) {
this.focusTerminal();
}
}));
this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'keyup', (event: KeyboardEvent) => {
// Keep terminal open on escape
if (event.keyCode === 27) {
event.stopPropagation();
}
}));
this.toDispose.push(this.themeService.onDidThemeChange((themeId) => {
this.setTerminalTheme();
}));
this.toDispose.push(this.configurationService.onDidUpdateConfiguration((e) => {
this.updateFont();
}));
this.terminal.open(this.terminalDomElement);
this.parentDomElement.appendChild(terminalScrollbar.getDomNode());
this.updateFont();
this.setTerminalTheme();
this.terminalInstance = new TerminalInstance(this.configurationHelper.getShell(), this.parentDomElement, this.contextService, this.terminalService, this.onTerminalInstanceExit.bind(this));
this.toDispose.push(this.themeService.onDidThemeChange(this.updateTheme.bind(this)));
this.toDispose.push(this.configurationService.onDidUpdateConfiguration(this.updateFont.bind(this)));
resolve(void 0);
});
}
private setTerminalTheme(): void {
if (!this.terminal) {
private onTerminalInstanceExit(terminalInstance: TerminalInstance): void {
if (this.terminalInstance === terminalInstance) {
this.terminalInstance = null;
}
this.terminalService.toggle();
}
private updateTheme(themeId?: string): void {
if (!this.terminalInstance) {
return;
}
this.terminal.colors = this.configurationHelper.getTheme();
this.terminal.refresh(0, this.terminal.rows);
if (!themeId) {
themeId = this.themeService.getTheme();
}
this.terminalInstance.setTheme(this.configurationHelper.getTheme(themeId));
}
private updateFont(): void {
this.font = this.configurationHelper.getFont();
this.terminalDomElement.style.fontFamily = this.font.fontFamily;
this.terminalDomElement.style.lineHeight = this.font.lineHeight + 'px';
this.terminalDomElement.style.fontSize = this.font.fontSize + 'px';
if (!this.terminalInstance) {
return;
}
this.terminalInstance.setFont(this.configurationHelper.getFont());
this.layout(new Dimension(this.parentDomElement.offsetWidth, this.parentDomElement.offsetHeight));
}
public focus(): void {
this.focusTerminal(true);
}
private focusTerminal(force?: boolean): void {
if (!this.terminal) {
return;
}
let text = window.getSelection().toString();
if (!text || force) {
this.terminal.focus();
if (this.terminal._textarea) {
this.terminal._textarea.focus();
}
if (this.terminalInstance) {
this.terminalInstance.focus(true);
}
}
public dispose(): void {
this.toDispose = lifecycle.dispose(this.toDispose);
this.terminal.destroy();
this.ptyProcess.kill();
if (this.terminalInstance) {
this.terminalInstance.dispose();
this.terminalInstance = null;
}
super.dispose();
}
}
......@@ -26,15 +26,6 @@ class MockConfigurationService implements IConfigurationService {
public setUserConfiguration(key: any, value: any): Thenable<void> { return TPromise.as(null); }
}
class MockThemeService implements IThemeService {
public constructor(private activeTheme: string = undefined) {}
public serviceId: ServiceIdentifier<any>;
public setTheme(themeId: string, broadcastToAllWindows: boolean): TPromise<boolean> { return null; }
public getTheme(): string { return this.activeTheme; }
public getThemes(): TPromise<IThemeData[]> { return null; }
public onDidThemeChange: Event<string>;
}
suite('Workbench - TerminalConfigHelper', () => {
let fixture: HTMLElement;
let fixtureId = 'terminal-config-helper-fixture';
......@@ -49,7 +40,6 @@ suite('Workbench - TerminalConfigHelper', () => {
});
test('TerminalConfigHelper - getFont', function () {
let themeService: IThemeService = new MockThemeService();
let configurationService: IConfigurationService;
let configHelper: TerminalConfigHelper;
......@@ -63,7 +53,7 @@ suite('Workbench - TerminalConfigHelper', () => {
}
}
});
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, themeService, fixture);
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture);
assert.equal(configHelper.getFont().fontFamily, 'bar', 'terminal.integrated.fontFamily should be selected over editor.fontFamily');
configurationService = new MockConfigurationService({
......@@ -76,7 +66,7 @@ suite('Workbench - TerminalConfigHelper', () => {
}
}
});
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, themeService, fixture);
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture);
assert.equal(configHelper.getFont().fontFamily, 'foo', 'editor.fontFamily should be the fallback when terminal.integrated.fontFamily not set');
configurationService = new MockConfigurationService({
......@@ -91,7 +81,7 @@ suite('Workbench - TerminalConfigHelper', () => {
}
}
});
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, themeService, fixture);
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture);
assert.equal(configHelper.getFont().fontSize, 2, 'terminal.integrated.fontSize should be selected over editor.fontSize');
configurationService = new MockConfigurationService({
......@@ -106,7 +96,7 @@ suite('Workbench - TerminalConfigHelper', () => {
}
}
});
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, themeService, fixture);
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture);
assert.equal(configHelper.getFont().fontSize, 1, 'editor.fontSize should be the fallback when terminal.integrated.fontSize not set');
configurationService = new MockConfigurationService({
......@@ -121,7 +111,7 @@ suite('Workbench - TerminalConfigHelper', () => {
}
}
});
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, themeService, fixture);
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture);
assert.equal(configHelper.getFont().lineHeight, 2, 'terminal.integrated.lineHeight should be selected over editor.lineHeight');
configurationService = new MockConfigurationService({
......@@ -136,12 +126,11 @@ suite('Workbench - TerminalConfigHelper', () => {
}
}
});
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, themeService, fixture);
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture);
assert.equal(configHelper.getFont().lineHeight, 1, 'editor.lineHeight should be the fallback when terminal.integrated.lineHeight not set');
});
test('TerminalConfigHelper - getShell', function () {
let themeService: IThemeService = new MockThemeService();
let configurationService: IConfigurationService;
let configHelper: TerminalConfigHelper;
......@@ -154,7 +143,7 @@ suite('Workbench - TerminalConfigHelper', () => {
}
}
});
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, themeService, fixture);
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture);
assert.equal(configHelper.getShell(), 'foo', 'terminal.integrated.shell.linux should be selected on Linux');
configurationService = new MockConfigurationService({
......@@ -166,7 +155,7 @@ suite('Workbench - TerminalConfigHelper', () => {
}
}
});
configHelper = new TerminalConfigHelper(Platform.Mac, configurationService, themeService, fixture);
configHelper = new TerminalConfigHelper(Platform.Mac, configurationService, fixture);
assert.equal(configHelper.getShell(), 'foo', 'terminal.integrated.shell.osx should be selected on OS X');
configurationService = new MockConfigurationService({
......@@ -178,18 +167,16 @@ suite('Workbench - TerminalConfigHelper', () => {
}
}
});
configHelper = new TerminalConfigHelper(Platform.Windows, configurationService, themeService, fixture);
configHelper = new TerminalConfigHelper(Platform.Windows, configurationService, fixture);
assert.equal(configHelper.getShell(), 'foo', 'terminal.integrated.shell.windows should be selected on Windows');
});
test('TerminalConfigHelper - getTheme', function () {
let configurationService: IConfigurationService = new MockConfigurationService();
let themeService: IThemeService;
let configHelper: TerminalConfigHelper;
themeService = new MockThemeService('hc-black foo');
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, themeService, fixture);
assert.deepEqual(configHelper.getTheme(), [
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture);
assert.deepEqual(configHelper.getTheme('hc-black foo'), [
'#000000',
'#cd0000',
'#00cd00',
......@@ -208,9 +195,8 @@ suite('Workbench - TerminalConfigHelper', () => {
'#ffffff'
], 'The high contrast terminal theme should be selected when the hc-black theme is active');
themeService = new MockThemeService('vs foo');
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, themeService, fixture);
assert.deepEqual(configHelper.getTheme(), [
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture);
assert.deepEqual(configHelper.getTheme('vs foo'), [
'#000000',
'#cd3131',
'#008000',
......@@ -229,9 +215,8 @@ suite('Workbench - TerminalConfigHelper', () => {
'#a5a5a5'
], 'The light terminal theme should be selected when a vs theme is active');
themeService = new MockThemeService('vs-dark foo');
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, themeService, fixture);
assert.deepEqual(configHelper.getTheme(), [
configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture);
assert.deepEqual(configHelper.getTheme('vs-dark foo'), [
'#000000',
'#cd3131',
'#09885a',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册