Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

terminal tabs UI #120591

Merged
merged 52 commits into from
Apr 8, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
3edb33f
push terminalTabsWidget
meganrogge Apr 5, 2021
027f629
add to terminal view
meganrogge Apr 5, 2021
76acf1e
working pretty well
meganrogge Apr 5, 2021
218e702
children now work
meganrogge Apr 5, 2021
76736a8
add showTabs setting
meganrogge Apr 5, 2021
59c22b3
more improvements
meganrogge Apr 5, 2021
92ff8a9
rearrange code
meganrogge Apr 5, 2021
0f69646
get instances to focus on click
meganrogge Apr 5, 2021
617b7c0
take a break
meganrogge Apr 5, 2021
e757e4e
still not working
meganrogge Apr 5, 2021
440a441
Merge branch 'main' into merogge/tabs2
meganrogge Apr 5, 2021
224127c
add splitTabsPane
meganrogge Apr 6, 2021
be2e72a
clean up
meganrogge Apr 6, 2021
d52fbad
more cleanup
meganrogge Apr 6, 2021
99d9bf2
use this.
meganrogge Apr 6, 2021
76ec942
a little closer
meganrogge Apr 6, 2021
383a22e
try more stuff
meganrogge Apr 6, 2021
56b96bc
The Great Migration of code from terminalView to tabsView
meganrogge Apr 6, 2021
ab4f455
more migration
meganrogge Apr 6, 2021
6b1d3bf
delete comment
meganrogge Apr 6, 2021
070e708
revert some changes
meganrogge Apr 6, 2021
1b70c81
fix error, sign off for now
meganrogge Apr 6, 2021
3546e73
take 2, sash still disabled but otherwise working
meganrogge Apr 6, 2021
0e15648
clean up a bit
meganrogge Apr 6, 2021
6beb1b4
more splitView work
meganrogge Apr 7, 2021
08508fd
call TerminalTabbedView.layout
joaomoreno Apr 7, 2021
55b1c5b
sash working!
meganrogge Apr 7, 2021
f8771f4
add Tabs Location
meganrogge Apr 7, 2021
c2269e6
get terminal container to show up
meganrogge Apr 7, 2021
aecaf0a
get widget to work
meganrogge Apr 7, 2021
c2075cd
revert some changes
meganrogge Apr 7, 2021
b116dce
Merge branch 'main' into merogge/tabs2
meganrogge Apr 7, 2021
ca0a583
🧹
meganrogge Apr 7, 2021
b4fe863
add some more checks
meganrogge Apr 7, 2021
e6bf0c5
feedback
meganrogge Apr 7, 2021
6846fee
Prevent disposable store exception
Tyriar Apr 7, 2021
5bcbaa2
Rerender tabs on title change event
Tyriar Apr 7, 2021
c492147
Simplify instance node
Tyriar Apr 7, 2021
3241e1a
Improve tabbed view size ranges
Tyriar Apr 7, 2021
d228dbc
use TerminalTab
meganrogge Apr 8, 2021
e6c32c3
create issue to track indent guides todo
meganrogge Apr 8, 2021
2c77e72
add or remove view depending on showTabs
meganrogge Apr 8, 2021
0b89eae
add a bunch of stuff from terminalView
meganrogge Apr 8, 2021
494536b
find widget
meganrogge Apr 8, 2021
77d5d2c
revert some changes
meganrogge Apr 8, 2021
2a9e136
revert more changes to settings
meganrogge Apr 8, 2021
966eea3
🧹
meganrogge Apr 8, 2021
71d57e0
copy directly from master
meganrogge Apr 8, 2021
1a14142
remove empty line
meganrogge Apr 8, 2021
29d772f
Merge remote-tracking branch 'origin/main' into merogge/tabs2
Tyriar Apr 8, 2021
37a44ac
Types, polish
Tyriar Apr 8, 2021
7a1e1fd
Fix ctx menu, clean up DI
Tyriar Apr 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/vs/base/browser/ui/list/listView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.rowsContainer.appendChild(item.row.domNode);
}
}

