提交 7a9f0350 编写于 作者: J Joao Moreno

Merge branch 'scm-viewlet'

......@@ -382,9 +382,7 @@ export class Repository implements Disposable {
const onRelevantGitChange = filterEvent(onRelevantRepositoryChange, uri => /\/\.git\//.test(uri.path));
onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables);
const label = `${path.basename(repository.root)} (Git)`;
this._sourceControl = scm.createSourceControl('git', label);
this._sourceControl = scm.createSourceControl('git', 'Git', Uri.parse(repository.root));
this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit"), arguments: [this._sourceControl] };
this._sourceControl.quickDiffProvider = this;
this.disposables.push(this._sourceControl);
......
......@@ -4,11 +4,10 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { escape } from 'vs/base/common/strings';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
import * as objects from 'vs/base/common/objects';
import { expand as expandOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { render as renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
export interface IHighlight {
start: number;
......@@ -64,19 +63,19 @@ export class HighlightedLabel implements IDisposable {
}
if (pos < highlight.start) {
htmlContent.push('<span>');
htmlContent.push(expandOcticons(escape(this.text.substring(pos, highlight.start))));
htmlContent.push(renderOcticons(this.text.substring(pos, highlight.start)));
htmlContent.push('</span>');
pos = highlight.end;
}
htmlContent.push('<span class="highlight">');
htmlContent.push(expandOcticons(escape(this.text.substring(highlight.start, highlight.end))));
htmlContent.push(renderOcticons(this.text.substring(highlight.start, highlight.end)));
htmlContent.push('</span>');
pos = highlight.end;
}
if (pos < this.text.length) {
htmlContent.push('<span>');
htmlContent.push(expandOcticons(escape(this.text.substring(pos))));
htmlContent.push(renderOcticons(this.text.substring(pos)));
htmlContent.push('</span>');
}
......
......@@ -179,7 +179,13 @@ export class InputBox extends Widget {
});
}
setTimeout(() => this.updateMirror(), 0);
setTimeout(() => {
if (!this.input) {
return;
}
this.updateMirror();
}, 0);
// Support actions
if (this.options.actions) {
......
......@@ -398,7 +398,7 @@ class MouseController<T> implements IDisposable {
if (isSelectionRangeChangeEvent(e) && reference !== undefined) {
const min = Math.min(reference, focus);
const max = Math.max(reference, focus);
const rangeSelection = range(max + 1, min);
const rangeSelection = range(min, max + 1);
const selection = this.list.getSelection();
const contiguousRange = getContiguousRangeContaining(disjunction(selection, [reference]), reference);
......
......@@ -5,8 +5,8 @@
import octiconLabel = require('vs/base/browser/ui/octiconLabel/octiconLabel');
import { escape } from 'vs/base/common/strings';
function expand(text: string): string {
return text;
function render(text: string): string {
return escape(text);
}
class MockOcticonLabel {
......@@ -18,16 +18,13 @@ class MockOcticonLabel {
}
set text(text: string) {
let innerHTML = text || '';
innerHTML = escape(innerHTML);
innerHTML = expand(innerHTML);
this._container.innerHTML = innerHTML;
this._container.innerHTML = render(text || '');
}
}
var mock: typeof octiconLabel = {
expand: expand,
render: render,
OcticonLabel: <any>MockOcticonLabel
};
export = mock;
\ No newline at end of file
......@@ -8,12 +8,16 @@ import 'vs/css!./octicons/octicons';
import 'vs/css!./octicons/octicons-animations';
import { escape } from 'vs/base/common/strings';
export function expand(text: string): string {
function expand(text: string): string {
return text.replace(/\$\(((.+?)(~(.*?))?)\)/g, (match, g1, name, g3, animation) => {
return `<span class="octicon octicon-${name} ${animation ? `octicon-animation-${animation}` : ''}"></span>`;
});
}
export function render(label: string): string {
return expand(escape(label));
}
export class OcticonLabel {
private _container: HTMLElement;
......@@ -23,13 +27,10 @@ export class OcticonLabel {
}
set text(text: string) {
let innerHTML = text || '';
innerHTML = escape(innerHTML);
innerHTML = expand(innerHTML);
this._container.innerHTML = innerHTML;
this._container.innerHTML = render(text || '');
}
set title(title: string) {
this._container.title = title;
}
}
}
\ No newline at end of file
......@@ -268,6 +268,10 @@ export class Sash extends EventEmitter {
this.isDisabled = true;
}
get enabled(): boolean {
return !this.isDisabled;
}
public dispose(): void {
if (this.$e) {
this.$e.destroy();
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-panel-view {
width: 100%;
height: 100%;
}
.monaco-panel-view .panel {
overflow: hidden;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.monaco-panel-view .panel > .panel-header {
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
padding-left: 20px;
overflow: hidden;
display: flex;
cursor: pointer;
}
.monaco-panel-view .panel > .panel-header {
background-image: url('arrow-collapse.svg');
background-position: 2px center;
background-repeat: no-repeat;
}
.monaco-panel-view .panel > .panel-header.expanded {
background-image: url('arrow-expand.svg');
background-position: 2px center;
background-repeat: no-repeat;
}
.vs-dark .monaco-panel-view .panel > .panel-header {
background-image: url('arrow-collapse-dark.svg');
}
.vs-dark .monaco-panel-view .panel > .panel-header.expanded {
background-image: url('arrow-expand-dark.svg');
}
/* TODO: actions should be part of the panel, but they aren't yet */
.monaco-panel-view .panel > .panel-header > .actions {
display: none;
flex: 1;
}
/* TODO: actions should be part of the panel, but they aren't yet */
.monaco-panel-view .panel:hover > .panel-header > .actions,
.monaco-panel-view .panel > .panel-header.focused > .actions {
display: initial;
}
/* TODO: actions should be part of the panel, but they aren't yet */
.monaco-panel-view .panel > .panel-header > .actions .action-label {
width: 28px;
height: 22px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
margin-right: 0;
}
/* Bold font style does not go well with CJK fonts */
.monaco-panel-view:lang(zh-Hans) .panel > .panel-header,
.monaco-panel-view:lang(zh-Hant) .panel > .panel-header,
.monaco-panel-view:lang(ja) .panel > .panel-header,
.monaco-panel-view:lang(ko) .panel > .panel-header {
font-weight: normal;
}
.monaco-panel-view .panel > .panel-header.hidden {
display: none;
}
.monaco-panel-view .panel > .panel-body {
overflow: hidden;
flex: 1;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./panelview';
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
import Event, { Emitter, chain } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { $, append, addClass, removeClass, toggleClass, trackFocus } from 'vs/base/browser/dom';
import { firstIndex } from 'vs/base/common/arrays';
import { Color, RGBA } from 'vs/base/common/color';
import { SplitView, IView } from './splitview2';
export interface IPanelOptions {
ariaHeaderLabel?: string;
minimumBodySize?: number;
maximumBodySize?: number;
expanded?: boolean;
}
export interface IPanelStyles {
dropBackground?: Color;
headerForeground?: Color;
headerBackground?: Color;
headerHighContrastBorder?: Color;
}
export abstract class Panel implements IView {
private static HEADER_SIZE = 22;
private _expanded: boolean;
private _headerVisible = true;
private _onDidChange = new Emitter<void>();
private _minimumBodySize: number;
private _maximumBodySize: number;
private ariaHeaderLabel: string;
private header: HTMLElement;
protected disposables: IDisposable[] = [];
get draggable(): HTMLElement {
return this.header;
}
private _dropBackground: Color | undefined;
get dropBackground(): Color | undefined {
return this._dropBackground;
}
get minimumBodySize(): number {
return this._minimumBodySize;
}
set minimumBodySize(size: number) {
this._minimumBodySize = size;
this._onDidChange.fire();
}
get maximumBodySize(): number {
return this._maximumBodySize;
}
set maximumBodySize(size: number) {
this._maximumBodySize = size;
this._onDidChange.fire();
}
private get headerSize(): number {
return this.headerVisible ? Panel.HEADER_SIZE : 0;
}
get minimumSize(): number {
const headerSize = this.headerSize;
const expanded = !this.headerVisible || this.expanded;
const minimumBodySize = expanded ? this._minimumBodySize : 0;
return headerSize + minimumBodySize;
}
get maximumSize(): number {
const headerSize = this.headerSize;
const expanded = !this.headerVisible || this.expanded;
const maximumBodySize = expanded ? this._maximumBodySize : 0;
return headerSize + maximumBodySize;
}
readonly onDidChange: Event<void> = this._onDidChange.event;
constructor(options: IPanelOptions = {}) {
this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
this.ariaHeaderLabel = options.ariaHeaderLabel || '';
this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120;
this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY;
this.header = $('.panel-header');
}
get expanded(): boolean {
return this._expanded;
}
set expanded(expanded: boolean) {
if (this._expanded === !!expanded) {
return;
}
this._expanded = !!expanded;
this.updateHeader();
this._onDidChange.fire();
}
get headerVisible(): boolean {
return this._headerVisible;
}
set headerVisible(visible: boolean) {
if (this._headerVisible === !!visible) {
return;
}
this._headerVisible = !!visible;
this.updateHeader();
this._onDidChange.fire();
}
render(container: HTMLElement): void {
const panel = append(container, $('.panel'));
append(panel, this.header);
this.header.setAttribute('tabindex', '0');
this.header.setAttribute('role', 'toolbar');
this.header.setAttribute('aria-label', this.ariaHeaderLabel);
this.renderHeader(this.header);
const focusTracker = trackFocus(this.header);
focusTracker.addFocusListener(() => addClass(this.header, 'focused'));
focusTracker.addBlurListener(() => removeClass(this.header, 'focused'));
this.updateHeader();
const onHeaderKeyDown = chain(domEvent(this.header, 'keydown'))
.map(e => new StandardKeyboardEvent(e));
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space)
.event(() => this.expanded = !this.expanded, null, this.disposables);
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow)
.event(() => this.expanded = false, null, this.disposables);
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow)
.event(() => this.expanded = true, null, this.disposables);
domEvent(this.header, 'click')
(() => this.expanded = !this.expanded, null, this.disposables);
// TODO@Joao move this down to panelview
// onHeaderKeyDown.filter(e => e.keyCode === KeyCode.UpArrow)
// .event(focusPrevious, this, this.disposables);
// onHeaderKeyDown.filter(e => e.keyCode === KeyCode.DownArrow)
// .event(focusNext, this, this.disposables);
const body = append(panel, $('.panel-body'));
this.renderBody(body);
}
layout(size: number): void {
const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0;
this.layoutBody(size - headerSize);
}
style(styles: IPanelStyles): void {
this.header.style.color = styles.headerForeground ? styles.headerForeground.toString() : null;
this.header.style.backgroundColor = styles.headerBackground ? styles.headerBackground.toString() : null;
this.header.style.borderTop = styles.headerHighContrastBorder ? `1px solid ${styles.headerHighContrastBorder}` : null;
this._dropBackground = styles.dropBackground;
}
private updateHeader(): void {
const expanded = !this.headerVisible || this.expanded;
this.header.style.height = `${this.headerSize}px`;
this.header.style.lineHeight = `${this.headerSize}px`;
toggleClass(this.header, 'hidden', !this.headerVisible);
toggleClass(this.header, 'expanded', expanded);
this.header.setAttribute('aria-expanded', String(expanded));
}
protected abstract renderHeader(container: HTMLElement): void;
protected abstract renderBody(container: HTMLElement): void;
protected abstract layoutBody(size: number): void;
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
interface IDndContext {
draggable: PanelDraggable | null;
}
class PanelDraggable implements IDisposable {
private static DefaultDragOverBackgroundColor = new Color(new RGBA(128, 128, 128, 0.5));
// see https://github.com/Microsoft/vscode/issues/14470
private dragOverCounter = 0;
private disposables: IDisposable[] = [];
private _onDidDrop = new Emitter<{ from: Panel, to: Panel }>();
readonly onDidDrop = this._onDidDrop.event;
constructor(private panel: Panel, private context: IDndContext) {
domEvent(panel.draggable, 'dragstart')(this.onDragStart, this, this.disposables);
domEvent(panel.draggable, 'dragenter')(this.onDragEnter, this, this.disposables);
domEvent(panel.draggable, 'dragleave')(this.onDragLeave, this, this.disposables);
domEvent(panel.draggable, 'dragend')(this.onDragEnd, this, this.disposables);
domEvent(panel.draggable, 'drop')(this.onDrop, this, this.disposables);
}
private onDragStart(e: DragEvent): void {
e.dataTransfer.effectAllowed = 'move';
const dragImage = append(document.body, $('.monaco-panel-drag-image', {}, this.panel.draggable.textContent));
e.dataTransfer.setDragImage(dragImage, -10, -10);
setTimeout(() => document.body.removeChild(dragImage), 0);
this.context.draggable = this;
}
private onDragEnter(e: DragEvent): void {
if (!this.context.draggable || this.context.draggable === this) {
return;
}
this.dragOverCounter++;
this.renderHeader();
}
private onDragLeave(e: DragEvent): void {
if (!this.context.draggable || this.context.draggable === this) {
return;
}
this.dragOverCounter--;
if (this.dragOverCounter === 0) {
this.renderHeader();
}
}
private onDragEnd(e: DragEvent): void {
if (!this.context.draggable) {
return;
}
this.dragOverCounter = 0;
this.renderHeader();
this.context.draggable = null;
}
private onDrop(e: DragEvent): void {
if (!this.context.draggable) {
return;
}
this.dragOverCounter = 0;
this.renderHeader();
if (this.context.draggable !== this) {
this._onDidDrop.fire({ from: this.context.draggable.panel, to: this.panel });
}
this.context.draggable = null;
}
private renderHeader(): void {
let backgroundColor: string = null;
if (this.dragOverCounter > 0) {
backgroundColor = (this.panel.dropBackground || PanelDraggable.DefaultDragOverBackgroundColor).toString();
}
this.panel.draggable.style.backgroundColor = backgroundColor;
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
export class IPanelViewOptions {
dnd?: boolean;
}
interface IPanelItem {
panel: Panel;
disposable: IDisposable;
}
export class PanelView implements IDisposable {
private dnd: boolean;
private dndContext: IDndContext = { draggable: null };
private el: HTMLElement;
private panelItems: IPanelItem[] = [];
private splitview: SplitView;
private animationTimer: number | null = null;
private _onDidDrop = new Emitter<{ from: Panel, to: Panel }>();
readonly onDidDrop: Event<{ from: Panel, to: Panel }> = this._onDidDrop.event;
constructor(private container: HTMLElement, options: IPanelViewOptions = {}) {
this.dnd = !!options.dnd;
this.el = append(container, $('.monaco-panel-view'));
this.splitview = new SplitView(this.el);
}
addPanel(panel: Panel, size: number, index = this.splitview.length): void {
const disposables: IDisposable[] = [];
panel.onDidChange(this.setupAnimation, this, disposables);
if (this.dnd) {
const draggable = new PanelDraggable(panel, this.dndContext);
disposables.push(draggable);
draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop, disposables);
}
const panelItem = { panel, disposable: combinedDisposable(disposables) };
this.panelItems.splice(index, 0, panelItem);
this.splitview.addView(panel, size, index);
}
removePanel(panel: Panel): void {
const index = firstIndex(this.panelItems, item => item.panel === panel);
if (index === -1) {
return;
}
this.splitview.removeView(index);
const panelItem = this.panelItems.splice(index, 1)[0];
panelItem.disposable.dispose();
}
movePanel(from: Panel, to: Panel): void {
const fromIndex = firstIndex(this.panelItems, item => item.panel === from);
const toIndex = firstIndex(this.panelItems, item => item.panel === to);
if (fromIndex === -1 || toIndex === -1) {
return;
}
const [panelItem] = this.panelItems.splice(fromIndex, 1);
this.panelItems.splice(toIndex < fromIndex ? toIndex : toIndex - 1, 0, panelItem);
this.splitview.moveView(fromIndex, toIndex);
}
resizePanel(panel: Panel, size: number): void {
const index = firstIndex(this.panelItems, item => item.panel === panel);
if (index === -1) {
return;
}
this.splitview.resizeView(index, size);
}
layout(size: number): void {
this.splitview.layout(size);
}
private setupAnimation(): void {
if (typeof this.animationTimer === 'number') {
window.clearTimeout(this.animationTimer);
}
addClass(this.el, 'animated');
this.animationTimer = window.setTimeout(() => {
this.animationTimer = null;
removeClass(this.el, 'animated');
}, 200);
}
dispose(): void {
this.panelItems.forEach(i => i.disposable.dispose());
this.splitview.dispose();
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-split-view2 {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
}
.monaco-split-view2 > .split-view-view {
overflow: hidden;
}
.monaco-split-view2.vertical > .split-view-view {
width: 100%;
}
.monaco-split-view2.horizontal > .split-view-view {
height: 100%;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./splitview2';
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import Event, { fromEventEmitter, mapEvent } from 'vs/base/common/event';
import types = require('vs/base/common/types');
import dom = require('vs/base/browser/dom');
import { clamp } from 'vs/base/common/numbers';
import { range, firstIndex } from 'vs/base/common/arrays';
import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash';
export { Orientation } from 'vs/base/browser/ui/sash/sash';
export interface ISplitViewOptions {
orientation?: Orientation; // default Orientation.VERTICAL
}
export interface IView {
readonly minimumSize: number;
readonly maximumSize: number;
readonly onDidChange: Event<void>;
render(container: HTMLElement, orientation: Orientation): void;
layout(size: number, orientation: Orientation): void;
}
interface ISashEvent {
sash: Sash;
start: number;
current: number;
}
interface IViewItem {
view: IView;
size: number;
container: HTMLElement;
disposable: IDisposable;
layout(): void;
}
interface ISashItem {
sash: Sash;
disposable: IDisposable;
}
interface ISashDragState {
index: number;
start: number;
sizes: number[];
minDelta: number;
maxDelta: number;
}
export class SplitView implements IDisposable {
private orientation: Orientation;
private el: HTMLElement;
private size = 0;
private contentSize = 0;
private viewItems: IViewItem[] = [];
private sashItems: ISashItem[] = [];
private sashDragState: ISashDragState;
get length(): number {
return this.viewItems.length;
}
constructor(private container: HTMLElement, options: ISplitViewOptions = {}) {
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
this.el = document.createElement('div');
dom.addClass(this.el, 'monaco-split-view2');
dom.addClass(this.el, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal');
container.appendChild(this.el);
}
addView(view: IView, size: number, index = this.viewItems.length): void {
// Add view
const container = dom.$('.split-view-view');
if (this.viewItems.length === 1) {
this.el.appendChild(container);
} else {
this.el.insertBefore(container, this.el.children.item(index));
}
const onChangeDisposable = mapEvent(view.onDidChange, () => item)(this.onViewChange, this);
const containerDisposable = toDisposable(() => this.el.removeChild(container));
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
const layoutContainer = this.orientation === Orientation.VERTICAL
? size => item.container.style.height = `${item.size}px`
: size => item.container.style.width = `${item.size}px`;
const layout = () => {
layoutContainer(item.size);
item.view.layout(item.size, this.orientation);
};
size = Math.round(size);
const item: IViewItem = { view, container, size, layout, disposable };
this.viewItems.splice(index, 0, item);
// Add sash
if (this.viewItems.length > 1) {
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
const sash = new Sash(this.el, layoutProvider, { orientation });
const sashEventMapper = this.orientation === Orientation.VERTICAL
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
const onStart = mapEvent(fromEventEmitter<IBaseSashEvent>(sash, 'start'), sashEventMapper);
const onStartDisposable = onStart(this.onSashStart, this);
const onChange = mapEvent(fromEventEmitter<IBaseSashEvent>(sash, 'change'), sashEventMapper);
const onSashChangeDisposable = onChange(this.onSashChange, this);
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, sash]);
const sashItem: ISashItem = { sash, disposable };
this.sashItems.splice(index - 1, 0, sashItem);
}
view.render(container, this.orientation);
this.relayout();
}
removeView(index: number): void {
if (index < 0 || index >= this.viewItems.length) {
return;
}
// Remove view
const viewItem = this.viewItems.splice(index, 1)[0];
viewItem.disposable.dispose();
// Remove sash
if (this.viewItems.length >= 1) {
const sashIndex = Math.max(index - 1, 0);
const sashItem = this.sashItems.splice(sashIndex, 1)[0];
sashItem.disposable.dispose();
}
this.relayout();
}
moveView(from: number, to: number): void {
if (from < 0 || from >= this.viewItems.length) {
return;
}
if (to < 0 || to >= this.viewItems.length) {
return;
}
if (from === to) {
return;
}
const viewItem = this.viewItems.splice(from, 1)[0];
this.viewItems.splice(to, 0, viewItem);
this.layoutViews();
}
private relayout(): void {
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
this.resize(this.viewItems.length - 1, this.contentSize - contentSize);
}
layout(size: number): void {
const previousSize = Math.max(this.size, this.contentSize);
this.size = size;
this.resize(this.viewItems.length - 1, size - previousSize);
}
private onSashStart({ sash, start }: ISashEvent): void {
const index = firstIndex(this.sashItems, item => item.sash === sash);
const sizes = this.viewItems.map(i => i.size);
const upIndexes = range(index, -1);
const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
const downIndexes = range(index + 1, this.viewItems.length);
const collapseDown = downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
const minDelta = -Math.min(collapseUp, expandDown);
const maxDelta = Math.min(collapseDown, expandUp);
this.sashDragState = { start, index, sizes, minDelta, maxDelta };
}
private onSashChange({ sash, current }: ISashEvent): void {
const { index, start, sizes, minDelta, maxDelta } = this.sashDragState;
const delta = clamp(current - start, minDelta, maxDelta);
this.resize(index, delta, sizes);
}
private onViewChange(item: IViewItem): void {
const index = this.viewItems.indexOf(item);
if (index < 0 || index >= this.viewItems.length) {
return;
}
const size = clamp(item.size, item.view.minimumSize, item.view.maximumSize);
item.size = size;
this.relayout();
}
resizeView(index: number, size: number): void {
if (index < 0 || index >= this.viewItems.length) {
return;
}
const item = this.viewItems[index];
size = Math.round(size);
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
let delta = size - item.size;
if (delta !== 0 && index < this.viewItems.length - 1) {
const downIndexes = range(index + 1, this.viewItems.length);
const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
const deltaDown = clamp(delta, -expandDown, collapseDown);
this.resize(index, deltaDown);
delta -= deltaDown;
}
if (delta !== 0 && index > 0) {
const upIndexes = range(index - 1, -1);
const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
const deltaUp = clamp(-delta, -collapseUp, expandUp);
this.resize(index - 1, deltaUp);
}
}
private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size)): void {
if (index < 0 || index >= this.viewItems.length) {
return;
}
if (delta !== 0) {
const upIndexes = range(index, -1);
const up = upIndexes.map(i => this.viewItems[i]);
const upSizes = upIndexes.map(i => sizes[i]);
const downIndexes = range(index + 1, this.viewItems.length);
const down = downIndexes.map(i => this.viewItems[i]);
const downSizes = downIndexes.map(i => sizes[i]);
for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < up.length; i++) {
const item = up[i];
const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize);
const viewDelta = size - upSizes[i];
deltaUp -= viewDelta;
item.size = size;
}
for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < down.length; i++) {
const item = down[i];
const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize);
const viewDelta = size - downSizes[i];
deltaDown += viewDelta;
item.size = size;
}
}
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
let emptyDelta = this.size - contentSize;
for (let i = this.viewItems.length - 1; emptyDelta > 0 && i >= 0; i--) {
const item = this.viewItems[i];
const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize);
const viewDelta = size - item.size;
emptyDelta -= viewDelta;
item.size = size;
}
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
this.layoutViews();
}
private layoutViews(): void {
this.viewItems.forEach(item => item.layout());
this.sashItems.forEach(item => item.sash.layout());
// Update sashes enablement
let previous = false;
const collapsesDown = this.viewItems.map(i => previous = (i.size - i.view.minimumSize > 0) || previous);
previous = false;
const expandsDown = this.viewItems.map(i => previous = (i.view.maximumSize - i.size > 0) || previous);
const reverseViews = [...this.viewItems].reverse();
previous = false;
const collapsesUp = reverseViews.map(i => previous = (i.size - i.view.minimumSize > 0) || previous).reverse();
previous = false;
const expandsUp = reverseViews.map(i => previous = (i.view.maximumSize - i.size > 0) || previous).reverse();
this.sashItems.forEach((s, i) => {
if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) {
s.sash.enable();
} else {
s.sash.disable();
}
});
}
private getSashPosition(sash: Sash): number {
let position = 0;
for (let i = 0; i < this.sashItems.length; i++) {
position += this.viewItems[i].size;
if (this.sashItems[i].sash === sash) {
return position;
}
}
return 0;
}
dispose(): void {
this.viewItems.forEach(i => i.disposable.dispose());
this.viewItems = [];
this.sashItems.forEach(i => i.disposable.dispose());
this.sashItems = [];
}
}
......@@ -325,11 +325,43 @@ export function flatten<T>(arr: T[][]): T[] {
return arr.reduce((r, v) => r.concat(v), []);
}
export function range(to: number, from = 0): number[] {
export function range(to: number): number[];
export function range(from: number, to: number): number[];
export function range(arg: number, to?: number): number[] {
let from = typeof to === 'number' ? arg : 0;
if (typeof to === 'number') {
from = arg;
} else {
from = 0;
to = arg;
}
const result: number[] = [];
for (let i = from; i < to; i++) {
result.push(i);
if (from <= to) {
for (let i = from; i < to; i++) {
result.push(i);
}
} else {
for (let i = from; i > to; i--) {
result.push(i);
}
}
return result;
}
export function weave<T>(a: T[], b: T[]): T[] {
const result: T[] = [];
let ai = 0, bi = 0;
for (let i = 0, length = a.length + b.length; i < length; i++) {
if ((i % 2 === 0 && ai < a.length) || bi >= b.length) {
result.push(a[ai++]);
} else {
result.push(b[bi++]);
}
}
return result;
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, toDisposable, combinedDisposable, empty as EmptyDisposable } from 'vs/base/common/lifecycle';
import CallbackList from 'vs/base/common/callbackList';
import { EventEmitter } from 'vs/base/common/eventEmitter';
import { TPromise } from 'vs/base/common/winjs.base';
......@@ -528,3 +528,21 @@ export function echo<T>(event: Event<T>, nextTick = false, buffer: T[] = []): Ev
return emitter.event;
}
export class Relay<T> implements IDisposable {
private emitter = new Emitter<T>();
readonly output: Event<T> = this.emitter.event;
private disposable: IDisposable = EmptyDisposable;
set input(event: Event<T>) {
this.disposable.dispose();
this.disposable = event(this.emitter.fire, this.emitter);
}
dispose() {
this.disposable.dispose();
this.emitter.dispose();
}
}
\ No newline at end of file
......@@ -46,3 +46,8 @@ export function countToArray(fromOrTo: number, to?: number): number[] {
return result;
}
export function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Emitter } from 'vs/base/common/event';
import { SplitView, IView, Orientation } from 'vs/base/browser/ui/splitview/splitview2';
import { Sash } from 'vs/base/browser/ui/sash/sash';
class TestView implements IView {
private _onDidChange = new Emitter<void>();
readonly onDidChange = this._onDidChange.event;
get minimumSize(): number { return this._minimumSize; }
set minimumSize(size: number) { this._minimumSize = size; this._onDidChange.fire(); }
get maximumSize(): number { return this._maximumSize; }
set maximumSize(size: number) { this._maximumSize = size; this._onDidChange.fire(); }
private _onDidRender = new Emitter<{ container: HTMLElement; orientation: Orientation }>();
readonly onDidRender = this._onDidRender.event;
private _size = 0;
get size(): number { return this._size; }
private _onDidLayout = new Emitter<{ size: number; orientation: Orientation }>();
readonly onDidLayout = this._onDidLayout.event;
private _onDidFocus = new Emitter<void>();
readonly onDidFocus = this._onDidFocus.event;
constructor(
private _minimumSize: number,
private _maximumSize: number
) {
assert(_minimumSize <= _maximumSize, 'splitview view minimum size must be <= maximum size');
}
render(container: HTMLElement, orientation: Orientation): void {
this._onDidRender.fire({ container, orientation });
}
layout(size: number, orientation: Orientation): void {
this._size = size;
this._onDidLayout.fire({ size, orientation });
}
focus(): void {
this._onDidFocus.fire();
}
dispose(): void {
this._onDidChange.dispose();
this._onDidRender.dispose();
this._onDidLayout.dispose();
this._onDidFocus.dispose();
}
}
function getSashes(splitview: SplitView): Sash[] {
return (splitview as any).sashItems.map(i => i.sash) as Sash[];
}
suite('Splitview', () => {
let container: HTMLElement;
setup(() => {
container = document.createElement('div');
container.style.position = 'absolute';
container.style.width = `${200}px`;
container.style.height = `${200}px`;
});
teardown(() => {
container = null;
});
test('empty splitview has empty DOM', () => {
const splitview = new SplitView(container);
assert.equal(container.firstElementChild.childElementCount, 0, 'split view should be empty');
splitview.dispose();
});
test('has views and sashes as children', () => {
const view1 = new TestView(20, 20);
const view2 = new TestView(20, 20);
const view3 = new TestView(20, 20);
const splitview = new SplitView(container);
splitview.addView(view1, 20);
splitview.addView(view2, 20);
splitview.addView(view3, 20);
let viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
assert.equal(viewQuery.length, 3, 'split view should have 3 views');
let sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
assert.equal(sashQuery.length, 2, 'split view should have 2 sashes');
splitview.removeView(2);
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
assert.equal(viewQuery.length, 2, 'split view should have 2 views');
sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
assert.equal(sashQuery.length, 1, 'split view should have 1 sash');
splitview.removeView(0);
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
assert.equal(viewQuery.length, 1, 'split view should have 1 view');
sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
assert.equal(sashQuery.length, 0, 'split view should have no sashes');
splitview.removeView(0);
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
assert.equal(viewQuery.length, 0, 'split view should have no views');
sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
assert.equal(sashQuery.length, 0, 'split view should have no sashes');
splitview.dispose();
view1.dispose();
view2.dispose();
view3.dispose();
});
test('calls view methods on addView and removeView', () => {
const view = new TestView(20, 20);
const splitview = new SplitView(container);
let didLayout = false;
const layoutDisposable = view.onDidLayout(() => didLayout = true);
let didRender = false;
const renderDisposable = view.onDidRender(() => didRender = true);
splitview.addView(view, 20);
assert.equal(view.size, 20, 'view has right size');
assert(didLayout, 'layout is called');
assert(didLayout, 'render is called');
splitview.dispose();
layoutDisposable.dispose();
renderDisposable.dispose();
view.dispose();
});
test('stretches view to viewport', () => {
const view = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view, 20);
assert.equal(view.size, 200, 'view is stretched');
splitview.layout(200);
assert.equal(view.size, 200, 'view stayed the same');
splitview.layout(100);
assert.equal(view.size, 100, 'view is collapsed');
splitview.layout(20);
assert.equal(view.size, 20, 'view is collapsed');
splitview.layout(10);
assert.equal(view.size, 20, 'view is clamped');
splitview.layout(200);
assert.equal(view.size, 200, 'view is stretched');
splitview.dispose();
view.dispose();
});
test('can resize views', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, 20);
splitview.addView(view2, 20);
splitview.addView(view3, 20);
assert.equal(view1.size, 160, 'view1 is stretched');
assert.equal(view2.size, 20, 'view2 size is 20');
assert.equal(view3.size, 20, 'view3 size is 20');
splitview.resizeView(1, 40);
assert.equal(view1.size, 140, 'view1 is collapsed');
assert.equal(view2.size, 40, 'view2 is stretched');
assert.equal(view3.size, 20, 'view3 stays the same');
splitview.resizeView(0, 70);
assert.equal(view1.size, 70, 'view1 is collapsed');
assert.equal(view2.size, 110, 'view2 is expanded');
assert.equal(view3.size, 20, 'view3 stays the same');
splitview.resizeView(2, 40);
assert.equal(view1.size, 70, 'view1 stays the same');
assert.equal(view2.size, 90, 'view2 is collapsed');
assert.equal(view3.size, 40, 'view3 is stretched');
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('reacts to view changes', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, 20);
splitview.addView(view2, 20);
splitview.addView(view3, 20);
assert.equal(view1.size, 160, 'view1 is stretched');
assert.equal(view2.size, 20, 'view2 size is 20');
assert.equal(view3.size, 20, 'view3 size is 20');
view1.maximumSize = 20;
assert.equal(view1.size, 20, 'view1 is collapsed');
assert.equal(view2.size, 20, 'view2 stays the same');
assert.equal(view3.size, 160, 'view3 is stretched');
view3.maximumSize = 40;
assert.equal(view1.size, 20, 'view1 stays the same');
assert.equal(view2.size, 140, 'view2 is stretched');
assert.equal(view3.size, 40, 'view3 is collapsed');
view2.maximumSize = 200;
assert.equal(view1.size, 20, 'view1 stays the same');
assert.equal(view2.size, 140, 'view2 stays the same');
assert.equal(view3.size, 40, 'view3 stays the same');
view3.maximumSize = Number.POSITIVE_INFINITY;
view3.minimumSize = 100;
assert.equal(view1.size, 20, 'view1 is collapsed');
assert.equal(view2.size, 80, 'view2 is collapsed');
assert.equal(view3.size, 100, 'view3 is stretched');
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('sashes are properly enabled/disabled', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, 20);
splitview.addView(view2, 20);
splitview.addView(view3, 20);
let sashes = getSashes(splitview);
assert.equal(sashes.length, 2, 'there are two sashes');
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
splitview.layout(60);
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
assert.equal(sashes[1].enabled, false, 'second sash is disabled');
splitview.layout(20);
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
assert.equal(sashes[1].enabled, false, 'second sash is disabled');
splitview.layout(200);
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
view1.maximumSize = 20;
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
view2.maximumSize = 20;
assert.equal(sashes[0].enabled, false, 'first sash is disabled');
assert.equal(sashes[1].enabled, false, 'second sash is disabled');
view1.maximumSize = 300;
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
view2.maximumSize = 200;
assert.equal(sashes[0].enabled, true, 'first sash is enabled');
assert.equal(sashes[1].enabled, true, 'second sash is enabled');
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
});
\ No newline at end of file
......@@ -15,11 +15,15 @@ export interface IThemable {
style: styleFn;
}
export function attachStyler(themeService: IThemeService, optionsMapping: { [optionsKey: string]: ColorIdentifier | ColorFunction }, widgetOrCallback: IThemable | styleFn): IDisposable {
export interface IColorMapping {
[optionsKey: string]: ColorIdentifier | ColorFunction | undefined;
}
export function attachStyler<T extends IColorMapping>(themeService: IThemeService, optionsMapping: T, widgetOrCallback: IThemable | styleFn): IDisposable {
function applyStyles(theme: ITheme): void {
const styles = Object.create(null);
for (let key in optionsMapping) {
const value = optionsMapping[key];
const value = optionsMapping[key as string];
if (typeof value === 'string') {
styles[key] = theme.getColor(value);
} else if (typeof value === 'function') {
......
......@@ -5506,6 +5506,11 @@ declare module 'vscode' {
*/
readonly label: string;
/**
* The (optional) Uri of the root of this source control.
*/
readonly rootUri: Uri | undefined;
/**
* The [input box](#SourceControlInputBox) for this source control.
*/
......@@ -5574,9 +5579,10 @@ declare module 'vscode' {
*
* @param id An `id` for the source control. Something short, eg: `git`.
* @param label A human-readable string for the source control. Eg: `Git`.
* @param rootUri An optional Uri of the root of the source control. Eg: `Uri.parse(workspaceRoot)`.
* @return An instance of [source control](#SourceControl).
*/
export function createSourceControl(id: string, label: string): SourceControl;
export function createSourceControl(id: string, label: string, rootUri?: Uri): SourceControl;
}
/**
......
......@@ -100,18 +100,17 @@ class MainThreadSCMProvider implements ISCMProvider {
get handle(): number { return this._handle; }
get label(): string { return this._label; }
get rootUri(): URI | undefined { return this._rootUri; }
get contextValue(): string { return this._contextValue; }
get commitTemplate(): string | undefined { return this.features.commitTemplate; }
get acceptInputCommand(): Command | undefined { return this.features.acceptInputCommand; }
get statusBarCommands(): Command[] | undefined { return this.features.statusBarCommands; }
get count(): number | undefined { return this.features.count; }
private _onDidChangeCommitTemplate = new Emitter<string>();
get onDidChangeCommitTemplate(): Event<string> { return this._onDidChangeCommitTemplate.event; }
private _count: number | undefined = undefined;
get count(): number | undefined { return this._count; }
private _onDidChange = new Emitter<void>();
get onDidChange(): Event<void> { return this._onDidChange.event; }
......@@ -120,15 +119,12 @@ class MainThreadSCMProvider implements ISCMProvider {
private _handle: number,
private _contextValue: string,
private _label: string,
private _rootUri: URI | undefined,
@ISCMService scmService: ISCMService,
@ICommandService private commandService: ICommandService
) { }
$updateSourceControl(features: SCMProviderFeatures): void {
if ('count' in features) {
this._count = features.count;
}
this.features = assign(this.features, features);
this._onDidChange.fire();
......@@ -275,8 +271,8 @@ export class MainThreadSCM implements MainThreadSCMShape {
this._disposables = dispose(this._disposables);
}
$registerSourceControl(handle: number, id: string, label: string): void {
const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, this.scmService, this.commandService);
$registerSourceControl(handle: number, id: string, label: string, rootUri: string | undefined): void {
const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, rootUri && URI.parse(rootUri), this.scmService, this.commandService);
const repository = this.scmService.registerSCMProvider(provider);
this._repositories[handle] = repository;
......
......@@ -486,14 +486,14 @@ export function createApiFactory(
get inputBox() {
return extHostSCM.getLastInputBox(extension);
},
createSourceControl(id: string, label: string) {
createSourceControl(id: string, label: string, rootUri?: vscode.Uri) {
mainThreadTelemetry.$publicLog('registerSCMProvider', {
extensionId: extension.id,
providerId: id,
providerLabel: label
});
return extHostSCM.createSourceControl(extension, id, label);
return extHostSCM.createSourceControl(extension, id, label, rootUri);
}
};
......
......@@ -363,7 +363,7 @@ export type SCMRawResourceSplices = [
];
export interface MainThreadSCMShape extends IDisposable {
$registerSourceControl(handle: number, id: string, label: string): void;
$registerSourceControl(handle: number, id: string, label: string, rootUri: string | undefined): void;
$updateSourceControl(handle: number, features: SCMProviderFeatures): void;
$unregisterSourceControl(handle: number): void;
......
......@@ -287,6 +287,10 @@ class ExtHostSourceControl implements vscode.SourceControl {
return this._label;
}
get rootUri(): vscode.Uri | undefined {
return this._rootUri;
}
private _inputBox: ExtHostSCMInputBox;
get inputBox(): ExtHostSCMInputBox { return this._inputBox; }
......@@ -356,9 +360,10 @@ class ExtHostSourceControl implements vscode.SourceControl {
private _commands: ExtHostCommands,
private _id: string,
private _label: string,
private _rootUri?: vscode.Uri
) {
this._inputBox = new ExtHostSCMInputBox(this._proxy, this.handle);
this._proxy.$registerSourceControl(this.handle, _id, _label);
this._proxy.$registerSourceControl(this.handle, _id, _label, _rootUri && _rootUri.toString());
}
private updatedResourceGroups = new Set<ExtHostSourceControlResourceGroup>();
......@@ -468,9 +473,9 @@ export class ExtHostSCM {
});
}
createSourceControl(extension: IExtensionDescription, id: string, label: string): vscode.SourceControl {
createSourceControl(extension: IExtensionDescription, id: string, label: string, rootUri: vscode.Uri | undefined): vscode.SourceControl {
const handle = ExtHostSCM._handlePool++;
const sourceControl = new ExtHostSourceControl(this._proxy, this._commands, id, label);
const sourceControl = new ExtHostSourceControl(this._proxy, this._commands, id, label, rootUri);
this._sourceControls.set(handle, sourceControl);
const sourceControls = this._sourceControlsByExtension.get(extension.id) || [];
......
......@@ -414,9 +414,14 @@ export class ViewsViewlet extends Viewlet {
}
}
private toggleViewVisibility(id: string): void {
toggleViewVisibility(id: string, visible?: boolean): void {
const view = this.getView(id);
let viewState = this.viewsStates.get(id);
if ((visible === true && view) || (visible === false && !view)) {
return;
}
if (view) {
viewState = viewState || this.createViewState(view);
viewState.isHidden = true;
......@@ -557,7 +562,7 @@ export class ViewsViewlet extends Viewlet {
private canBeVisible(viewDescriptor: IViewDescriptor): boolean {
const viewstate = this.viewsStates.get(viewDescriptor.id);
if (viewDescriptor.canToggleVisibility && viewstate && viewstate.isHidden) {
if (viewstate && viewstate.isHidden) {
return false;
}
return this.contextKeyService.contextMatchesRules(viewDescriptor.when);
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import Event, { Emitter } from 'vs/base/common/event';
import { ColorIdentifier, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { attachStyler, IColorMapping, IThemable } from 'vs/platform/theme/common/styler';
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND } from 'vs/workbench/common/theme';
import { Dimension, Builder } from 'vs/base/browser/builder';
import { append, $ } from 'vs/base/browser/dom';
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { firstIndex } from 'vs/base/common/arrays';
import { IAction, IActionRunner } from 'vs/base/common/actions';
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { Registry } from 'vs/platform/registry/common/platform';
import { prepareActions } from 'vs/workbench/browser/actions';
import { Viewlet, ViewletRegistry, Extensions } from 'vs/workbench/browser/viewlet';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { PanelView, IPanelOptions, Panel } from 'vs/base/browser/ui/splitview/panelview';
export interface IPanelColors extends IColorMapping {
dropBackground?: ColorIdentifier;
headerForeground?: ColorIdentifier;
headerBackground?: ColorIdentifier;
headerHighContrastBorder?: ColorIdentifier;
}
export function attachPanelStyler(widget: IThemable, themeService: IThemeService) {
return attachStyler<IPanelColors>(themeService, {
headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND,
headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND,
headerHighContrastBorder: contrastBorder,
dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND
}, widget);
}
export interface IViewletPanelOptions extends IPanelOptions {
actionRunner?: IActionRunner;
}
export abstract class ViewletPanel extends Panel {
private _onDidFocus = new Emitter<void>();
readonly onDidFocus: Event<void> = this._onDidFocus.event;
private actionRunner: IActionRunner;
private toolbar: ToolBar;
constructor(
readonly title: string,
options: IViewletPanelOptions,
@IKeybindingService protected keybindingService: IKeybindingService,
@IContextMenuService protected contextMenuService: IContextMenuService
) {
super(options);
this.actionRunner = options.actionRunner;
}
protected renderHeader(container: HTMLElement): void {
this.renderHeaderTitle(container);
const actions = append(container, $('.actions'));
this.toolbar = new ToolBar(actions, this.contextMenuService, {
orientation: ActionsOrientation.HORIZONTAL,
actionItemProvider: action => this.getActionItem(action),
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
actionRunner: this.actionRunner
});
this.disposables.push(this.toolbar);
this.updateActions();
}
protected renderHeaderTitle(container: HTMLElement): void {
append(container, $('.title', null, this.title));
}
focus(): void {
this._onDidFocus.fire();
}
protected updateActions(): void {
this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))();
this.toolbar.context = this.getActionsContext();
}
getActions(): IAction[] {
return [];
}
getSecondaryActions(): IAction[] {
return [];
}
getActionItem(action: IAction): IActionItem {
return null;
}
getActionsContext(): any {
return undefined;
}
getOptimalWidth(): number {
return 0;
}
}
export interface IViewsViewletOptions {
showHeaderInTitleWhenSingleView: boolean;
}
interface IViewletPanelItem {
panel: ViewletPanel;
disposable: IDisposable;
}
export class PanelViewlet extends Viewlet {
protected lastFocusedPanel: ViewletPanel | undefined;
private panelItems: IViewletPanelItem[] = [];
private panelview: PanelView;
protected get isSingleView(): boolean {
return this.options.showHeaderInTitleWhenSingleView && this.panelItems.length === 1;
}
protected get length(): number {
return this.panelItems.length;
}
constructor(
id: string,
private options: Partial<IViewsViewletOptions>,
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService
) {
super(id, telemetryService, themeService);
}
async create(parent: Builder): TPromise<void> {
super.create(parent);
const container = parent.getHTMLElement();
this.panelview = this._register(new PanelView(container));
}
getTitle(): string {
let title = Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet(this.getId()).name;
if (this.isSingleView) {
title += ': ' + this.panelItems[0].panel.title;
}
return title;
}
getActions(): IAction[] {
if (this.isSingleView) {
return this.panelItems[0].panel.getActions();
}
return [];
}
getSecondaryActions(): IAction[] {
if (this.isSingleView) {
return this.panelItems[0].panel.getSecondaryActions();
}
return [];
}
focus(): void {
super.focus();
if (this.lastFocusedPanel) {
this.lastFocusedPanel.focus();
} else if (this.panelItems.length > 0) {
this.panelItems[0].panel.focus();
}
}
layout(dimension: Dimension): void {
this.panelview.layout(dimension.height);
}
getOptimalWidth(): number {
const sizes = this.panelItems
.map(panelItem => panelItem.panel.getOptimalWidth() || 0);
return Math.max(...sizes);
}
addPanel(panel: ViewletPanel, size: number, index = this.panelItems.length - 1): void {
const disposables: IDisposable[] = [];
const onDidFocus = panel.onDidFocus(() => this.lastFocusedPanel = panel, null, disposables);
const styler = attachPanelStyler(panel, this.themeService);
const disposable = combinedDisposable([onDidFocus, styler]);
const panelItem: IViewletPanelItem = { panel, disposable };
this.panelItems.splice(index, 0, panelItem);
this.panelview.addPanel(panel, size, index);
this.updateViewHeaders();
this.updateTitleArea();
}
removePanel(panel: ViewletPanel): void {
const index = firstIndex(this.panelItems, i => i.panel === panel);
if (index === -1) {
return;
}
if (this.lastFocusedPanel === panel) {
this.lastFocusedPanel = undefined;
}
this.panelview.removePanel(panel);
const [panelItem] = this.panelItems.splice(index, 1);
panelItem.disposable.dispose();
this.updateViewHeaders();
this.updateTitleArea();
}
movePanel(from: ViewletPanel, to: ViewletPanel): void {
const fromIndex = firstIndex(this.panelItems, item => item.panel === from);
const toIndex = firstIndex(this.panelItems, item => item.panel === to);
if (fromIndex < 0 || fromIndex >= this.panelItems.length) {
return;
}
if (toIndex < 0 || toIndex >= this.panelItems.length) {
return;
}
const [panelItem] = this.panelItems.splice(fromIndex, 1);
this.panelItems.splice(toIndex < fromIndex ? toIndex : toIndex - 1, 0, panelItem);
this.panelview.movePanel(from, to);
}
resizePanel(panel: ViewletPanel, size: number): void {
this.panelview.resizePanel(panel, size);
}
private updateViewHeaders(): void {
if (this.isSingleView) {
this.panelItems[0].panel.headerVisible = false;
} else {
this.panelItems.forEach(i => i.panel.headerVisible = true);
}
}
dispose(): void {
super.dispose();
this.panelItems.forEach(i => i.disposable.dispose());
this.panelview.dispose();
}
}
\ No newline at end of file
......@@ -19,7 +19,7 @@
}
.scm-viewlet:not(.empty) .empty-message,
.scm-viewlet.empty .monaco-split-view{
.scm-viewlet.empty .monaco-panel-view {
display: none;
}
......@@ -27,6 +27,30 @@
height: 100%;
}
.scm-viewlet .monaco-list-row > .scm-provider {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.scm-viewlet .monaco-list-row > .scm-provider > input {
flex-shrink: 0;
}
.scm-viewlet .scm-provider > .count {
margin: 0 0.5em;
}
.scm-viewlet .scm-provider > .count.hidden {
display: none;
}
.scm-viewlet .scm-provider > .name > .type {
opacity: 0.7;
margin-left: 0.5em;
font-size: 0.9em;
}
.scm-viewlet .monaco-list-row {
padding: 0 12px 0 20px;
line-height: 22px;
......@@ -125,4 +149,8 @@
.scm-viewlet .scm-editor.scroll > .monaco-inputbox > .wrapper > textarea.input {
overflow-y: scroll;
}
.scm-viewlet .spacer {
flex: 1;
}
\ No newline at end of file
......@@ -6,6 +6,7 @@
'use strict';
import { localize } from 'vs/nls';
import { basename } from 'vs/base/common/paths';
import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { filterEvent, any as anyEvent } from 'vs/base/common/event';
import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm';
......@@ -139,9 +140,13 @@ export class StatusBarController implements IWorkbenchContribution {
this.statusBarDisposable.dispose();
const commands = repository.provider.statusBarCommands || [];
const label = repository.provider.rootUri
? `${basename(repository.provider.rootUri.fsPath)} (${repository.provider.label})`
: repository.provider.label;
const disposables = commands.map(c => this.statusbarService.addEntry({
text: c.title,
tooltip: `${repository.provider.label} - ${c.tooltip}`,
tooltip: `${label} - ${c.tooltip}`,
command: c.id,
arguments: c.arguments
}, MainThreadStatusBarAlignment.LEFT, 10000));
......
......@@ -60,6 +60,7 @@ export interface ISCMProvider extends IDisposable {
readonly resources: ISCMResourceGroup[];
readonly onDidChangeResources: Event<void>;
readonly rootUri?: URI;
readonly count?: number;
readonly commitTemplate?: string;
readonly onDidChangeCommitTemplate?: Event<string>;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册