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 Apr 5, 2021
1 parent d3cc96f commit e4cc5d3
Show file tree
Hide file tree
Showing 13 changed files with 696 additions and 7 deletions.
6 changes: 6 additions & 0 deletions packages/navigator/compile.tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
},
{
"path": "../workspace/compile.tsconfig.json"
},
{
"path": "../editor-preview/compile.tsconfig.json"
},
{
"path": "../editor/compile.tsconfig.json"
}
]
}
2 changes: 2 additions & 0 deletions packages/navigator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"description": "Theia - Navigator Extension",
"dependencies": {
"@theia/core": "1.12.0",
"@theia/editor": "1.12.0",
"@theia/editor-preview": "1.12.0",
"@theia/filesystem": "1.12.0",
"@theia/workspace": "1.12.0",
"minimatch": "^3.0.4"
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
113 changes: 111 additions & 2 deletions packages/navigator/src/browser/navigator-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { SelectionService } from '@theia/core/lib/common/selection-service';
import { UriAwareCommandHandler } from '@theia/core/lib/common/uri-command-handler';
import URI from '@theia/core/lib/common/uri';
import { EditorWidget } from '@theia/editor/lib/browser';
import { OpenEditorsWidget } from './open-editors-widget/navigator-open-editors-widget';
import { OpenEditorsContextMenu } from './open-editors-widget/navigator-open-editors-menus';
import { EditorPreviewWidget } from '@theia/editor-preview/lib/browser';

export namespace FileNavigatorCommands {
export const REVEAL_IN_NAVIGATOR: Command = {
Expand Down Expand Up @@ -106,7 +110,8 @@ export namespace FileNavigatorCommands {
label: 'Focus on Files Explorer'
};
export const COPY_RELATIVE_FILE_PATH: Command = {
id: 'navigator.copyRelativeFilePath'
id: 'navigator.copyRelativeFilePath',
label: 'Copy Relative Path'
};
export const OPEN: Command = {
id: 'navigator.open',
Expand Down Expand Up @@ -158,6 +163,28 @@ export namespace NavigatorContextMenu {

export const FILE_NAVIGATOR_TOGGLE_COMMAND_ID = 'fileNavigator:toggle';

export namespace OpenEditorsCommands {
export const CLOSE_ALL_TABS_FROM_TOOLBAR: Command = {
id: 'navigator.close.all.editors',
category: 'File',
label: 'Close All Editors',
iconClass: 'codicon codicon-close-all'
};

export const SAVE_ALL_TABS: Command = {
id: 'navigator.save.all.editors',
category: 'File',
label: 'Save All Editor',
iconClass: 'codicon codicon-save-all'
};

export const SAVE_EDITOR: Command = {
id: 'navigator.save.editor',
category: 'File',
label: 'Save'
};
}

@injectable()
export class FileNavigatorContribution extends AbstractViewContribution<FileNavigatorWidget> implements FrontendApplicationContribution, TabBarToolbarContribution {

Expand Down Expand Up @@ -358,6 +385,26 @@ export class FileNavigatorContribution extends AbstractViewContribution<FileNavi
});
}
});
registry.registerCommand(OpenEditorsCommands.CLOSE_ALL_TABS_FROM_TOOLBAR, {
execute: widget => this.withOpenEditorsWidget(widget, async () => {
this.shell.widgets.forEach(w => {
if (w instanceof EditorWidget || w instanceof EditorPreviewWidget) {
w.close();
}
});
}),
isEnabled: widget => this.withOpenEditorsWidget(widget, () => !!this.editorWidgets.length),
isVisible: widget => this.withOpenEditorsWidget(widget, () => !!this.editorWidgets.length)
});
registry.registerCommand(OpenEditorsCommands.SAVE_ALL_TABS, {
execute: widget => this.withOpenEditorsWidget(widget, () => this.editorWidgets.forEach(editor => editor.saveable.save())),
isEnabled: widget => this.withOpenEditorsWidget(widget, () => !!this.editorWidgets.length),
isVisible: widget => this.withOpenEditorsWidget(widget, () => !!this.editorWidgets.length)
});
}

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