this.updateItemInDOM(item, index);

const renderer = this.renderers.get(item.templateId);
Expand Down
10 changes: 10 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/media/terminal.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/


.tabs-widget-container {
width: 20%;
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
float: left;
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
height: 100%;
}

.monaco-workbench .pane-body.integrated-terminal {
align-content: flex-start;
align-items: baseline;
Expand All @@ -19,10 +26,13 @@
width: 100%;
box-sizing: border-box;
overflow: hidden;
float:right;
}

.monaco-workbench .pane-body.integrated-terminal .terminal-tab {
height: 100%;
width: 80%;
float: right;
}

.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper {
Expand Down
34 changes: 34 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/tabsView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalTabsWidget } from 'vs/workbench/contrib/terminal/browser/terminalTabsWidget';


export class TabsView {
private _splitView: SplitView | undefined;
private _widget: WorkbenchObjectTree<any> | undefined;

constructor(
context: string,
container: HTMLElement,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ITerminalService private readonly _terminalService: ITerminalService,

) {
if (context === 'terminal') {
this._splitView = new SplitView(container, { orientation: Orientation.HORIZONTAL });
this._widget = _instantiationService.createInstance(TerminalTabsWidget, this._splitView.el);
}
}

public layout(width: number, height: number): void {
this._splitView?.layout(100);
this._terminalService.terminalTabs.forEach(t => t.layout(width - 100, height));
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
}
}
10 changes: 10 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@ export class TerminalService implements ITerminalService {
e.affectsConfiguration('terminal.integrated.profiles.linux') ||
e.affectsConfiguration('terminal.integrated.useWslProfiles')) {
this._updateAvailableProfilesNow();
} else if (e.affectsConfiguration('terminal.integrated.showTabs')) {
const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane;
pane?.renderBody();
}
});

this.onInstancesChanged(() => {
if (this._configHelper.config.showTabs) {
const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane;
pane?.renderBody();
}
});

Expand Down
6 changes: 5 additions & 1 deletion src/vs/workbench/contrib/terminal/browser/terminalTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { localize } from 'vs/nls';

const SPLIT_PANE_MIN_SIZE = 120;

