Skip to content

Commit

Permalink
Handle deleted files in open editors widget
Browse files Browse the repository at this point in the history
  • Loading branch information
kenneth-marut-work committed Nov 15, 2021
1 parent c558358 commit cc34e5a
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { injectable, inject } from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri';
import { environment } from '@theia/core/shared/@theia/application-package/lib/environment';
import { MaybePromise, SelectionService, isCancelled } from '@theia/core/lib/common';
import { MaybePromise, SelectionService, isCancelled, Emitter } from '@theia/core/lib/common';
import { Command, CommandContribution, CommandRegistry } from '@theia/core/lib/common/command';
import {
FrontendApplicationContribution, ApplicationShell,
Expand Down Expand Up @@ -77,6 +77,9 @@ export class FileSystemFrontendContribution implements FrontendApplicationContri
@inject(FileService)
protected readonly fileService: FileService;

protected onDidChangeEditorFileEmitter = new Emitter<{ editor: NavigatableWidget, type: FileChangeType }>();
readonly onDidChangeEditorFile = this.onDidChangeEditorFileEmitter.event;

protected readonly userOperations = new Map<number, Deferred<void>>();
protected queueUserOperation(event: UserFileOperationEvent): void {
const moveOperation = new Deferred<void>();
Expand Down Expand Up @@ -301,13 +304,15 @@ export class FileSystemFrontendContribution implements FrontendApplicationContri
}
if (!deleted) {
widget.title.label += this.deletedSuffix;
this.onDidChangeEditorFileEmitter.fire({ editor: widget, type: FileChangeType.DELETED });
}
const widgets = toClose.get(uriString) || [];
widgets.push(widget);
toClose.set(uriString, widgets);
} else if (event.contains(uri, FileChangeType.ADDED)) {
if (deleted) {
widget.title.label = widget.title.label.substr(0, label.length - this.deletedSuffix.length);
this.onDidChangeEditorFileEmitter.fire({ editor: widget, type: FileChangeType.ADDED });
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/navigator/src/browser/navigator-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { bindContributionProvider } from '@theia/core/lib/common';
import { OpenEditorsTreeDecorator } from './open-editors-widget/navigator-open-editors-decorator-service';
import { OpenEditorsWidget } from './open-editors-widget/navigator-open-editors-widget';
import { NavigatorTreeDecorator } from './navigator-decorator-service';
import { NavigatorDeletedEditorDecorator } from './open-editors-widget/navigator-deleted-editor-decorator';

export default new ContainerModule(bind => {
bindFileNavigatorPreferences(bind);
Expand All @@ -63,6 +64,8 @@ export default new ContainerModule(bind => {
})).inSingletonScope();
bindContributionProvider(bind, NavigatorTreeDecorator);
bindContributionProvider(bind, OpenEditorsTreeDecorator);
bind(NavigatorDeletedEditorDecorator).toSelf().inSingletonScope();
bind(OpenEditorsTreeDecorator).toService(NavigatorDeletedEditorDecorator);

bind(WidgetFactory).toDynamicValue(({ container }) => ({
id: OpenEditorsWidget.ID,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/********************************************************************************
* 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, postConstruct } from '@theia/core/shared/inversify';
import { DepthFirstTreeIterator, NavigatableWidget, Tree, TreeDecoration, TreeDecorator } from '@theia/core/lib/browser';
import { FileSystemFrontendContribution } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
import { Emitter } from '@theia/core';
import { OpenEditorNode } from './navigator-open-editors-tree-model';
import { FileChangeType } from '@theia/filesystem/lib/browser';

@injectable()
export class NavigatorDeletedEditorDecorator implements TreeDecorator {

@inject(FileSystemFrontendContribution)
protected readonly fileSystemContribution: FileSystemFrontendContribution;

readonly id = 'theia-deleted-editor-decorator';
protected readonly onDidChangeDecorationsEmitter = new Emitter();
readonly onDidChangeDecorations = this.onDidChangeDecorationsEmitter.event;
protected deletedEditors = new Set<NavigatableWidget>();

@postConstruct()
init(): void {
this.fileSystemContribution.onDidChangeEditorFile(({ editor, type }) => {
if (type === FileChangeType.DELETED) {
this.deletedEditors.add(editor);
} else if (type === FileChangeType.ADDED) {
this.deletedEditors.delete(editor);
}
this.fireDidChangeDecorations((tree: Tree) => this.collectDecorators(tree));
});
}

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

protected collectDecorators(tree: Tree): Map<string, TreeDecoration.Data> {
const deletedEditorsCopy = new Set(this.deletedEditors);
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) && NavigatableWidget.is(node.widget)) {
if (this.deletedEditors.has(node.widget)) {
const deletedDecoration: TreeDecoration.Data = {
fontData: {
style: 'line-through',
}
};
deletedEditorsCopy.delete(node.widget);
result.set(node.id, deletedDecoration);
}
}
}
deletedEditorsCopy.forEach(remainingEditor => this.deletedEditors.delete(remainingEditor));
return result;
}

protected fireDidChangeDecorations(event: (tree: Tree) => Map<string, TreeDecoration.Data>): void {
this.onDidChangeDecorationsEmitter.fire(event);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
import { WorkspaceService } from '@theia/workspace/lib/browser';
import debounce = require('@theia/core/shared/lodash.debounce');
import { DisposableCollection } from '@theia/core/lib/common';
import { FileStat } from '@theia/filesystem/lib/common/files';

export interface OpenEditorNode extends FileStatNode {
widget: Widget;
};
Expand Down Expand Up @@ -60,6 +62,8 @@ export class OpenEditorsModel extends FileTreeModel {
// Last collection of editors before a layout modification, used to detect changes in widget ordering
protected _lastEditorWidgetsByArea = new Map<ApplicationShell.Area, NavigatableWidget[]>();

protected cachedFileStats = new Map<string, FileStat>();

get editorWidgets(): NavigatableWidget[] {
const editorWidgets: NavigatableWidget[] = [];
this._editorWidgetsByArea.forEach(widgets => editorWidgets.push(...widgets));
Expand All @@ -84,7 +88,15 @@ export class OpenEditorsModel extends FileTreeModel {
this.selectNode(nodeToSelect);
}
}));
this.toDispose.push(this.applicationShell.onDidAddWidget(() => this.updateOpenWidgets()));
this.toDispose.push(this.applicationShell.onDidAddWidget(async () => {
await this.updateOpenWidgets();
const existingWidgetIds = new Set(this.editorWidgets.map(widget => widget.id));
this.cachedFileStats.forEach((_fileStat, id) => {
if (!existingWidgetIds.has(id)) {
this.cachedFileStats.delete(id);
}
});
}));
this.toDispose.push(this.applicationShell.onDidRemoveWidget(() => this.updateOpenWidgets()));
// Check for tabs rearranged in main and bottom
this.applicationShell.mainPanel.layoutModified.connect(() => this.doUpdateOpenWidgets('main'));
Expand Down Expand Up @@ -187,7 +199,19 @@ export class OpenEditorsModel extends FileTreeModel {
for (const widget of widgetsInArea) {
const uri = widget.getResourceUri();
if (uri) {
const fileStat = await this.fileService.resolve(uri);
let fileStat: FileStat;
try {
fileStat = await this.fileService.resolve(uri);
this.cachedFileStats.set(widget.id, fileStat);
} catch {
const cachedStat = this.cachedFileStats.get(widget.id);
if (cachedStat) {
fileStat = cachedStat;
} else {
continue;
}
}

const openEditorNode: OpenEditorNode = {
id: widget.id,
fileStat,
Expand Down

0 comments on commit cc34e5a

Please sign in to comment.