@theia/monaco
Version:
Theia - Monaco Extension
377 lines • 18.8 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.MonacoWorkspace = exports.ResourceTextEdit = exports.ResourceFileEdit = exports.WorkspaceTextEdit = exports.WorkspaceFileEdit = void 0;
const tslib_1 = require("tslib");
const inversify_1 = require("@theia/core/shared/inversify");
const uri_1 = require("@theia/core/lib/common/uri");
const event_1 = require("@theia/core/lib/common/event");
const browser_1 = require("@theia/filesystem/lib/browser");
const browser_2 = require("@theia/editor/lib/browser");
const monaco_text_model_service_1 = require("./monaco-text-model-service");
const monaco_editor_1 = require("./monaco-editor");
const browser_3 = require("@theia/markers/lib/browser");
const types_1 = require("@theia/core/lib/common/types");
const file_service_1 = require("@theia/filesystem/lib/browser/file-service");
const monaco = require("@theia/monaco-editor-core");
const bulkEditService_1 = require("@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService");
const editorWorker_1 = require("@theia/monaco-editor-core/esm/vs/editor/common/services/editorWorker");
const standaloneServices_1 = require("@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices");
const snippetParser_1 = require("@theia/monaco-editor-core/esm/vs/editor/contrib/snippet/browser/snippetParser");
const common_1 = require("@theia/core/lib/common");
const browser_4 = require("@theia/core/lib/browser");
var WorkspaceFileEdit;
(function (WorkspaceFileEdit) {
function is(arg) {
return ('oldResource' in arg && monaco.Uri.isUri(arg.oldResource)) ||
('newResource' in arg && monaco.Uri.isUri(arg.newResource));
}
WorkspaceFileEdit.is = is;
})(WorkspaceFileEdit || (exports.WorkspaceFileEdit = WorkspaceFileEdit = {}));
var WorkspaceTextEdit;
(function (WorkspaceTextEdit) {
function is(arg) {
return (0, common_1.isObject)(arg)
&& monaco.Uri.isUri(arg.resource)
&& (0, common_1.isObject)(arg.textEdit);
}
WorkspaceTextEdit.is = is;
})(WorkspaceTextEdit || (exports.WorkspaceTextEdit = WorkspaceTextEdit = {}));
var ResourceFileEdit;
(function (ResourceFileEdit) {
function is(arg) {
return (0, common_1.isObject)(arg) && (monaco.Uri.isUri(arg.oldResource) || monaco.Uri.isUri(arg.newResource));
}
ResourceFileEdit.is = is;
})(ResourceFileEdit || (exports.ResourceFileEdit = ResourceFileEdit = {}));
var ResourceTextEdit;
(function (ResourceTextEdit) {
function is(arg) {
return ('resource' in arg && monaco.Uri.isUri(arg.resource));
}
ResourceTextEdit.is = is;
})(ResourceTextEdit || (exports.ResourceTextEdit = ResourceTextEdit = {}));
let MonacoWorkspace = class MonacoWorkspace {
constructor() {
this.ready = new Promise(resolve => {
this.resolveReady = resolve;
});
this.onDidOpenTextDocumentEmitter = new event_1.Emitter();
this.onDidOpenTextDocument = this.onDidOpenTextDocumentEmitter.event;
this.onDidCloseTextDocumentEmitter = new event_1.Emitter();
this.onDidCloseTextDocument = this.onDidCloseTextDocumentEmitter.event;
this.onDidChangeTextDocumentEmitter = new event_1.Emitter();
this.onDidChangeTextDocument = this.onDidChangeTextDocumentEmitter.event;
this.onDidSaveTextDocumentEmitter = new event_1.Emitter();
this.onDidSaveTextDocument = this.onDidSaveTextDocumentEmitter.event;
this.suppressedOpenIfDirty = [];
}
init() {
this.resolveReady();
for (const model of this.textModelService.models) {
this.fireDidOpen(model);
}
this.textModelService.onDidCreate(model => this.fireDidOpen(model));
}
get textDocuments() {
return this.textModelService.models;
}
getTextDocument(uri) {
return this.textModelService.get(uri);
}
fireDidOpen(model) {
this.doFireDidOpen(model);
model.textEditorModel.onDidChangeLanguage(e => {
this.problems.cleanAllMarkers(new uri_1.default(model.uri));
model.setLanguageId(e.oldLanguage);
try {
this.fireDidClose(model);
}
finally {
model.setLanguageId(undefined);
}
this.doFireDidOpen(model);
});
model.onDidChangeContent(event => this.fireDidChangeContent(event));
model.onDidSaveModel(() => this.fireDidSave(model));
model.onDirtyChanged(() => this.openEditorIfDirty(model));
model.onDispose(() => this.fireDidClose(model));
}
doFireDidOpen(model) {
this.onDidOpenTextDocumentEmitter.fire(model);
}
fireDidClose(model) {
this.onDidCloseTextDocumentEmitter.fire(model);
}
fireDidChangeContent(event) {
this.onDidChangeTextDocumentEmitter.fire(event);
}
fireDidSave(model) {
this.onDidSaveTextDocumentEmitter.fire(model);
}
openEditorIfDirty(model) {
if (model.suppressOpenEditorWhenDirty || this.suppressedOpenIfDirty.indexOf(model) !== -1) {
return;
}
if (model.dirty && monaco_editor_1.MonacoEditor.findByDocument(this.editorManager, model).length === 0) {
// create a new reference to make sure the model is not disposed before it is
// acquired by the editor, thus losing the changes that made it dirty.
this.textModelService.createModelReference(model.textEditorModel.uri).then(ref => {
(this.saveService.autoSave !== 'off' ? new Promise(resolve => model.onDidSaveModel(resolve)) :
this.editorManager.open(new uri_1.default(model.uri), { mode: 'open' })).then(() => ref.dispose());
});
}
}
async suppressOpenIfDirty(model, cb) {
this.suppressedOpenIfDirty.push(model);
try {
await cb();
}
finally {
const i = this.suppressedOpenIfDirty.indexOf(model);
if (i !== -1) {
this.suppressedOpenIfDirty.splice(i, 1);
}
}
}
/**
* Applies given edits to the given model.
* The model is saved if no editors is opened for it.
*/
applyBackgroundEdit(model, editOperations, shouldSave) {
return this.suppressOpenIfDirty(model, async () => {
const editor = monaco_editor_1.MonacoEditor.findByDocument(this.editorManager, model)[0];
const wasDirty = !!(editor === null || editor === void 0 ? void 0 : editor.document.dirty);
const cursorState = editor && editor.getControl().getSelections() || [];
model.textEditorModel.pushStackElement();
model.textEditorModel.pushEditOperations(cursorState, editOperations, () => cursorState);
model.textEditorModel.pushStackElement();
if ((typeof shouldSave === 'function' && shouldSave(editor, wasDirty)) || (!editor && shouldSave)) {
await model.save();
}
});
}
async applyBulkEdit(edits, options) {
try {
let totalEdits = 0;
let totalFiles = 0;
const fileEdits = edits.filter(edit => edit instanceof bulkEditService_1.ResourceFileEdit);
const [snippetEdits, textEdits] = types_1.ArrayUtils.partition(edits.filter(edit => edit instanceof bulkEditService_1.ResourceTextEdit), edit => { var _a, _b; return edit.textEdit.insertAsSnippet && (edit.resource.toString() === ((_b = (_a = this.editorManager.activeEditor) === null || _a === void 0 ? void 0 : _a.getResourceUri()) === null || _b === void 0 ? void 0 : _b.toString())); });
if (fileEdits.length > 0) {
await this.performFileEdits(fileEdits);
}
if (textEdits.length > 0) {
const result = await this.performTextEdits(textEdits);
totalEdits += result.totalEdits;
totalFiles += result.totalFiles;
}
if (snippetEdits.length > 0) {
await this.performSnippetEdits(snippetEdits);
}
// when enabled (option AND setting) loop over all dirty working copies and trigger save
// for those that were involved in this bulk edit operation.
const resources = new Set(edits
.filter((edit) => edit instanceof bulkEditService_1.ResourceTextEdit)
.map(edit => edit.resource.toString()));
if (resources.size > 0 && (options === null || options === void 0 ? void 0 : options.respectAutoSaveConfig) && this.editorPreferences.get('files.refactoring.autoSave') === true) {
await this.saveAll(resources);
}
const ariaSummary = this.getAriaSummary(totalEdits, totalFiles);
return { ariaSummary, isApplied: true };
}
catch (e) {
console.error('Failed to apply Resource edits:', e);
return {
ariaSummary: `Error applying Resource edits: ${e.toString()}`,
isApplied: false
};
}
}
async saveAll(resources) {
await Promise.all(Array.from(resources.values()).map(uri => { var _a; return (_a = this.textModelService.get(uri)) === null || _a === void 0 ? void 0 : _a.save(); }));
}
getAriaSummary(totalEdits, totalFiles) {
if (totalEdits === 0) {
return common_1.nls.localizeByDefault('Made no edits');
}
if (totalEdits > 1 && totalFiles > 1) {
return common_1.nls.localizeByDefault('Made {0} text edits in {1} files', totalEdits, totalFiles);
}
return common_1.nls.localizeByDefault('Made {0} text edits in one file', totalEdits);
}
async performTextEdits(edits) {
let totalEdits = 0;
let totalFiles = 0;
const resourceEdits = new Map();
for (const edit of edits) {
if (typeof edit.versionId === 'number') {
const model = this.textModelService.get(edit.resource.toString());
if (model && model.textEditorModel.getVersionId() !== edit.versionId) {
throw new Error(`${model.uri} has changed in the meantime`);
}
}
const key = edit.resource.toString();
let array = resourceEdits.get(key);
if (!array) {
array = [];
resourceEdits.set(key, array);
}
array.push(edit);
}
const pending = [];
for (const [key, value] of resourceEdits) {
pending.push((async () => {
var _a;
const uri = monaco.Uri.parse(key);
let eol;
const editOperations = [];
const minimalEdits = await standaloneServices_1.StandaloneServices.get(editorWorker_1.IEditorWorkerService)
.computeMoreMinimalEdits(uri, value.map(edit => this.transformSnippetStringToInsertText(edit)));
if (minimalEdits) {
for (const textEdit of minimalEdits) {
if (typeof textEdit.eol === 'number') {
eol = textEdit.eol;
}
if (monaco.Range.isEmpty(textEdit.range) && !textEdit.text) {
// skip no-op
continue;
}
editOperations.push({
forceMoveMarkers: false,
range: monaco.Range.lift(textEdit.range),
text: textEdit.text
});
}
}
if (!editOperations.length && eol === undefined) {
return;
}
const reference = await this.textModelService.createModelReference(uri);
try {
const document = reference.object;
const model = document.textEditorModel;
const editor = monaco_editor_1.MonacoEditor.findByDocument(this.editorManager, document)[0];
const cursorState = (_a = editor === null || editor === void 0 ? void 0 : editor.getControl().getSelections()) !== null && _a !== void 0 ? _a : [];
// start a fresh operation
model.pushStackElement();
if (editOperations.length) {
model.pushEditOperations(cursorState, editOperations, () => cursorState);
}
if (eol !== undefined) {
model.pushEOL(eol);
}
// push again to make this change an undoable operation
model.pushStackElement();
totalFiles += 1;
totalEdits += editOperations.length;
}
finally {
reference.dispose();
}
})());
}
await Promise.all(pending);
return { totalEdits, totalFiles };
}
async performFileEdits(edits) {
for (const edit of edits) {
const options = edit.options || {};
if (edit.newResource && edit.oldResource) {
// rename
if (options.overwrite === undefined && options.ignoreIfExists && await this.fileService.exists(uri_1.default.fromComponents(edit.newResource))) {
return; // not overwriting, but ignoring, and the target file exists
}
await this.fileService.move(uri_1.default.fromComponents(edit.oldResource), uri_1.default.fromComponents(edit.newResource), { overwrite: options.overwrite });
}
else if (!edit.newResource && edit.oldResource) {
// delete file
if (await this.fileService.exists(uri_1.default.fromComponents(edit.oldResource))) {
let useTrash = this.filePreferences['files.enableTrash'];
if (useTrash && !(this.fileService.hasCapability(uri_1.default.fromComponents(edit.oldResource), 4096 /* FileSystemProviderCapabilities.Trash */))) {
useTrash = false; // not supported by provider
}
await this.fileService.delete(uri_1.default.fromComponents(edit.oldResource), { useTrash, recursive: options.recursive });
}
else if (!options.ignoreIfNotExists) {
throw new Error(`${edit.oldResource} does not exist and can not be deleted`);
}
}
else if (edit.newResource && !edit.oldResource) {
// create file
if (options.overwrite === undefined && options.ignoreIfExists && await this.fileService.exists(uri_1.default.fromComponents(edit.newResource))) {
return; // not overwriting, but ignoring, and the target file exists
}
await this.fileService.create(uri_1.default.fromComponents(edit.newResource), undefined, { overwrite: options.overwrite });
}
}
}
async performSnippetEdits(edits) {
var _a;
const activeEditor = (_a = monaco_editor_1.MonacoEditor.getActive(this.editorManager)) === null || _a === void 0 ? void 0 : _a.getControl();
if (activeEditor) {
const snippetController = activeEditor.getContribution('snippetController2');
snippetController.apply(edits.map(edit => ({ range: monaco.Range.lift(edit.textEdit.range), template: edit.textEdit.text })));
}
}
transformSnippetStringToInsertText(resourceEdit) {
if (resourceEdit.textEdit.insertAsSnippet) {
return { ...resourceEdit.textEdit, insertAsSnippet: false, text: snippetParser_1.SnippetParser.asInsertText(resourceEdit.textEdit.text) };
}
else {
return resourceEdit.textEdit;
}
}
};
exports.MonacoWorkspace = MonacoWorkspace;
tslib_1.__decorate([
(0, inversify_1.inject)(file_service_1.FileService),
tslib_1.__metadata("design:type", file_service_1.FileService)
], MonacoWorkspace.prototype, "fileService", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(browser_1.FileSystemPreferences),
tslib_1.__metadata("design:type", Object)
], MonacoWorkspace.prototype, "filePreferences", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(browser_2.EditorPreferences),
tslib_1.__metadata("design:type", Object)
], MonacoWorkspace.prototype, "editorPreferences", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(monaco_text_model_service_1.MonacoTextModelService),
tslib_1.__metadata("design:type", monaco_text_model_service_1.MonacoTextModelService)
], MonacoWorkspace.prototype, "textModelService", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(browser_2.EditorManager),
tslib_1.__metadata("design:type", browser_2.EditorManager)
], MonacoWorkspace.prototype, "editorManager", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(browser_3.ProblemManager),
tslib_1.__metadata("design:type", browser_3.ProblemManager)
], MonacoWorkspace.prototype, "problems", void 0);
tslib_1.__decorate([
(0, inversify_1.inject)(browser_4.SaveableService),
tslib_1.__metadata("design:type", browser_4.SaveableService)
], MonacoWorkspace.prototype, "saveService", void 0);
tslib_1.__decorate([
(0, inversify_1.postConstruct)(),
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", []),
tslib_1.__metadata("design:returntype", void 0)
], MonacoWorkspace.prototype, "init", null);
exports.MonacoWorkspace = MonacoWorkspace = tslib_1.__decorate([
(0, inversify_1.injectable)()
], MonacoWorkspace);
//# sourceMappingURL=monaco-workspace.js.map