@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
303 lines • 11.7 kB
JavaScript
"use strict";
/********************************************************************************
* Copyright (C) 2022 Arm 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.AutoSaveThrottle = exports.SaveableService = void 0;
const tslib_1 = require("tslib");
const inversify_1 = require("inversify");
const common_1 = require("../common");
const navigatable_types_1 = require("./navigatable-types");
const saveable_1 = require("./saveable");
const widgets_1 = require("./widgets");
const throttle = require("lodash.throttle");
let SaveableService = class SaveableService {
constructor() {
this.saveThrottles = new Map();
this.saveMode = 'off';
this.saveDelay = 1000;
this.onDidAutoSaveChangeEmitter = new common_1.Emitter();
this.onDidAutoSaveDelayChangeEmitter = new common_1.Emitter();
}
get onDidAutoSaveChange() {
return this.onDidAutoSaveChangeEmitter.event;
}
get onDidAutoSaveDelayChange() {
return this.onDidAutoSaveDelayChangeEmitter.event;
}
get autoSave() {
return this.saveMode;
}
set autoSave(value) {
this.updateAutoSaveMode(value);
}
get autoSaveDelay() {
return this.saveDelay;
}
set autoSaveDelay(value) {
this.updateAutoSaveDelay(value);
}
onDidInitializeLayout(app) {
this.shell = app.shell;
// Register restored editors first
for (const widget of this.shell.widgets) {
const saveable = saveable_1.Saveable.get(widget);
if (saveable) {
this.registerSaveable(widget, saveable);
}
}
this.shell.onDidAddWidget(e => {
const saveable = saveable_1.Saveable.get(e);
if (saveable) {
this.registerSaveable(e, saveable);
}
});
this.shell.onDidChangeCurrentWidget(e => {
if (this.saveMode === 'onFocusChange') {
const widget = e.oldValue;
const saveable = saveable_1.Saveable.get(widget);
if (saveable && widget && this.shouldAutoSave(widget, saveable)) {
saveable.save({
saveReason: saveable_1.SaveReason.FocusChange
});
}
}
});
this.shell.onDidRemoveWidget(e => {
var _a;
(_a = this.saveThrottles.get(e)) === null || _a === void 0 ? void 0 : _a.dispose();
this.saveThrottles.delete(e);
});
}
updateAutoSaveMode(mode) {
this.saveMode = mode;
this.onDidAutoSaveChangeEmitter.fire(mode);
if (mode === 'onFocusChange') {
// If the new mode is onFocusChange, we need to save all dirty documents that are not focused
const widgets = this.shell.widgets;
for (const widget of widgets) {
const saveable = saveable_1.Saveable.get(widget);
if (saveable && widget !== this.shell.currentWidget && this.shouldAutoSave(widget, saveable)) {
saveable.save({
saveReason: saveable_1.SaveReason.FocusChange
});
}
}
}
}
updateAutoSaveDelay(delay) {
this.saveDelay = delay;
this.onDidAutoSaveDelayChangeEmitter.fire(delay);
}
registerSaveable(widget, saveable) {
const saveThrottle = new AutoSaveThrottle(saveable, this, () => {
if (this.saveMode === 'afterDelay' && this.shouldAutoSave(widget, saveable)) {
saveable.save({
saveReason: saveable_1.SaveReason.AfterDelay
});
}
}, this.addBlurListener(widget, saveable));
this.saveThrottles.set(widget, saveThrottle);
this.applySaveableWidget(widget, saveable);
return saveThrottle;
}
addBlurListener(widget, saveable) {
const document = widget.node.ownerDocument;
const listener = (() => {
if (this.saveMode === 'onWindowChange' && !this.windowHasFocus(document) && this.shouldAutoSave(widget, saveable)) {
saveable.save({
saveReason: saveable_1.SaveReason.FocusChange
});
}
}).bind(this);
document.addEventListener('blur', listener);
return common_1.Disposable.create(() => {
document.removeEventListener('blur', listener);
});
}
windowHasFocus(document) {
if (document.visibilityState === 'hidden') {
return false;
}
else if (document.hasFocus()) {
return true;
}
// TODO: Add support for iframes
return false;
}
shouldAutoSave(widget, saveable) {
const uri = navigatable_types_1.NavigatableWidget.getUri(widget);
if ((uri === null || uri === void 0 ? void 0 : uri.scheme) === common_1.UNTITLED_SCHEME) {
// Never auto-save untitled documents
return false;
}
else {
return saveable.autosaveable !== false && saveable.dirty;
}
}
applySaveableWidget(widget, saveable) {
if (saveable_1.SaveableWidget.is(widget)) {
return;
}
const saveableWidget = widget;
(0, saveable_1.setDirty)(saveableWidget, saveable.dirty);
saveable.onDirtyChanged(() => (0, saveable_1.setDirty)(saveableWidget, saveable.dirty));
const closeWithSaving = this.createCloseWithSaving();
const closeWithoutSaving = async () => {
const revert = saveable_1.Saveable.closingWidgetWouldLoseSaveable(saveableWidget, Array.from(this.saveThrottles.keys()));
await this.closeWithoutSaving(saveableWidget, revert);
};
Object.assign(saveableWidget, {
closeWithoutSaving,
closeWithSaving,
close: closeWithSaving,
[saveable_1.close]: saveableWidget.close,
});
}
createCloseWithSaving() {
let closing = false;
const doSave = this.closeWithSaving.bind(this);
return async function (options) {
if (closing) {
return;
}
closing = true;
try {
await doSave(this, options);
}
finally {
closing = false;
}
};
}
async closeWithSaving(widget, options) {
const result = await this.shouldSaveWidget(widget, options);
if (typeof result === 'boolean') {
if (result) {
await this.save(widget, {
saveReason: saveable_1.SaveReason.AfterDelay
});
if (!saveable_1.Saveable.isDirty(widget)) {
await widget.closeWithoutSaving();
}
}
else {
await widget.closeWithoutSaving();
}
}
}
async shouldSaveWidget(widget, options) {
if (!saveable_1.Saveable.isDirty(widget)) {
return false;
}
const saveable = saveable_1.Saveable.get(widget);
if (!saveable) {
console.warn('Saveable.get returned undefined on a known saveable widget. This is unexpected.');
}
// Enter branch if saveable absent since we cannot check autosaveability more definitely.
if (this.autoSave !== 'off' && (!saveable || this.shouldAutoSave(widget, saveable))) {
return true;
}
const notLastWithDocument = !saveable_1.Saveable.closingWidgetWouldLoseSaveable(widget, Array.from(this.saveThrottles.keys()));
if (notLastWithDocument) {
await widget.closeWithoutSaving(false);
return undefined;
}
if (options && options.shouldSave) {
return options.shouldSave();
}
return new saveable_1.ShouldSaveDialog(widget).open();
}
async closeWithoutSaving(widget, doRevert = true) {
const saveable = saveable_1.Saveable.get(widget);
if (saveable && doRevert && saveable.dirty && saveable.revert) {
await saveable.revert();
}
widget[saveable_1.close]();
return (0, widgets_1.waitForClosed)(widget);
}
/**
* Indicate if the document can be saved ('Save' command should be disable if not).
*/
canSave(widget) {
return saveable_1.Saveable.isDirty(widget) && (this.canSaveNotSaveAs(widget) || this.canSaveAs(widget));
}
canSaveNotSaveAs(widget) {
var _a;
// By default, we never allow a document to be saved if it is untitled.
return Boolean(widget && ((_a = navigatable_types_1.NavigatableWidget.getUri(widget)) === null || _a === void 0 ? void 0 : _a.scheme) !== common_1.UNTITLED_SCHEME);
}
/**
* Saves the document
*
* No op if the widget is not saveable.
*/
async save(widget, options) {
if (this.canSaveNotSaveAs(widget)) {
await saveable_1.Saveable.save(widget, options);
return navigatable_types_1.NavigatableWidget.getUri(widget);
}
else if (this.canSaveAs(widget)) {
return this.saveAs(widget, options);
}
}
canSaveAs(saveable) {
return false;
}
saveAs(sourceWidget, options) {
return Promise.reject('Unsupported: The base SaveResourceService does not support saveAs action.');
}
};
exports.SaveableService = SaveableService;
exports.SaveableService = SaveableService = tslib_1.__decorate([
(0, inversify_1.injectable)()
], SaveableService);
class AutoSaveThrottle {
constructor(saveable, saveService, callback, ...disposables) {
this._callback = callback;
this._saveable = saveable;
this._saveService = saveService;
this._disposable = new common_1.DisposableCollection(...disposables, saveable.onContentChanged(() => {
this.throttledSave();
}), saveable.onDirtyChanged(() => {
this.throttledSave();
}), saveService.onDidAutoSaveChange(() => {
this.throttledSave();
}), saveService.onDidAutoSaveDelayChange(() => {
this.throttledSave(true);
}));
}
throttledSave(reset = false) {
var _a;
(_a = this._throttle) === null || _a === void 0 ? void 0 : _a.cancel();
if (reset) {
this._throttle = undefined;
}
if (this._saveService.autoSave === 'afterDelay' && this._saveable.dirty) {
if (!this._throttle) {
this._throttle = throttle(() => this._callback(), this._saveService.autoSaveDelay, {
leading: false,
trailing: true
});
}
this._throttle();
}
}
dispose() {
this._disposable.dispose();
}
}
exports.AutoSaveThrottle = AutoSaveThrottle;
//# sourceMappingURL=saveable-service.js.map