Skip to content

Commit

Permalink
Feature: Open editors widget in navigator
Browse files Browse the repository at this point in the history
Signed-off-by: Kenneth Marut <[email protected]>
  • Loading branch information
kenneth-marut-work committed Jul 21, 2021
1 parent 61f229e commit f7d1a2d
Show file tree
Hide file tree
Showing 20 changed files with 1,037 additions and 34 deletions.
29 changes: 29 additions & 0 deletions packages/core/src/browser/shell/application-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,29 @@ export class ApplicationShell extends Widget {
}
}

saveTabs(tabBarOrArea: TabBar<Widget> | ApplicationShell.Area,
filter?: (title: Title<Widget>, index: number) => boolean): void {
if (tabBarOrArea === 'main') {
this.mainAreaTabBars.forEach(tb => this.saveTabs(tb, filter));
} else if (tabBarOrArea === 'bottom') {
this.bottomAreaTabBars.forEach(tb => this.saveTabs(tb, filter));
} else if (typeof tabBarOrArea === 'string') {
const tabBar = this.getTabBarFor(tabBarOrArea);
if (tabBar) {
this.saveTabs(tabBar, filter);
}
} else if (tabBarOrArea) {
const titles = toArray(tabBarOrArea.titles);
for (let i = 0; i < titles.length; i++) {
if (filter === undefined || filter(titles[i], i)) {
const widget = titles[i].owner;
const saveable = Saveable.get(widget);
saveable?.save();
}
}
}
}

