提交 e35f72e5 编写于 作者: J Johannes Rieken

move breadcrumbs into editor group view

上级 ecaae775
......@@ -9,8 +9,6 @@
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
--item-hover-background: green;
--item-hover-color: inhert;
}
.monaco-breadcrumbs .monaco-breadcrumb-item {
......@@ -19,15 +17,7 @@
flex: 0 1 auto;
white-space: nowrap;
cursor: pointer;
}
.monaco-breadcrumbs:focus .monaco-breadcrumb-item.active {
color: pink;
}
.monaco-breadcrumbs .monaco-breadcrumb-item:hover {
color: var(--item-hover-color);
background-color: var(--item-hover-background);
align-self: center;
}
.monaco-breadcrumbs .monaco-breadcrumb-item-more {
......
......@@ -38,11 +38,12 @@ export class SimpleBreadcrumbsItem extends BreadcrumbsItem {
export class RenderedBreadcrumbsItem<E> extends BreadcrumbsItem {
readonly element: E;
private _disposables: IDisposable[] = [];
constructor(render: (element: E, container: HTMLDivElement, bucket: IDisposable[]) => any, element: E, more: boolean) {
super(document.createElement('div'), more);
this.element = element;
render(element, this.node as HTMLDivElement, this._disposables);
}
......@@ -57,7 +58,6 @@ export class BreadcrumbsWidget {
private readonly _disposables = new Array<IDisposable>();
private readonly _domNode: HTMLDivElement;
private readonly _scrollable: DomScrollableElement;
private _cachedWidth: number;
private readonly _onDidSelectItem = new Emitter<BreadcrumbsItem>();
private readonly _onDidChangeFocus = new Emitter<boolean>();
......@@ -100,10 +100,12 @@ export class BreadcrumbsWidget {
this._freeNodes.length = 0;
}
layout(width: number = this._cachedWidth): void {
if (typeof width === 'number') {
this._cachedWidth = width;
this._domNode.style.width = `${this._cachedWidth}px`;
layout(dim: dom.Dimension): void {
if (!dim) {
this._scrollable.scanDomNode();
} else {
this._domNode.style.width = `${dim.width}px`;
this._domNode.style.height = `${dim.height}px`;
this._scrollable.scanDomNode();
}
}
......@@ -124,7 +126,7 @@ export class BreadcrumbsWidget {
private _focus(nth: number): boolean {
if (this._focusedItemIdx !== -1) {
dom.removeClass(this._nodes[this._focusedItemIdx], 'active');
dom.removeClass(this._nodes[this._focusedItemIdx], 'focused');
this._focusedItemIdx = -1;
}
if (nth < 0 || nth >= this._nodes.length) {
......@@ -132,7 +134,7 @@ export class BreadcrumbsWidget {
}
this._focusedItemIdx = nth;
let node = this._nodes[this._focusedItemIdx];
dom.addClass(node, 'active');
dom.addClass(node, 'focused');
this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft });
return true;
}
......@@ -177,7 +179,7 @@ export class BreadcrumbsWidget {
this._domNode.appendChild(node);
this._nodes[start] = node;
}
this.layout();
this.layout(undefined);
this._focus(this._nodes.length - 1);
}
......
......@@ -20,6 +20,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export const EDITOR_TITLE_HEIGHT = 35;
export const BREAD_CRUMPS_HEIGHT = 30;
export const EDITOR_MIN_DIMENSIONS = new Dimension(220, 70);
export const EDITOR_MAX_DIMENSIONS = new Dimension(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
......@@ -159,4 +160,4 @@ export interface EditorGroupsServiceImpl extends IEditorGroupsService {
* A promise that resolves when groups have been restored.
*/
readonly whenRestored: TPromise<void>;
}
\ 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!./media/editorbreadcrumbs';
import * as dom from 'vs/base/browser/dom';
import { BreadcrumbsWidget, RenderedBreadcrumbsItem } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import * as paths from 'vs/base/common/paths';
import URI from 'vs/base/common/uri';
import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { FileLabel } from 'vs/workbench/browser/labels';
import { EditorInput } from 'vs/workbench/common/editor';
import { IEditorBreadcrumbs, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { ITreeConfiguration, IDataSource, ITree, IRenderer, ISelectionEvent } from 'vs/base/parts/tree/browser/tree';
import { TPromise } from 'vs/base/common/winjs.base';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { debounceEvent, Emitter, Event } from 'vs/base/common/event';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { isEqual } from 'vs/base/common/resources';
interface FileElement {
name: string;
uri: URI;
kind: FileKind;
}
export class EditorBreadcrumbs implements IEditorBreadcrumbs {
static CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false);
static CK_BreadcrumbsFocused = new RawContextKey('breadcrumbsFocused', false);
private readonly _disposables = new Array<IDisposable>();
private readonly _domNode: HTMLDivElement;
private readonly _widget: BreadcrumbsWidget;
private readonly _ckBreadcrumbsVisible: IContextKey<boolean>;
private readonly _ckBreadcrumbsFocused: IContextKey<boolean>;
constructor(
container: HTMLElement,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IFileService private readonly _fileService: IFileService,
@IContextViewService private readonly _contextViewService: IContextViewService,
@IEditorService private readonly _editorService: IEditorService,
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
this._domNode = document.createElement('div');
this._domNode.className = 'editor-breadcrumbs';
this._widget = new BreadcrumbsWidget(this._domNode);
this._widget.onDidSelectItem(this._onDidSelectItem, this, this._disposables);
this._widget.onDidChangeFocus(val => this._ckBreadcrumbsFocused.set(val), undefined, this._disposables);
container.appendChild(this._domNode);
this._ckBreadcrumbsVisible = EditorBreadcrumbs.CK_BreadcrumbsVisible.bindTo(this._contextKeyService);
this._ckBreadcrumbsFocused = EditorBreadcrumbs.CK_BreadcrumbsFocused.bindTo(this._contextKeyService);
}
dispose(): void {
dispose(this._disposables);
this._widget.dispose();
this._ckBreadcrumbsVisible.reset();
}
layout(dim: dom.Dimension): void {
this._domNode.style.width = `${dim.width}px`;
this._domNode.style.height = `${dim.height}px`;
this._widget.layout(dim);
}
setActive(value: boolean): void {
dom.toggleClass(this._domNode, 'active', value);
}
openEditor(input: EditorInput): void {
let uri = input.getResource();
if (!uri || !this._fileService.canHandleResource(uri)) {
return this.closeEditor(undefined);
}
this._ckBreadcrumbsVisible.set(true);
dom.toggleClass(this._domNode, 'hidden', false);
const render = (element: FileElement, target: HTMLElement, disposables: IDisposable[]) => {
let label = this._instantiationService.createInstance(FileLabel, target, {});
label.setFile(element.uri, { fileKind: element.kind, hidePath: true });
disposables.push(label);
};
let items: RenderedBreadcrumbsItem<FileElement>[] = [];
let workspace = this._workspaceService.getWorkspaceFolder(uri);
let path = uri.path;
while (true) {
if (workspace && isEqual(workspace.uri, uri, true) || path === '/') {
break;
}
let first = items.length === 0;
let name = paths.basename(path);
uri = uri.with({ path });
path = paths.dirname(path);
items.unshift(new RenderedBreadcrumbsItem<FileElement>(
render,
{ name, uri, kind: first ? FileKind.FILE : FileKind.FOLDER },
!first
));
}
this._widget.replace(undefined, items);
}
closeEditor(input: EditorInput): void {
this._ckBreadcrumbsVisible.set(false);
dom.toggleClass(this._domNode, 'hidden', true);
}
focus(): void {
this._widget.focus();
}
focusNext(): void {
this._widget.focusNext();
}
focusPrev(): void {
this._widget.focusPrev();
}
select(): void {
this._widget.select();
}
private _onDidSelectItem(item: RenderedBreadcrumbsItem<FileElement>): void {
this._contextViewService.showContextView({
getAnchor() {
return item.node;
},
render: (container: HTMLElement) => {
let res = this._instantiationService.createInstance(BreadcrumbsFilePicker, container);
res.layout({ width: 250, height: 300 });
res.setInput(item.element.uri.with({ path: paths.dirname(item.element.uri.path) }));
res.onDidPickElement(data => {
this._contextViewService.hideContextView();
if (!data) {
return;
}
if (URI.isUri(data)) {
this._editorService.openEditor({ resource: data });
}
});
return res;
},
});
}
}
export abstract class BreadcrumbsPicker {
readonly focus: dom.IFocusTracker;
protected readonly _onDidPickElement = new Emitter<any>();
readonly onDidPickElement: Event<any> = this._onDidPickElement.event;
protected readonly _disposables = new Array<IDisposable>();
protected readonly _domNode: HTMLDivElement;
protected readonly _tree: WorkbenchTree;
constructor(
container: HTMLElement,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IThemeService protected readonly _themeService: IThemeService,
) {
this._domNode = document.createElement('div');
// this._domNode.style.background = this._themeService.getTheme().getColor(colors.progressBarBackground).toString();
container.appendChild(this._domNode);
this._tree = this._instantiationService.createInstance(WorkbenchTree, this._domNode, this._completeTreeConfiguration({ dataSource: undefined }), {});
debounceEvent(this._tree.onDidChangeSelection, (last, cur) => cur, 0)(this._onDidChangeSelection, this, this._disposables);
this.focus = dom.trackFocus(this._domNode);
this.focus.onDidBlur(_ => this._onDidPickElement.fire(undefined), undefined, this._disposables);
}
dispose(): void {
dispose(this._disposables);
this._onDidPickElement.dispose();
this._tree.dispose();
this.focus.dispose();
}
layout(dim: dom.Dimension) {
this._domNode.style.width = `${dim.width}px`;
this._domNode.style.height = `${dim.height}px`;
this._tree.layout(dim.height, dim.width);
}
protected abstract _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration;
protected abstract _onDidChangeSelection(e: any): void;
}
export class FileDataSource implements IDataSource {
private readonly _parents = new WeakMap<IFileStat, IFileStat>();
constructor(
@IFileService private readonly _fileService: IFileService,
) { }
getId(tree: ITree, element: IFileStat | URI): string {
return URI.isUri(element) ? element.toString() : element.resource.toString();
}
hasChildren(tree: ITree, element: IFileStat | URI): boolean {
return URI.isUri(element) || element.isDirectory;
}
getChildren(tree: ITree, element: IFileStat | URI): TPromise<IFileStat[]> {
return this._fileService.resolveFile(
URI.isUri(element) ? element : element.resource
).then(stat => {
for (const child of stat.children) {
this._parents.set(child, stat);
}
return stat.children;
});
}
getParent(tree: ITree, element: IFileStat | URI): TPromise<IFileStat> {
return TPromise.as(URI.isUri(element) ? undefined : this._parents.get(element));
}
}
export class FileRenderer implements IRenderer {
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) { }
getHeight(tree: ITree, element: any): number {
return 22;
}
getTemplateId(tree: ITree, element: any): string {
return 'FileStat';
}
renderTemplate(tree: ITree, templateId: string, container: HTMLElement) {
return this._instantiationService.createInstance(FileLabel, container, {});
}
renderElement(tree: ITree, element: IFileStat, templateId: string, templateData: FileLabel): void {
templateData.setFile(element.resource, { hidePath: true, fileKind: element.isDirectory ? FileKind.FOLDER : FileKind.FILE });
}
disposeTemplate(tree: ITree, templateId: string, templateData: FileLabel): void {
templateData.dispose();
}
}
export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
protected _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration {
config.dataSource = this._instantiationService.createInstance(FileDataSource);
config.renderer = this._instantiationService.createInstance(FileRenderer);
return config;
}
setInput(resource: URI): void {
this._tree.domFocus();
this._tree.setInput(resource);
}
protected _onDidChangeSelection(e: ISelectionEvent): void {
let [first] = e.selection;
let stat = first as IFileStat;
if (stat && !stat.isDirectory) {
this._onDidPickElement.fire(stat.resource);
}
}
}
//#region commands
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focus',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_DOT,
when: EditorBreadcrumbs.CK_BreadcrumbsVisible,
handler(accessor) {
let groups = accessor.get(IEditorGroupsService);
groups.activeGroup.breadcrumbs.focus();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focusNext',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
primary: KeyCode.RightArrow,
when: ContextKeyExpr.and(EditorBreadcrumbs.CK_BreadcrumbsVisible, EditorBreadcrumbs.CK_BreadcrumbsFocused),
handler(accessor) {
let groups = accessor.get(IEditorGroupsService);
groups.activeGroup.breadcrumbs.focusNext();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focusPrevious',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
primary: KeyCode.LeftArrow,
when: ContextKeyExpr.and(EditorBreadcrumbs.CK_BreadcrumbsVisible, EditorBreadcrumbs.CK_BreadcrumbsFocused),
handler(accessor) {
let groups = accessor.get(IEditorGroupsService);
groups.activeGroup.breadcrumbs.focusPrev();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.selectFocused',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
primary: KeyCode.Enter,
secondary: [KeyCode.UpArrow, KeyCode.Space],
when: ContextKeyExpr.and(EditorBreadcrumbs.CK_BreadcrumbsVisible, EditorBreadcrumbs.CK_BreadcrumbsFocused),
handler(accessor) {
let groups = accessor.get(IEditorGroupsService);
groups.activeGroup.breadcrumbs.select();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.selectEditor',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
primary: KeyCode.Escape,
when: ContextKeyExpr.and(EditorBreadcrumbs.CK_BreadcrumbsVisible, EditorBreadcrumbs.CK_BreadcrumbsFocused),
handler(accessor) {
let groups = accessor.get(IEditorGroupsService);
groups.activeGroup.activeControl.focus();
}
});
//#endregion
......@@ -19,7 +19,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER } from 'vs/workbench/common/theme';
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService';
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder, IEditorBreadcrumbs } from 'vs/workbench/services/group/common/editorGroupsService';
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl';
import { IProgressService } from 'vs/platform/progress/common/progress';
......@@ -34,7 +34,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { RunOnceWorker } from 'vs/base/common/async';
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, EDITOR_TITLE_HEIGHT, EDITOR_MIN_DIMENSIONS, EDITOR_MAX_DIMENSIONS, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, EDITOR_TITLE_HEIGHT, EDITOR_MIN_DIMENSIONS, EDITOR_MAX_DIMENSIONS, getActiveTextEditorOptions, IEditorOpeningEvent, BREAD_CRUMPS_HEIGHT } from 'vs/workbench/browser/parts/editor/editor';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { join } from 'vs/base/common/paths';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
......@@ -46,6 +46,7 @@ import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { EditorBreadcrumbs } from 'vs/workbench/browser/parts/editor/editorBreadcrumbs';
export class EditorGroupView extends Themable implements IEditorGroupView {
......@@ -104,6 +105,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private titleContainer: HTMLElement;
private titleAreaControl: TitleControl;
private breadcrumbsContainer: HTMLElement;
private breadcrumbsControl: EditorBreadcrumbs;
private progressBar: ProgressBar;
private editorContainer: HTMLElement;
......@@ -190,6 +194,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Title control
this.createTitleAreaControl();
// Breadcrumbs container
this.breadcrumbsContainer = document.createElement('div');
addClass(this.breadcrumbsContainer, 'editor-breadcrumbs');
this.element.appendChild(this.breadcrumbsContainer);
// Breadcrumbs control
this.createEditorBreadcrumbs();
// Editor container
this.editorContainer = document.createElement('div');
addClass(this.editorContainer, 'editor-container');
......@@ -396,6 +408,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
private createEditorBreadcrumbs(): void {
if (this.breadcrumbsControl) {
this.breadcrumbsControl.dispose();
clearNode(this.breadcrumbsContainer);
}
this.breadcrumbsControl = this.scopedInstantiationService.createInstance(EditorBreadcrumbs, this.breadcrumbsContainer);
}
private restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): TPromise<void> {
if (this._group.count === 0) {
return TPromise.as(void 0); // nothing to show
......@@ -592,6 +614,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this._label;
}
get breadcrumbs(): IEditorBreadcrumbs {
return this.breadcrumbsControl;
}
get disposed(): boolean {
return this._disposed;
}
......@@ -617,6 +643,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Update title control
this.titleAreaControl.setActive(isActive);
this.breadcrumbsControl.setActive(isActive);
// Update styles
this.updateStyles();
......@@ -788,6 +816,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Show in title control after editor control because some actions depend on it
this.titleAreaControl.openEditor(editor);
this.breadcrumbsControl.openEditor(editor);
return openEditorPromise;
}
......@@ -955,8 +985,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.doCloseInactiveEditor(editor);
}
// Forward to title control
// Forward to title control & breadcrumbs
this.titleAreaControl.closeEditor(editor);
this.breadcrumbsControl.closeEditor(editor);
}
private doCloseActiveEditor(focusNext = this.accessor.activeGroup === this, fromError?: boolean): void {
......@@ -1342,7 +1373,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Forward to controls
this.titleAreaControl.layout(new Dimension(this.dimension.width, EDITOR_TITLE_HEIGHT));
this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - EDITOR_TITLE_HEIGHT));
this.breadcrumbsControl.layout(new Dimension(this.dimension.width, BREAD_CRUMPS_HEIGHT));
this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - (EDITOR_TITLE_HEIGHT + BREAD_CRUMPS_HEIGHT)));
}
toJSON(): ISerializedEditorGroup {
......@@ -1362,6 +1394,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.titleAreaControl.dispose();
this.breadcrumbsControl.dispose();
super.dispose();
}
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench>.part.editor>.content .editor-breadcrumbs .monaco-breadcrumbs {
--color-item-focused: pink;
}
.monaco-workbench>.part.editor>.content .editor-breadcrumbs .monaco-breadcrumbs:focus .monaco-breadcrumb-item.focused {
color: var(--color-item-focused);
}
/*---------------------------------------------------------------------------------------------
* 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 { Dimension, trackFocus, IFocusTracker } from 'vs/base/browser/dom';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITreeConfiguration, IDataSource, ITree, IRenderer, ISelectionEvent } from 'vs/base/parts/tree/browser/tree';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
import { IFileService, IFileStat, FileKind } from 'vs/platform/files/common/files';
import { FileLabel } from 'vs/workbench/browser/labels';
import URI from 'vs/base/common/uri';
import { Emitter, Event, debounceEvent } from 'vs/base/common/event';
import { IThemeService } from 'vs/platform/theme/common/themeService';
export abstract class BreadcrumbsPicker {
readonly focus: IFocusTracker;
protected readonly _onDidPickElement = new Emitter<any>();
readonly onDidPickElement: Event<any> = this._onDidPickElement.event;
protected readonly _disposables = new Array<IDisposable>();
protected readonly _domNode: HTMLDivElement;
protected readonly _tree: WorkbenchTree;
constructor(
container: HTMLElement,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IThemeService protected readonly _themeService: IThemeService,
) {
this._domNode = document.createElement('div');
// this._domNode.style.background = this._themeService.getTheme().getColor(colors.progressBarBackground).toString();
container.appendChild(this._domNode);
this._tree = this._instantiationService.createInstance(WorkbenchTree, this._domNode, this._completeTreeConfiguration({ dataSource: undefined }), {});
debounceEvent(this._tree.onDidChangeSelection, (last, cur) => cur, 0)(this._onDidChangeSelection, this, this._disposables);
this.focus = trackFocus(this._domNode);
this.focus.onDidBlur(_ => this._onDidPickElement.fire(undefined), undefined, this._disposables);
}
dispose(): void {
dispose(this._disposables);
this._onDidPickElement.dispose();
this._tree.dispose();
this.focus.dispose();
}
layout(dim: Dimension) {
this._domNode.style.width = `${dim.width}px`;
this._domNode.style.height = `${dim.height}px`;
this._tree.layout(dim.height, dim.width);
}
protected abstract _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration;
protected abstract _onDidChangeSelection(e: any): void;
}
export class FileDataSource implements IDataSource {
private readonly _parents = new WeakMap<IFileStat, IFileStat>();
constructor(
@IFileService private readonly _fileService: IFileService,
) { }
getId(tree: ITree, element: IFileStat | URI): string {
return URI.isUri(element) ? element.toString() : element.resource.toString();
}
hasChildren(tree: ITree, element: IFileStat | URI): boolean {
return URI.isUri(element) || element.isDirectory;
}
getChildren(tree: ITree, element: IFileStat | URI): TPromise<IFileStat[]> {
return this._fileService.resolveFile(
URI.isUri(element) ? element : element.resource
).then(stat => {
for (const child of stat.children) {
this._parents.set(child, stat);
}
return stat.children;
});
}
getParent(tree: ITree, element: IFileStat | URI): TPromise<IFileStat> {
return TPromise.as(URI.isUri(element) ? undefined : this._parents.get(element));
}
}
export class FileRenderer implements IRenderer {
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) { }
getHeight(tree: ITree, element: any): number {
return 22;
}
getTemplateId(tree: ITree, element: any): string {
return 'FileStat';
}
renderTemplate(tree: ITree, templateId: string, container: HTMLElement) {
return this._instantiationService.createInstance(FileLabel, container, {});
}
renderElement(tree: ITree, element: IFileStat, templateId: string, templateData: FileLabel): void {
templateData.setFile(element.resource, { hidePath: true, fileKind: element.isDirectory ? FileKind.FOLDER : FileKind.FILE });
}
disposeTemplate(tree: ITree, templateId: string, templateData: FileLabel): void {
templateData.dispose();
}
}
export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
protected _completeTreeConfiguration(config: ITreeConfiguration): ITreeConfiguration {
config.dataSource = this._instantiationService.createInstance(FileDataSource);
config.renderer = this._instantiationService.createInstance(FileRenderer);
return config;
}
setInput(resource: URI): void {
this._tree.domFocus();
this._tree.setInput(resource);
}
protected _onDidChangeSelection(e: ISelectionEvent): void {
let [first] = e.selection;
let stat = first as IFileStat;
if (stat && !stat.isDirectory) {
this._onDidPickElement.fire(stat.resource);
}
}
}
......@@ -7,7 +7,7 @@
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { BreadcrumbsWidget, RenderedBreadcrumbsItem } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsWidget';
import { BreadcrumbsWidget, RenderedBreadcrumbsItem } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
......@@ -16,6 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { FileKind } from 'vs/platform/files/common/files';
import { FileLabel } from 'vs/workbench/browser/labels';
import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { BreadcrumbsFilePicker } from 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbsPicker';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
class Widget implements IOverlayWidget {
......@@ -59,6 +62,11 @@ class BreadcrumbsUpdateEvent {
}
}
interface FileElement {
name: string;
uri: URI;
kind: FileKind;
}
export class EditorBreadcrumbs implements IEditorContribution {
......@@ -78,6 +86,8 @@ export class EditorBreadcrumbs implements IEditorContribution {
readonly editor: ICodeEditor,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IContextViewService private readonly _contextViewService: IContextViewService,
@IEditorService private readonly _editorService: IEditorService,
) {
this._widget = new Widget(() => this._onWidgetReady());
this._update = new BreadcrumbsUpdateEvent(this.editor);
......@@ -99,6 +109,7 @@ export class EditorBreadcrumbs implements IEditorContribution {
this._disposables.push(this._widget.breadcrumb.onDidChangeFocus(value => this._ckFocused.set(value)));
this._disposables.push(this._widget.breadcrumb.onDidSelectItem(this._onDidSelectItem, this));
this._update.event(this._onUpdate, this, this._disposables);
this._onUpdate();
}
private _onUpdate(): void {
......@@ -107,26 +118,20 @@ export class EditorBreadcrumbs implements IEditorContribution {
}
let { uri } = this.editor.getModel();
interface Element {
name: string;
uri: URI;
kind: FileKind;
}
const render = (element: Element, target: HTMLElement, disposables: IDisposable[]) => {
const render = (element: FileElement, target: HTMLElement, disposables: IDisposable[]) => {
let label = this._instantiationService.createInstance(FileLabel, target, {});
label.setFile(element.uri, { fileKind: element.kind, hidePath: true });
disposables.push(label);
};
let items: RenderedBreadcrumbsItem<Element>[] = [];
let items: RenderedBreadcrumbsItem<FileElement>[] = [];
let path = uri.path;
while (path !== '/') {
let first = items.length === 0;
let name = posix.basename(path);
uri = uri.with({ path });
path = posix.dirname(path);
items.unshift(new RenderedBreadcrumbsItem<Element>(
items.unshift(new RenderedBreadcrumbsItem<FileElement>(
render,
{ name, uri, kind: first ? FileKind.FILE : FileKind.FOLDER },
!first
......@@ -136,8 +141,28 @@ export class EditorBreadcrumbs implements IEditorContribution {
this._widget.breadcrumb.replace(undefined, items);
}
private _onDidSelectItem(item: RenderedBreadcrumbsItem<any>): void {
console.log(item);
private _onDidSelectItem(item: RenderedBreadcrumbsItem<FileElement>): void {
this._contextViewService.showContextView({
getAnchor() {
return item.node;
},
render: (container: HTMLElement) => {
let res = this._instantiationService.createInstance(BreadcrumbsFilePicker, container);
res.layout({ width: 300, height: 450 });
res.setInput(item.element.uri.with({ path: posix.dirname(item.element.uri.path) }));
res.onDidPickElement(data => {
this._contextViewService.hideContextView();
if (!data) {
return;
}
if (URI.isUri(data)) {
this._editorService.openEditor({ resource: data });
}
});
return res;
},
});
}
focus(): void {
......
......@@ -312,6 +312,13 @@ export interface IGroupChangeEvent {
editorIndex?: number;
}
export interface IEditorBreadcrumbs {
focus(): void;
focusNext(): void;
focusPrev(): void;
select(): void;
}
export interface IEditorGroup {
/**
......@@ -332,6 +339,11 @@ export interface IEditorGroup {
*/
readonly label: string;
/**
*
*/
readonly breadcrumbs: IEditorBreadcrumbs;
/**
* The active control is the currently visible control of the group.
*/
......@@ -476,4 +488,4 @@ export interface IEditorGroup {
* Invoke a function in the context of the services of this group.
*/
invokeWithinContext<T>(fn: (accessor: ServicesAccessor) => T): T;
}
\ No newline at end of file
}
......@@ -94,8 +94,6 @@ import 'vs/workbench/electron-browser/workbench';
import 'vs/workbench/parts/relauncher/electron-browser/relauncher.contribution';
import 'vs/workbench/parts/breadcrumbs/electron-browser/breadcrumbs.contribution';
import 'vs/workbench/parts/tasks/electron-browser/task.contribution';
import 'vs/workbench/parts/emmet/browser/emmet.browser.contribution';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册