protected getSelectedFileNodes(): FileNode[] {
Expand All @@ -371,6 +418,13 @@ export class FileNavigatorContribution extends AbstractViewContribution<FileNavi
return false;
}

protected withOpenEditorsWidget<T>(widget: Widget, cb: (navigator: OpenEditorsWidget) => T): T | false {
if (widget instanceof OpenEditorsWidget && widget.id === OpenEditorsWidget.ID) {
return cb(widget);
}
return false;
}

registerMenus(registry: MenuModelRegistry): void {
super.registerMenus(registry);
registry.registerMenuAction(SHELL_TABBAR_CONTEXT_MENU, {
Expand Down Expand Up @@ -413,7 +467,7 @@ export class FileNavigatorContribution extends AbstractViewContribution<FileNavi
});
registry.registerMenuAction(NavigatorContextMenu.CLIPBOARD, {
commandId: FileNavigatorCommands.COPY_RELATIVE_FILE_PATH.id,
label: 'Copy Relative Path',
label: FileNavigatorCommands.COPY_RELATIVE_FILE_PATH.label,
order: 'd'
});
registry.registerMenuAction(NavigatorContextMenu.CLIPBOARD, {
Expand Down Expand Up @@ -464,6 +518,48 @@ export class FileNavigatorContribution extends AbstractViewContribution<FileNavi
commandId: NavigatorDiffCommands.COMPARE_SECOND.id,
order: 'zb'
});

// Open Editors Widget Menu Items
registry.registerMenuAction(OpenEditorsContextMenu.CLIPBOARD, {
commandId: CommonCommands.COPY_PATH.id,
label: CommonCommands.COPY_PATH.label,
order: 'a'
});
registry.registerMenuAction(OpenEditorsContextMenu.CLIPBOARD, {
commandId: FileNavigatorCommands.COPY_RELATIVE_FILE_PATH.id,
label: FileNavigatorCommands.COPY_RELATIVE_FILE_PATH.label,
order: 'b'
});
registry.registerMenuAction(OpenEditorsContextMenu.SAVE, {
commandId: CommonCommands.SAVE.id,
label: CommonCommands.SAVE.label,
order: 'a'
});

registry.registerMenuAction(OpenEditorsContextMenu.COMPARE, {
commandId: NavigatorDiffCommands.COMPARE_FIRST.id,
order: 'a'
});
registry.registerMenuAction(OpenEditorsContextMenu.COMPARE, {
commandId: NavigatorDiffCommands.COMPARE_SECOND.id,
order: 'b'
});

registry.registerMenuAction(OpenEditorsContextMenu.MODIFICATION, {
commandId: CommonCommands.CLOSE_TAB.id,
label: 'Close',
order: 'a'
});
registry.registerMenuAction(OpenEditorsContextMenu.MODIFICATION, {
commandId: CommonCommands.CLOSE_OTHER_TABS.id,
label: 'Close Others',
order: 'b'
});
registry.registerMenuAction(OpenEditorsContextMenu.MODIFICATION, {
commandId: CommonCommands.CLOSE_ALL_MAIN_TABS.id,
label: 'Close All',
order: 'c'
});
}

registerKeybindings(registry: KeybindingRegistry): void {
Expand Down Expand Up @@ -535,6 +631,19 @@ export class FileNavigatorContribution extends AbstractViewContribution<FileNavi
tooltip: WorkspaceCommands.ADD_FOLDER.label,
group: NavigatorMoreToolbarGroups.WORKSPACE,
});