async closeWidget(id: string, options?: ApplicationShell.CloseOptions): Promise<Widget | undefined> {
// TODO handle save for composite widgets, i.e. the preference widget has 2 editors
const stack = this.toTrackedStack(id);
Expand Down Expand Up @@ -1811,6 +1834,12 @@ export namespace ApplicationShell {
return area === 'left' || area === 'right' || area === 'bottom';
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isValidArea(area?: any): area is ApplicationShell.Area {
const areas = ['main', 'top', 'left', 'right', 'bottom'];
return (area !== undefined && typeof area === 'string' && areas.includes(area));
}

/**
* General options for the application shell. These are passed on construction and can be modified
* through dependency injection (`ApplicationShellOptions` symbol).
Expand Down
3 changes: 3 additions & 0 deletions packages/editor-preview/compile.tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
},
{
"path": "../editor/compile.tsconfig.json"
},
{
"path": "../navigator/compile.tsconfig.json"
}
]
}
3 changes: 2 additions & 1 deletion packages/editor-preview/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "Theia - Editor Preview Extension",
"dependencies": {
"@theia/core": "1.15.0",
"@theia/editor": "1.15.0"
"@theia/editor": "1.15.0",
"@theia/navigator": "1.15.0"
},
"publishConfig": {
"access": "public"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
********************************************************************************/

import '../../src/browser/style/index.css';
import { KeybindingContribution, WidgetFactory } from '@theia/core/lib/browser';
import { FrontendApplicationContribution, KeybindingContribution, WidgetFactory } from '@theia/core/lib/browser';
import { ContainerModule } from '@theia/core/shared/inversify';
import { bindEditorPreviewPreferences } from './editor-preview-preferences';
import { EditorPreviewManager } from './editor-preview-manager';
import { EditorManager } from '@theia/editor/lib/browser';
import { EditorPreviewWidgetFactory } from './editor-preview-widget-factory';
import { EditorPreviewContribution } from './editor-preview-contribution';
import { CommandContribution, MenuContribution } from '@theia/core/lib/common';
import { OpenEditorsTreeDecorator } from '@theia/navigator/lib/browser/open-editors-widget/navigator-open-editors-decorator-service';
import { EditorPreviewTreeDecorator } from './editor-preview-tree-decorator';

export default new ContainerModule((bind, unbind, isBound, rebind) => {

Expand All @@ -37,5 +39,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(KeybindingContribution).toService(EditorPreviewContribution);
bind(MenuContribution).toService(EditorPreviewContribution);

bind(EditorPreviewTreeDecorator).toSelf().inSingletonScope();
bind(OpenEditorsTreeDecorator).toService(EditorPreviewTreeDecorator);
bind(FrontendApplicationContribution).toService(EditorPreviewTreeDecorator);
bindEditorPreviewPreferences(bind);
});
107 changes: 107 additions & 0 deletions packages/editor-preview/src/browser/editor-preview-tree-decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/********************************************************************************
* Copyright (C) 2021 Ericsson and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable, inject } from '@theia/core/shared/inversify';
import { TreeDecorator, TreeDecoration } from '@theia/core/lib/browser/tree/tree-decorator';
import { Emitter } from '@theia/core/lib/common/event';
import { Tree } from '@theia/core/lib/browser/tree/tree';
import {
ApplicationShell,
DepthFirstTreeIterator,
FrontendApplication,
FrontendApplicationContribution,
NavigatableWidget,
Saveable,
Widget,
} from '@theia/core/lib/browser';
import { Disposable } from '@theia/core/lib/common';
import { OpenEditorNode } from '@theia/navigator/lib/browser/open-editors-widget/navigator-open-editors-tree-model';
import { EditorPreviewWidget } from './editor-preview-widget';
import { EditorPreviewManager } from './editor-preview-manager';

@injectable()
export class EditorPreviewTreeDecorator implements TreeDecorator, FrontendApplicationContribution {
@inject(EditorPreviewManager) protected readonly editorPreviewManager: EditorPreviewManager;
@inject(ApplicationShell) protected readonly shell: ApplicationShell;

readonly id = 'theia-open-editors-file-decorator';
protected decorationsMap = new Map<string, TreeDecoration.Data>();

protected readonly decorationsChangedEmitter = new Emitter();
readonly onDidChangeDecorations = this.decorationsChangedEmitter.event;
protected readonly toDisposeOnDirtyChanged = new Map<string, Disposable>();
protected readonly toDisposeOnPreviewPinned = new Map<string, Disposable>();

onDidInitializeLayout(app: FrontendApplication): void {
this.shell.onDidAddWidget(widget => this.registerEditorListeners(widget));
this.shell.onDidRemoveWidget(widget => this.unregisterEditorListeners(widget));
this.editorWidgets.forEach(widget => this.registerEditorListeners(widget));
}

protected registerEditorListeners(widget: Widget): void {
const saveable = Saveable.get(widget);
if (saveable) {
this.toDisposeOnDirtyChanged.set(widget.id, saveable.onDirtyChanged(() => {
this.fireDidChangeDecorations((tree: Tree) => this.collectDecorators(tree));
}));
}
if (widget instanceof EditorPreviewWidget) {
this.toDisposeOnPreviewPinned.set(widget.id, widget.onDidChangePreviewState(() => {
this.fireDidChangeDecorations((tree: Tree) => this.collectDecorators(tree));
this.toDisposeOnPreviewPinned.get(widget.id)?.dispose();
this.toDisposeOnDirtyChanged.delete(widget.id);
}));
}
}

protected unregisterEditorListeners(widget: Widget): void {
this.toDisposeOnDirtyChanged.get(widget.id)?.dispose();
this.toDisposeOnDirtyChanged.delete(widget.id);
this.toDisposeOnPreviewPinned.get(widget.id)?.dispose();
this.toDisposeOnPreviewPinned.delete(widget.id);
}

protected get editorWidgets(): NavigatableWidget[] {
return this.shell.widgets.filter((widget): widget is NavigatableWidget => NavigatableWidget.is(widget));
}

protected fireDidChangeDecorations(event: (tree: Tree) => Map<string, TreeDecoration.Data>): void {
this.decorationsChangedEmitter.fire(event);
}

decorations(tree: Tree): Map<string, TreeDecoration.Data> {
return this.collectDecorators(tree);
}

// Add workspace root as caption suffix and italicize if PreviewWidget
protected collectDecorators(tree: Tree): Map<string, TreeDecoration.Data> {
const result = new Map<string, TreeDecoration.Data>();
if (tree.root === undefined) {
return result;
}
for (const node of new DepthFirstTreeIterator(tree.root)) {
if (OpenEditorNode.is(node)) {
const { widget } = node;
const isPreviewWidget = widget instanceof EditorPreviewWidget && widget.isPreview;
const decorations: TreeDecoration.Data = {
fontData: { style: isPreviewWidget ? 'italic' : undefined }
};
result.set(node.id, decorations);
}
}
return result;
}
}
61 changes: 55 additions & 6 deletions packages/markers/src/browser/problem/problem-decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@ import { Marker } from '../../common/marker';
import { ProblemManager } from './problem-manager';
import { ProblemPreferences } from './problem-preferences';
import { ProblemUtils } from './problem-utils';
import { OpenEditorNode } from '@theia/navigator/lib/browser/open-editors-widget/navigator-open-editors-tree-model';
import { LabelProvider } from '@theia/core/lib/browser';
import { WorkspaceService } from '@theia/workspace/lib/browser';
import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration';

@injectable()
export class ProblemDecorator implements TreeDecorator {

@inject(ProblemPreferences)
protected problemPreferences: ProblemPreferences;
@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService;
@inject(LabelProvider) protected readonly labelProvider: LabelProvider;

readonly id = 'theia-problem-decorator';

Expand All @@ -50,6 +56,12 @@ export class ProblemDecorator implements TreeDecorator {
this.fireDidChangeDecorations(tree => this.collectDecorators(tree));
}
});
this.workspaceService.onWorkspaceChanged(() => {
this.fireDidChangeDecorations((tree: Tree) => this.collectDecorators(tree));
});
this.workspaceService.onWorkspaceLocationChanged(() => {
this.fireDidChangeDecorations((tree: Tree) => this.collectDecorators(tree));
});
}

async decorations(tree: Tree): Promise<Map<string, TreeDecoration.Data>> {
Expand All @@ -65,24 +77,61 @@ export class ProblemDecorator implements TreeDecorator {
}

protected collectDecorators(tree: Tree): Map<string, TreeDecoration.Data> {

const result = new Map();

const decorations = new Map<string, TreeDecoration.Data>();
// If the tree root is undefined or the preference for the decorations is disabled, return an empty result map.
if (tree.root === undefined || !this.problemPreferences['problems.decorations.enabled']) {
return result;
return decorations;
}
const markers = this.appendContainerMarkers(tree, this.collectMarkers(tree));
for (const node of new DepthFirstTreeIterator(tree.root)) {
const nodeUri = FileStatNode.getUri(node);
if (nodeUri) {
const marker = markers.get(nodeUri);
let decorator: TreeDecoration.Data | undefined;
if (marker) {
result.set(node.id, marker);
decorator = this.toDecorator(marker);
}
if (OpenEditorNode.is(node)) {
decorator = this.appendSuffixDecoration(node.uri, decorator);
}
if (decorator) {
decorations.set(node.id, decorator);
}
}
}
return new Map(Array.from(result.entries()).map(m => [m[0], this.toDecorator(m[1])] as [string, TreeDecoration.Data]));
return decorations;
}

protected appendSuffixDecoration(nodeURI: URI, existingDecorations?: TreeDecoration.Data): TreeDecoration.Data {
const workspaceAndPath = this.generateCaptionSuffix(nodeURI);
const color = existingDecorations?.fontData?.color;
const captionSuffix: WidgetDecoration.CaptionAffix = { data: workspaceAndPath };
if (color) {
Object.assign(captionSuffix, { fontData: { color } });
}
const suffixDecorations: TreeDecoration.Data = {
captionSuffixes: [captionSuffix]
};
const decorator = existingDecorations ?? {};
return Object.assign(decorator, suffixDecorations);
}

protected generateCaptionSuffix(nodeURI: URI): string {
const workspaceRoots = this.workspaceService.tryGetRoots();
const parentWorkspace = this.workspaceService.getWorkspaceRootUri(nodeURI);
let workspacePrefixString = '';
let separator = '';
let filePathString = '';
const nodeURIDir = nodeURI.parent;
if (parentWorkspace) {
const relativeDirFromWorkspace = parentWorkspace.relative(nodeURIDir);
workspacePrefixString = workspaceRoots.length > 1 ? this.labelProvider.getName(parentWorkspace) : '';
filePathString = relativeDirFromWorkspace?.toString() ?? '';
separator = filePathString && workspacePrefixString ? ' \u2022 ' : ''; // add a bullet point between workspace and path
} else {
workspacePrefixString = nodeURIDir.path.toString();
}
return `${workspacePrefixString}${separator}${filePathString}`;
}

protected appendContainerMarkers(tree: Tree, markers: Marker<Diagnostic>[]): Map<string, Marker<Diagnostic>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { TabBarDecorator } from '@theia/core/lib/browser/shell/tab-bar-decorator
import { bindProblemPreferences } from './problem-preferences';
import { MarkerTreeLabelProvider } from '../marker-tree-label-provider';
import { ProblemWidgetTabBarDecorator } from './problem-widget-tab-bar-decorator';
import { OpenEditorsTreeDecorator } from '@theia/navigator/lib/browser/open-editors-widget/navigator-open-editors-decorator-service';

export default new ContainerModule(bind => {
bindProblemPreferences(bind);
Expand All @@ -53,6 +54,7 @@ export default new ContainerModule(bind => {

bind(ProblemDecorator).toSelf().inSingletonScope();
bind(NavigatorTreeDecorator).toService(ProblemDecorator);
bind(OpenEditorsTreeDecorator).toService(ProblemDecorator);
bind(ProblemTabBarDecorator).toSelf().inSingletonScope();
bind(TabBarDecorator).toService(ProblemTabBarDecorator);

Expand Down
4 changes: 1 addition & 3 deletions packages/navigator/src/browser/navigator-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@
import { Container, interfaces } from '@theia/core/shared/inversify';
import { Tree, TreeModel, TreeProps, defaultTreeProps, TreeDecoratorService } from '@theia/core/lib/browser';
import { createFileTreeContainer, FileTree, FileTreeModel, FileTreeWidget } from '@theia/filesystem/lib/browser';
import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
import { FileNavigatorTree } from './navigator-tree';
import { FileNavigatorModel } from './navigator-model';
import { FileNavigatorWidget } from './navigator-widget';
import { NAVIGATOR_CONTEXT_MENU } from './navigator-contribution';
import { NavigatorDecoratorService, NavigatorTreeDecorator } from './navigator-decorator-service';
import { NavigatorDecoratorService } from './navigator-decorator-service';

export const FILE_NAVIGATOR_PROPS = <TreeProps>{
...defaultTreeProps,
Expand Down Expand Up @@ -50,7 +49,6 @@ export function createFileNavigatorContainer(parent: interfaces.Container): Cont

child.bind(NavigatorDecoratorService).toSelf().inSingletonScope();
child.rebind(TreeDecoratorService).toService(NavigatorDecoratorService);
bindContributionProvider(child, NavigatorTreeDecorator);

return child;
}
Expand Down
Loading

0 comments on commit f7d1a2d

Please sign in to comment.