class SplitPaneContainer extends Disposable {
export class SplitPaneContainer extends Disposable {
private _height: number;
private _width: number;
private _splitView!: SplitView;
Expand All @@ -38,6 +38,10 @@ class SplitPaneContainer extends Disposable {
this._splitView.layout(this.orientation === Orientation.HORIZONTAL ? this._width : this._height);
}

public get splitView(): SplitView {
return this._splitView;
}

private _createSplitView(): void {
this._splitView = new SplitView(this._container, { orientation: this.orientation });
this._splitViewDisposables.clear();
Expand Down
196 changes: 196 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminalTabsWidget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
import { IListService, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { DefaultStyleController, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ITerminalInstance, ITerminalService, ITerminalTab } from 'vs/workbench/contrib/terminal/browser/terminal';
import { localize } from 'vs/nls';
import * as DOM from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';

const $ = DOM.$;

export class TerminalTabsWidget extends WorkbenchObjectTree<TabTreeNode> {
constructor(
container: HTMLElement,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@ITerminalService terminalService: ITerminalService,
@IInstantiationService _instantiationService: IInstantiationService
) {
super('TerminalTabsTree', container,
new TerminalTabsDelegate(),
[new TerminalTabsRenderer()],
{
horizontalScrolling: false,
supportDynamicHeights: true,
identityProvider: new TerminalTabsIdentityProvider(),
accessibilityProvider: new TerminalTabsAccessibilityProvider(),
styleController: id => new DefaultStyleController(DOM.createStyleSheet(container), id),
filter: undefined,
smoothScrolling: configurationService.getValue<boolean>('workbench.list.smoothScrolling'),
multipleSelectionSupport: false,
expandOnlyOnTwistieClick: true
},
contextKeyService,
listService,
themeService,
configurationService,
keybindingService,
accessibilityService,
);
this.setChildren(null, undefined);
const children = createTerminalTabsIterator(terminalService.terminalTabs);
this.setChildren(null, children);
this.onDidChangeSelection(e => {
if (e.elements && e.elements[0]) {
if ('_instance' in e.elements[0]) {
e.elements[0].instance.focus(true);
} else {
terminalService.setActiveTabByIndex(terminalService.terminalTabs.indexOf(e.elements[0].tab));
}
}
});
}
}

class TerminalTabsDelegate implements IListVirtualDelegate<TerminalTab> {
getHeight(element: any): number {
return 24;
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
}
getTemplateId(element: any): string {
return 'terminal.tabs';
}
}
class TerminalTabsIdentityProvider implements IIdentityProvider<TabTreeNode> {
constructor() {
}
getId(element: TabTreeNode): { toString(): string; } {
if ('tab' in element) {
return element.tab.title;
} else {
return element.instance.instanceId;
}
}

}
class TerminalTabsAccessibilityProvider implements IListAccessibilityProvider<TabTreeNode> {
getAriaLabel(element: TabTreeNode) {
if ('tab' in element) {
return element.tab ? element.tab.terminalInstances.length > 1 ? `Terminals (${element.tab.terminalInstances.length})` : element.tab.terminalInstances[0].title : '';
} else {
return element.instance.title;
}
}

getWidgetAriaLabel() {
return localize('terminal.tabs', "TerminalTabs");
}
}

class TerminalTabsRenderer implements ITreeRenderer<TabTreeNode, never, ITerminalTabEntryTemplate> {

templateId = 'terminal.tabs';

renderTemplate(container: HTMLElement): ITerminalTabEntryTemplate {
return {
labelElement: DOM.append(container, $('.terminal-tabs-entry')),
};
}

renderElement(node: ITreeNode<TabTreeNode>, index: number, template: ITerminalTabEntryTemplate): void {
let label = '';
let item = node.element;
if ('children' in item) {
label = item ? item.children.length === 0 ? 'Starting...' : item?.children.length > 1 ? `Terminals (${item.children.length})` : item.children[0].instance.title : '';
} else if ('instance' in item) {
label = item.instance.title;
}
template.labelElement.textContent = label;
template.labelElement.title = label;
}

disposeTemplate(templateData: ITerminalTabEntryTemplate): void {
}
}

interface ITerminalTabEntryTemplate {
labelElement: HTMLElement;
}

type TabTreeNode = TabTreeElement | TabTreeChild;

class TabTreeElement {
private _tab: ITerminalTab;
private _children: TabTreeChild[];
constructor(tab: ITerminalTab) {
this._tab = tab;
this._children = this._tab.terminalInstances.map(i => new TabTreeChild(i, this._tab));
}
get tab(): ITerminalTab {
return this._tab;
}
get children(): TabTreeChild[] {
return this._children;
}
set children(newChildren: TabTreeChild[]) {
this._children = newChildren;
}
}

class TabTreeChild {
private _instance: ITerminalInstance;
private _tab: ITerminalTab;
constructor(instance: ITerminalInstance, tab: ITerminalTab) {
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
this._instance = instance;
this._tab = tab;
}
get instance(): ITerminalInstance {
return this._instance;
}
get parent(): ITerminalTab {
return this._tab;
}
}


function createTerminalTabsIterator(tabs: ITerminalTab[]): Iterable<ITreeElement<TabTreeNode>> {
const result = tabs.map(tab => {
const hasChildren = tab.terminalInstances.length > 1;
const elt = new TabTreeElement(tab);
return {
element: elt,
collapsed: true,
collapsible: hasChildren,
children: getChildren(elt)
};
});
return result;
}

function getChildren(elt: TabTreeElement): Iterable<ITreeElement<TabTreeChild>> | undefined {
if (elt.children.length > 1) {
return elt.children.map(child => {
return {
element: child,
collapsed: true,
collapsible: false
};
});
}
return undefined;
}
36 changes: 28 additions & 8 deletions src/vs/workbench/contrib/terminal/browser/terminalView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { selectBorder } from 'vs/platform/theme/common/colorRegistry';
import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
import { BrowserFeatures } from 'vs/base/browser/canIUse';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { TabsView } from 'vs/workbench/contrib/terminal/browser/tabsView';

const FIND_FOCUS_CLASS = 'find-focused';

Expand All @@ -47,6 +48,7 @@ export class TerminalViewPane extends ViewPane {
private _parentDomElement: HTMLElement | undefined;
private _terminalContainer: HTMLElement | undefined;
private _findWidget: TerminalFindWidget | undefined;
private _tabsView: TabsView | undefined;
private _terminalsInitialized = false;
private _bodyDimensions: { width: number, height: number } = { width: 0, height: 0 };
private _isWelcomeShowing: boolean = false;
Expand Down Expand Up @@ -89,19 +91,33 @@ export class TerminalViewPane extends ViewPane {
});
}

protected renderBody(container: HTMLElement): void {
public renderBody(container?: HTMLElement): void {
if (!container && !this._parentDomElement) {
return;
} else if (!container) {
container = this._parentDomElement;
}
if (!container) {
return;
}
super.renderBody(container);

this._parentDomElement = container;
this._parentDomElement.classList.add('integrated-terminal');
this._fontStyleElement = document.createElement('style');

this._terminalContainer = document.createElement('div');
this._terminalContainer.classList.add('terminal-outer-container');
this._terminalContainer.style.display = this.shouldShowWelcome() ? 'none' : 'block';

this._findWidget = this._instantiationService.createInstance(TerminalFindWidget, this._terminalService.getFindState());
this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer!.classList.add(FIND_FOCUS_CLASS));
if (!this._terminalContainer) {
this._terminalContainer = document.createElement('div');
this._terminalContainer.classList.add('terminal-outer-container');
this._terminalContainer.style.display = this.shouldShowWelcome() ? 'none' : 'block';
}
if (!this._findWidget) {
this._findWidget = this._instantiationService.createInstance(TerminalFindWidget, this._terminalService.getFindState());
this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer!.classList.add(FIND_FOCUS_CLASS));
}
if (!this._tabsView && this._terminalService.configHelper.config.showTabs) {
this._tabsView = this.instantiationService.createInstance(TabsView, 'terminal', this._terminalContainer);
}

this._parentDomElement.appendChild(this._fontStyleElement);
this._parentDomElement.appendChild(this._terminalContainer);
Expand Down Expand Up @@ -165,7 +181,11 @@ export class TerminalViewPane extends ViewPane {

this._bodyDimensions.width = width;
this._bodyDimensions.height = height;
this._terminalService.terminalTabs.forEach(t => t.layout(width, height));
if (this._tabsView) {
this._tabsView?.layout(width, height);
} else {
this._terminalService.terminalTabs.forEach(t => t.layout(width, height));
}
}

public getActionViewItem(action: Action): IActionViewItem | undefined {
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export interface ITerminalConfiguration {
};
profiles: ITerminalProfiles;
useWslProfiles: boolean;
showTabs: boolean;
altClickMovesCursor: boolean;
macOptionIsMeta: boolean;
macOptionClickForcesSelection: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ export const terminalConfiguration: IConfigurationNode = {
type: 'boolean',
default: true
},
'terminal.integrated.showTabs': {
description: localize('terminal.integrated.showTabs', 'Controls whether or not the terminal tabs widget is shown'),
type: 'boolean',
default: false
},
'terminal.integrated.macOptionIsMeta': {
description: localize('terminal.integrated.macOptionIsMeta', "Controls whether to treat the option key as the meta key in the terminal on macOS."),
type: 'boolean',
Expand Down