@theia/filesystem
Version:
Theia - FileSystem Extension
372 lines • 16.9 kB
JavaScript
"use strict";
// *****************************************************************************
// Copyright (C) 2018 TypeFox 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-only WITH Classpath-exception-2.0
// *****************************************************************************
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileSystemFrontendContribution = exports.FileSystemCommands = void 0;
const tslib_1 = require("tslib");
const core_1 = require("@theia/core");
const browser_1 = require("@theia/core/lib/browser");
const mime_service_1 = require("@theia/core/lib/browser/mime-service");
const tree_widget_selection_1 = require("@theia/core/lib/browser/tree/tree-widget-selection");
const common_1 = require("@theia/core/lib/common");
const command_1 = require("@theia/core/lib/common/command");
const promise_util_1 = require("@theia/core/lib/common/promise-util");
const uri_1 = require("@theia/core/lib/common/uri");
const environment_1 = require("@theia/core/shared/@theia/application-package/lib/environment");
const inversify_1 = require("@theia/core/shared/inversify");
const user_working_directory_provider_1 = require("@theia/core/lib/browser/user-working-directory-provider");
const file_dialog_1 = require("./file-dialog");
const file_selection_1 = require("./file-selection");
const file_service_1 = require("./file-service");
const filesystem_preferences_1 = require("../common/filesystem-preferences");
const file_upload_1 = require("../common/upload/file-upload");
var FileSystemCommands;
(function (FileSystemCommands) {
FileSystemCommands.UPLOAD = command_1.Command.toLocalizedCommand({
id: 'file.upload',
category: browser_1.CommonCommands.FILE_CATEGORY,
label: 'Upload Files...'
}, 'theia/filesystem/uploadFiles', browser_1.CommonCommands.FILE_CATEGORY_KEY);
})(FileSystemCommands || (exports.FileSystemCommands = FileSystemCommands = {}));
let FileSystemFrontendContribution = class FileSystemFrontendContribution {
constructor() {
this.onDidChangeEditorFileEmitter = new common_1.Emitter();
this.onDidChangeEditorFile = this.onDidChangeEditorFileEmitter.event;
this.userOperations = new Map();
this.pendingOperation = Promise.resolve();
this.moveSnapshots = new Map();
this.deletedSuffix = `(${core_1.nls.localizeByDefault('Deleted')})`;
}
queueUserOperation(event) {
const moveOperation = new promise_util_1.Deferred();
this.userOperations.set(event.correlationId, moveOperation);
this.run(() => moveOperation.promise);
}
resolveUserOperation(event) {
const operation = this.userOperations.get(event.correlationId);
if (operation) {
this.userOperations.delete(event.correlationId);
operation.resolve();
}
}
initialize() {
this.fileService.onDidFilesChange(event => this.run(() => this.updateWidgets(event)));
this.fileService.onWillRunUserOperation(event => {
this.queueUserOperation(event);
event.waitUntil(this.runEach((uri, widget) => this.pushMove(uri, widget, event)));
});
this.fileService.onDidFailUserOperation(event => event.waitUntil((async () => {
await this.runEach((uri, widget) => this.revertMove(uri, widget, event));
this.resolveUserOperation(event);
})()));
this.fileService.onDidRunUserOperation(event => event.waitUntil((async () => {
await this.runEach((uri, widget) => this.applyMove(uri, widget, event));
this.resolveUserOperation(event);
})()));
this.uploadService.onDidUpload(files => {
this.doHandleUpload(files);
});
}
onStart(app) {
this.updateAssociations();
this.preferences.onPreferenceChanged(e => {
if (e.preferenceName === 'files.associations') {
this.updateAssociations();
}
});
}
registerCommands(commands) {
commands.registerCommand(FileSystemCommands.UPLOAD, {
isEnabled: (...args) => {
const selection = this.getSelection(...args);
return !!selection && !environment_1.environment.electron.is();
},
isVisible: () => !environment_1.environment.electron.is(),
execute: (...args) => {
const selection = this.getSelection(...args);
if (selection) {
return this.upload(selection);
}
}
});
commands.registerCommand(browser_1.CommonCommands.NEW_FILE, {
execute: (...args) => {
this.handleNewFileCommand(args);
}
});
}
async upload(selection) {
try {
const source = tree_widget_selection_1.TreeWidgetSelection.getSource(this.selectionService.selection);
const fileUploadResult = await this.uploadService.upload(selection.fileStat.isDirectory ? selection.fileStat.resource : selection.fileStat.resource.parent);
if (browser_1.ExpandableTreeNode.is(selection) && source) {
await source.model.expandNode(selection);
}
return fileUploadResult;
}
catch (e) {
if (!(0, common_1.isCancelled)(e)) {
console.error(e);
}
}
}
async doHandleUpload(uploads) {
// Only handle single file uploads
if (uploads.length === 1) {
const uri = new uri_1.default(uploads[0]);
// Close all existing widgets for this URI
const widgets = this.shell.widgets.filter(widget => { var _a; return (_a = browser_1.NavigatableWidget.getUri(widget)) === null || _a === void 0 ? void 0 : _a.isEqual(uri); });
await this.shell.closeMany(widgets, {
// Don't ask to save the file if it's dirty
// The user has already confirmed the file overwrite
save: false
});
// Open a new editor for this URI
(0, browser_1.open)(this.openerService, uri);
}
}
/**
* Opens a save dialog to create a new file.
*
* @param args The first argument is the name of the new file. The second argument is the parent directory URI.
*/
async handleNewFileCommand(args) {
const fileName = (args !== undefined && typeof args[0] === 'string') ? args[0] : undefined;
const title = core_1.nls.localizeByDefault('Create File');
const props = { title, saveLabel: title, inputValue: fileName };
const dirUri = (args[1] instanceof uri_1.default) ? args[1] : await this.workingDirectory.getUserWorkingDir();
const directory = await this.fileService.resolve(dirUri);
const filePath = await this.fileDialogService.showSaveDialog(props, directory.isDirectory ? directory : undefined);
if (filePath) {
const file = await this.fileService.createFile(filePath);
(0, browser_1.open)(this.openerService, file.resource);
}
}
getSelection(...args) {
var _a;
const { selection } = this.selectionService;
return (_a = this.toSelection(args[0])) !== null && _a !== void 0 ? _a : (Array.isArray(selection) ? selection.find(file_selection_1.FileSelection.is) : this.toSelection(selection));
}
;
toSelection(arg) {
return file_selection_1.FileSelection.is(arg) ? arg : undefined;
}
run(operation) {
return this.pendingOperation = this.pendingOperation.then(async () => {
try {
await operation();
}
catch (e) {
console.error(e);
}
});
}
async runEach(participant) {
const promises = [];
for (const [resourceUri, widget] of browser_1.NavigatableWidget.get(this.shell.widgets)) {
promises.push(participant(resourceUri, widget));
}
await Promise.all(promises);
}
popMoveSnapshot(resourceUri) {
const snapshotKey = resourceUri.toString();
const snapshot = this.moveSnapshots.get(snapshotKey);
if (snapshot) {
this.moveSnapshots.delete(snapshotKey);
}
return snapshot;
}
applyMoveSnapshot(widget, snapshot) {
if (!snapshot) {
return undefined;
}
if (snapshot.dirty) {
const saveable = browser_1.Saveable.get(widget);
if (saveable && saveable.applySnapshot) {
saveable.applySnapshot(snapshot.dirty);
}
}
if (snapshot.view && browser_1.StatefulWidget.is(widget)) {
widget.restoreState(snapshot.view);
}
}
async pushMove(resourceUri, widget, event) {
const newResourceUri = this.createMoveToUri(resourceUri, widget, event);
if (!newResourceUri) {
return;
}
const snapshot = {};
const saveable = browser_1.Saveable.get(widget);
if (browser_1.StatefulWidget.is(widget)) {
snapshot.view = widget.storeState();
}
if (saveable && saveable.dirty) {
if (saveable.createSnapshot) {
snapshot.dirty = saveable.createSnapshot();
}
if (saveable.revert) {
await saveable.revert({ soft: true });
}
}
this.moveSnapshots.set(newResourceUri.toString(), snapshot);
}
async revertMove(resourceUri, widget, event) {
const newResourceUri = this.createMoveToUri(resourceUri, widget, event);
if (!newResourceUri) {
return;
}
const snapshot = this.popMoveSnapshot(newResourceUri);
this.applyMoveSnapshot(widget, snapshot);
}
async applyMove(resourceUri, widget, event) {
const newResourceUri = this.createMoveToUri(resourceUri, widget, event);
if (!newResourceUri) {
return;
}
const snapshot = this.popMoveSnapshot(newResourceUri);
const description = this.widgetManager.getDescription(widget);
if (!description) {
return;
}
const { factoryId, options } = description;
if (!browser_1.NavigatableWidgetOptions.is(options)) {
return;
}
const newWidget = await this.widgetManager.getOrCreateWidget(factoryId, {
...options,
uri: newResourceUri.toString()
});
this.applyMoveSnapshot(newWidget, snapshot);
const area = this.shell.getAreaFor(widget) || 'main';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const pending = [this.shell.addWidget(newWidget, {
area, ref: widget
})];
if (this.shell.activeWidget === widget) {
pending.push(this.shell.activateWidget(newWidget.id));
}
else if (widget.isVisible) {
pending.push(this.shell.revealWidget(newWidget.id));
}
pending.push(this.shell.closeWidget(widget.id, { save: false }));
await Promise.all(pending);
}
createMoveToUri(resourceUri, widget, event) {
var _a;
if (event.operation !== 2 /* FileOperation.MOVE */) {
return undefined;
}
const path = (_a = event.source) === null || _a === void 0 ? void 0 : _a.relative(resourceUri);
const targetUri = path && event.target.resolve(path);
return targetUri && widget.createMoveToUri(targetUri);
}
async updateWidgets(event) {
if (!event.gotDeleted() && !event.gotAdded()) {
return;
}
const dirty = new Set();
const toClose = new Map();
for (const [uri, widget] of browser_1.NavigatableWidget.get(this.shell.widgets)) {
this.updateWidget(uri, widget, event, { dirty, toClose: toClose });
}
if (this.corePreferences['workbench.editor.closeOnFileDelete']) {
const doClose = [];
for (const [uri, widgets] of toClose.entries()) {
if (!dirty.has(uri)) {
doClose.push(...widgets);
}
}
await this.shell.closeMany(doClose);
}
}
updateWidget(uri, widget, event, { dirty, toClose }) {
const label = widget.title.label;
const deleted = label.endsWith(this.deletedSuffix);
if (event.contains(uri, 2 /* FileChangeType.DELETED */)) {
const uriString = uri.toString();
if (browser_1.Saveable.isDirty(widget)) {
dirty.add(uriString);
}
if (!deleted) {
widget.title.label += this.deletedSuffix;
this.onDidChangeEditorFileEmitter.fire({ editor: widget, type: 2 /* FileChangeType.DELETED */ });
}
const widgets = toClose.get(uriString) || [];
widgets.push(widget);
toClose.set(uriString, widgets);
}
else if (event.contains(uri, 1 /* FileChangeType.ADDED */)) {
if (deleted) {
widget.title.label = widget.title.label.substring(0, label.length - this.deletedSuffix.length);
this.onDidChangeEditorFileEmitter.fire({ editor: widget, type: 1 /* FileChangeType.ADDED */ });
}
}
}
updateAssociations() {
const fileAssociations = this.preferences['files.associations'];
const mimeAssociations = Object.keys(fileAssociations).map(filepattern => ({ id: fileAssociations[filepattern], filepattern }));
this.mimeService.setAssociations(mimeAssociations);
}
};
exports.FileSystemFrontendContribution = FileSystemFrontendContribution;
tslib_1.__decorate([
(0, inversify_1.inject)(browser_1.ApplicationShell),
tslib_1.__metadata("design:type", browser_1.ApplicationShell)
], FileSystemFrontendContribution.prototype, "shell", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(browser_1.WidgetManager),
tslib_1.__metadata("design:type", browser_1.WidgetManager)
], FileSystemFrontendContribution.prototype, "widgetManager", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(mime_service_1.MimeService),
tslib_1.__metadata("design:type", mime_service_1.MimeService)
], FileSystemFrontendContribution.prototype, "mimeService", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(filesystem_preferences_1.FileSystemPreferences),
tslib_1.__metadata("design:type", Object)
], FileSystemFrontendContribution.prototype, "preferences", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(core_1.CorePreferences),
tslib_1.__metadata("design:type", Object)
], FileSystemFrontendContribution.prototype, "corePreferences", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(common_1.SelectionService),
tslib_1.__metadata("design:type", common_1.SelectionService)
], FileSystemFrontendContribution.prototype, "selectionService", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(file_upload_1.FileUploadService),
tslib_1.__metadata("design:type", Object)
], FileSystemFrontendContribution.prototype, "uploadService", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(file_service_1.FileService),
tslib_1.__metadata("design:type", file_service_1.FileService)
], FileSystemFrontendContribution.prototype, "fileService", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(file_dialog_1.FileDialogService),
tslib_1.__metadata("design:type", Object)
], FileSystemFrontendContribution.prototype, "fileDialogService", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(browser_1.OpenerService),
tslib_1.__metadata("design:type", Object)
], FileSystemFrontendContribution.prototype, "openerService", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(user_working_directory_provider_1.UserWorkingDirectoryProvider),
tslib_1.__metadata("design:type", user_working_directory_provider_1.UserWorkingDirectoryProvider)
], FileSystemFrontendContribution.prototype, "workingDirectory", void 0);
exports.FileSystemFrontendContribution = FileSystemFrontendContribution = tslib_1.__decorate([
(0, inversify_1.injectable)()
], FileSystemFrontendContribution);
//# sourceMappingURL=filesystem-frontend-contribution.js.map