toolbarRegistry.registerItem({
id: OpenEditorsCommands.SAVE_ALL_TABS.id,
command: OpenEditorsCommands.SAVE_ALL_TABS.id,
tooltip: 'Save All Editors',
priority: 0,
});
toolbarRegistry.registerItem({
id: OpenEditorsCommands.CLOSE_ALL_TABS_FROM_TOOLBAR.id,
command: OpenEditorsCommands.CLOSE_ALL_TABS_FROM_TOOLBAR.id,
tooltip: 'Close All Editors',
priority: 1,
});
}

/**
Expand Down
16 changes: 16 additions & 0 deletions packages/navigator/src/browser/navigator-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
********************************************************************************/

import '../../src/browser/style/index.css';
import '../../src/browser/open-editors-widget/open-editors.css';

import { ContainerModule } from '@theia/core/shared/inversify';
import {
Expand All @@ -36,6 +37,11 @@ import { NavigatorLayoutVersion3Migration } from './navigator-layout-migrations'
import { NavigatorTabBarDecorator } from './navigator-tab-bar-decorator';
import { TabBarDecorator } from '@theia/core/lib/browser/shell/tab-bar-decorator';
import { NavigatorWidgetFactory } from './navigator-widget-factory';
import { bindContributionProvider } from '@theia/core/lib/common';
import { NavigatorTreeDecorator } from '.';
import { OpenEditorsFileDecorator } from './open-editors-widget/navigator-open-editors-file-decorator';
import { OpenEditorsTreeDecorator } from './open-editors-widget/navigator-open-editors-decorator-service';
import { OpenEditorsWidget } from './open-editors-widget/navigator-open-editors-widget';

export default new ContainerModule(bind => {
bindFileNavigatorPreferences(bind);
Expand All @@ -56,6 +62,16 @@ export default new ContainerModule(bind => {
id: FILE_NAVIGATOR_ID,
createWidget: () => container.get(FileNavigatorWidget)
})).inSingletonScope();
bindContributionProvider(bind, NavigatorTreeDecorator);
bindContributionProvider(bind, OpenEditorsTreeDecorator);
bind(OpenEditorsTreeDecorator).to(OpenEditorsFileDecorator);

bind(OpenEditorsWidget).toSelf().inSingletonScope();
bind(WidgetFactory).toDynamicValue(({ container }) => ({
id: OpenEditorsWidget.ID,
createWidget: () => OpenEditorsWidget.createWidget(container)
})).inSingletonScope();

bind(NavigatorWidgetFactory).toSelf().inSingletonScope();
bind(WidgetFactory).toService(NavigatorWidgetFactory);
bind(ApplicationShellLayoutMigration).to(NavigatorLayoutVersion3Migration).inSingletonScope();
Expand Down
16 changes: 14 additions & 2 deletions packages/navigator/src/browser/navigator-widget-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
WidgetManager
} from '@theia/core/lib/browser';
import { FILE_NAVIGATOR_ID } from './navigator-widget';
import { OpenEditorsWidget } from './open-editors-widget/navigator-open-editors-widget';

export const EXPLORER_VIEW_CONTAINER_ID = 'explorer-view-container';
export const EXPLORER_VIEW_CONTAINER_TITLE_OPTIONS: ViewContainerTitleOptions = {
Expand All @@ -37,9 +38,18 @@ export class NavigatorWidgetFactory implements WidgetFactory {

readonly id = NavigatorWidgetFactory.ID;

protected openEditorsWidgetOptions: ViewContainer.Factory.WidgetOptions = {
order: 0,
canHide: true,
initiallyCollapsed: true,
weight: 20
};

protected fileNavigatorWidgetOptions: ViewContainer.Factory.WidgetOptions = {
order: 1,
canHide: false,
initiallyCollapsed: false,
weight: 80
};

@inject(ViewContainer.Factory)
Expand All @@ -52,8 +62,10 @@ export class NavigatorWidgetFactory implements WidgetFactory {
progressLocationId: 'explorer'
});
viewContainer.setTitleOptions(EXPLORER_VIEW_CONTAINER_TITLE_OPTIONS);
const widget = await this.widgetManager.getOrCreateWidget(FILE_NAVIGATOR_ID);
viewContainer.addWidget(widget, this.fileNavigatorWidgetOptions);
const openEditorsWidget = await this.widgetManager.getOrCreateWidget(OpenEditorsWidget.ID);
const navigatorWidget = await this.widgetManager.getOrCreateWidget(FILE_NAVIGATOR_ID);
viewContainer.addWidget(openEditorsWidget, this.openEditorsWidgetOptions);
viewContainer.addWidget(navigatorWidget, this.fileNavigatorWidgetOptions);
return viewContainer;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/********************************************************************************
* 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 { inject, injectable, named } from '@theia/core/shared/inversify';
import { ContributionProvider } from '@theia/core/lib/common/contribution-provider';
import { TreeDecorator, AbstractTreeDecoratorService, TreeDecoration } from '@theia/core/lib/browser/tree/tree-decorator';
import { NavigatorTreeDecorator } from '../navigator-decorator-service';
import { Tree } from '@theia/core/lib/browser';

export const OpenEditorsTreeDecorator = Symbol('OpenEditorsTreeDecorator');

@injectable()
export class OpenEditorsTreeDecoratorService extends AbstractTreeDecoratorService {
protected problemDecorator: TreeDecorator | undefined;

protected globalColorMap = new Map<string, string>();

constructor(@inject(ContributionProvider) @named(OpenEditorsTreeDecorator) protected readonly contributions: ContributionProvider<TreeDecorator>,
@inject(ContributionProvider) @named(NavigatorTreeDecorator) protected readonly navigatorContributions: ContributionProvider<TreeDecorator>) {
super([...contributions.getContributions(), ...navigatorContributions.getContributions()]);
this.problemDecorator = this.decorators.find(decorator => decorator.id === 'theia-problem-decorator');
}

protected async getColorsFromProblemDecorator(tree: Tree): Promise<Map<string, string>> {
const colorMap = new Map<string, string>();
const { problemDecorator } = this;
if (problemDecorator) {
const problemDecoratorIterator = (await problemDecorator.decorations(tree)).entries();
for (const [id, data] of problemDecoratorIterator) {
const colorFromDecorator = data.fontData?.color;
if (colorFromDecorator) {
colorMap.set(id, colorFromDecorator);
}
}
}
return colorMap;
}

protected addColorToSuffixes(suffixes: TreeDecoration.CaptionAffix[], colorToAdd: string): TreeDecoration.CaptionAffix[] {
const modifiedCaptionSuffixes = suffixes.map(suffix => {
const existingFontData = { ...suffix.fontData };
return { ...suffix, fontData: { ...existingFontData, color: colorToAdd } };
});
return modifiedCaptionSuffixes;
}

// Theia's problem decorator provides colorization to FileTree's Caption, but not to its caption suffix.
// Since the suffix text is coming from elsewhere (OpenEditorsDecoratorService), the color needs to be manually
// added here
async getDecorations(tree: Tree): Promise<Map<string, TreeDecoration.Data[]>> {
const changes = new Map<string, TreeDecoration.Data[]>();
const colorsFromProblemDecorator = await this.getColorsFromProblemDecorator(tree);

for (const decorator of this.decorators) {
for (const [id, data] of (await decorator.decorations(tree)).entries()) {
const decorationCopy = { ...data };
const existingColor = colorsFromProblemDecorator.get(id);
if (existingColor && decorator.id !== 'theia-problem-decorator' && data.captionSuffixes) {
const modifiedCaptionSuffixes = this.addColorToSuffixes(data.captionSuffixes, existingColor);
decorationCopy.captionSuffixes = modifiedCaptionSuffixes;
}
if (changes.has(id)) {
changes.get(id)!.push(decorationCopy);
} else {
changes.set(id, [decorationCopy]);
}
}
}
return changes;
}
}
Loading

0 comments on commit e4cc5d3

Please sign in to comment.