diff --git a/packages/editor-preview/package.json b/packages/editor-preview/package.json index 7b6226a96d642..5375756b55c89 100644 --- a/packages/editor-preview/package.json +++ b/packages/editor-preview/package.json @@ -3,9 +3,10 @@ "version": "1.19.0", "description": "Theia - Editor Preview Extension", "dependencies": { - "@theia/core": "1.19.0", - "@theia/editor": "1.19.0", - "@theia/navigator": "1.19.0" + "@theia/core": "1.18.0", + "@theia/editor": "1.18.0", + "@theia/filesystem": "1.18.0", + "@theia/navigator": "1.18.0" }, "publishConfig": { "access": "public" diff --git a/packages/editor-preview/src/browser/editor-preview-tree-decorator.ts b/packages/editor-preview/src/browser/editor-preview-tree-decorator.ts index fff937c5d0bcf..1596dbb29ab10 100644 --- a/packages/editor-preview/src/browser/editor-preview-tree-decorator.ts +++ b/packages/editor-preview/src/browser/editor-preview-tree-decorator.ts @@ -20,6 +20,7 @@ import { Emitter } from '@theia/core/lib/common/event'; import { Tree } from '@theia/core/lib/browser/tree/tree'; import { ApplicationShell, + CorePreferences, DepthFirstTreeIterator, FrontendApplication, FrontendApplicationContribution, @@ -27,15 +28,21 @@ import { Saveable, Widget, } from '@theia/core/lib/browser'; -import { Disposable } from '@theia/core/lib/common'; +import { Disposable, MaybeArray } 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'; +import { FileService } from '@theia/filesystem/lib/browser/file-service'; +import { FileChangesEvent } from '@theia/filesystem/lib/common/files'; @injectable() export class EditorPreviewTreeDecorator implements TreeDecorator, FrontendApplicationContribution { @inject(EditorPreviewManager) protected readonly editorPreviewManager: EditorPreviewManager; @inject(ApplicationShell) protected readonly shell: ApplicationShell; + @inject(CorePreferences) protected readonly corePreferences: CorePreferences; + @inject(FileService) protected readonly fileService: FileService; + + protected deletedEditorIds = new Set(); readonly id = 'theia-open-editors-file-decorator'; protected decorationsMap = new Map(); @@ -49,6 +56,35 @@ export class EditorPreviewTreeDecorator implements TreeDecorator, FrontendApplic this.shell.onDidAddWidget(widget => this.registerEditorListeners(widget)); this.shell.onDidRemoveWidget(widget => this.unregisterEditorListeners(widget)); this.editorWidgets.forEach(widget => this.registerEditorListeners(widget)); + this.fileService.onDidFilesChange(async (event: FileChangesEvent) => { + if (!this.corePreferences['workbench.editor.closeOnFileDelete']) { + await this.checkForEditorDeleted(event); + this.fireDidChangeDecorations((tree: Tree) => this.collectDecorators(tree)); + } + }); + } + + protected async checkForEditorDeleted(event: FileChangesEvent): Promise { + // this implementation to check for deleted files is necessary since the gotDeleted check is not always reliable + const fileExistPromises: Promise[] = []; + this.editorWidgets.forEach(editor => { + const editorURI = editor.getResourceUri(); + if (editorURI && event.contains(editorURI)) { + const fileExistsPromise = this.fileService.exists(editorURI) + .then(doesExist => { + if (doesExist) { + this.deletedEditorIds.delete(editor.id); + } else { + this.deletedEditorIds.add(editor.id); + } + }); + fileExistPromises.push(fileExistsPromise); + } + }); + if (!fileExistPromises.length) { + return; + } + return Promise.all(fileExistPromises); } protected registerEditorListeners(widget: Widget): void { @@ -93,13 +129,18 @@ export class EditorPreviewTreeDecorator implements TreeDecorator, FrontendApplic return result; } for (const node of new DepthFirstTreeIterator(tree.root)) { - if (OpenEditorNode.is(node)) { + if (OpenEditorNode.is(node) && NavigatableWidget.is(node.widget)) { const { widget } = node; const isPreviewWidget = widget instanceof EditorPreviewWidget && widget.isPreview; - const decorations: TreeDecoration.Data = { - fontData: { style: isPreviewWidget ? 'italic' : undefined } - }; - result.set(node.id, decorations); + const deleted = this.deletedEditorIds.has(widget.id); + const style: MaybeArray = []; + if (isPreviewWidget) { + style.push('italic'); + } + if (deleted) { + style.push('line-through'); + } + result.set(node.id, { fontData: { style } }); } } return result; diff --git a/packages/editor-preview/tsconfig.json b/packages/editor-preview/tsconfig.json index b8599fa6f6f5a..b65f58ffd3702 100644 --- a/packages/editor-preview/tsconfig.json +++ b/packages/editor-preview/tsconfig.json @@ -14,6 +14,12 @@ }, { "path": "../editor" + }, + { + "path": "../filesystem" + }, + { + "path": "../navigator" } ] } diff --git a/packages/navigator/src/browser/open-editors-widget/navigator-open-editors-tree-model.ts b/packages/navigator/src/browser/open-editors-widget/navigator-open-editors-tree-model.ts index 2b257b0d9aeb1..661efa8c85bb4 100644 --- a/packages/navigator/src/browser/open-editors-widget/navigator-open-editors-tree-model.ts +++ b/packages/navigator/src/browser/open-editors-widget/navigator-open-editors-tree-model.ts @@ -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; }; @@ -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(); + protected cachedFileStats = new Map(); + get editorWidgets(): NavigatableWidget[] { const editorWidgets: NavigatableWidget[] = []; this._editorWidgetsByArea.forEach(widgets => editorWidgets.push(...widgets)); @@ -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 = this.editorWidgets.map(widget => widget.id); + this.cachedFileStats.forEach((_fileStat, id) => { + if (!existingWidgetIds.includes(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')); @@ -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,