debian-mirror-gitlab/app/assets/javascripts/editor/source_editor.js

245 lines
6.6 KiB
JavaScript
Raw Normal View History

2021-10-27 15:23:28 +05:30
import { editor as monacoEditor, Uri } from 'monaco-editor';
2020-03-13 15:44:24 +05:30
import { defaultEditorOptions } from '~/ide/lib/editor_options';
2021-03-11 19:13:27 +05:30
import languages from '~/ide/lib/languages';
2020-05-24 23:13:21 +05:30
import { registerLanguages } from '~/ide/utils';
2020-10-24 23:57:45 +05:30
import { joinPaths } from '~/lib/utils/url_utility';
2021-06-08 01:23:25 +05:30
import { uuids } from '~/lib/utils/uuids';
2021-03-11 19:13:27 +05:30
import {
2021-09-30 23:02:18 +05:30
SOURCE_EDITOR_INSTANCE_ERROR_NO_EL,
2021-03-11 19:13:27 +05:30
URI_PREFIX,
EDITOR_READY_EVENT,
EDITOR_TYPE_DIFF,
} from './constants';
2021-10-27 15:23:28 +05:30
import { clearDomElement, setupEditorTheme, getBlobLanguage } from './utils';
2020-03-13 15:44:24 +05:30
2021-09-30 23:02:18 +05:30
export default class SourceEditor {
2020-03-13 15:44:24 +05:30
constructor(options = {}) {
2020-11-24 15:15:51 +05:30
this.instances = [];
2020-03-13 15:44:24 +05:30
this.options = {
2021-09-30 23:02:18 +05:30
extraEditorClassName: 'gl-source-editor',
2020-03-13 15:44:24 +05:30
...defaultEditorOptions,
...options,
};
2021-10-27 15:23:28 +05:30
setupEditorTheme();
2020-05-24 23:13:21 +05:30
registerLanguages(...languages);
2020-03-13 15:44:24 +05:30
}
2021-01-03 14:25:43 +05:30
static pushToImportsArray(arr, toImport) {
arr.push(import(toImport));
}
static loadExtensions(extensions) {
if (!extensions) {
return Promise.resolve();
}
const promises = [];
const extensionsArray = typeof extensions === 'string' ? extensions.split(',') : extensions;
2021-03-08 18:12:59 +05:30
extensionsArray.forEach((ext) => {
2021-01-03 14:25:43 +05:30
const prefix = ext.includes('/') ? '' : 'editor/';
const trimmedExt = ext.replace(/^\//, '').trim();
2021-09-30 23:02:18 +05:30
SourceEditor.pushToImportsArray(promises, `~/${prefix}${trimmedExt}`);
2021-01-03 14:25:43 +05:30
});
return Promise.all(promises);
}
2021-02-22 17:27:13 +05:30
static mixIntoInstance(source, inst) {
if (!inst) {
return;
}
const isClassInstance = source.constructor.prototype !== Object.prototype;
const sanitizedSource = isClassInstance ? source.constructor.prototype : source;
2021-03-08 18:12:59 +05:30
Object.getOwnPropertyNames(sanitizedSource).forEach((prop) => {
2021-02-22 17:27:13 +05:30
if (prop !== 'constructor') {
Object.assign(inst, { [prop]: source[prop] });
}
});
}
2021-03-11 19:13:27 +05:30
static prepareInstance(el) {
2020-11-24 15:15:51 +05:30
if (!el) {
2021-09-30 23:02:18 +05:30
throw new Error(SOURCE_EDITOR_INSTANCE_ERROR_NO_EL);
2020-10-24 23:57:45 +05:30
}
2020-11-24 15:15:51 +05:30
clearDomElement(el);
2020-03-13 15:44:24 +05:30
2020-11-24 15:15:51 +05:30
monacoEditor.onDidCreateEditor(() => {
delete el.dataset.editorLoading;
});
2021-03-11 19:13:27 +05:30
}
2020-03-13 15:44:24 +05:30
2021-03-11 19:13:27 +05:30
static manageDefaultExtensions(instance, el, extensions) {
2021-09-30 23:02:18 +05:30
SourceEditor.loadExtensions(extensions, instance)
2021-03-08 18:12:59 +05:30
.then((modules) => {
2021-01-03 14:25:43 +05:30
if (modules) {
2021-03-08 18:12:59 +05:30
modules.forEach((module) => {
2021-01-03 14:25:43 +05:30
instance.use(module.default);
});
}
})
.then(() => {
2021-03-11 19:13:27 +05:30
el.dispatchEvent(new Event(EDITOR_READY_EVENT));
2021-01-03 14:25:43 +05:30
})
2021-03-08 18:12:59 +05:30
.catch((e) => {
2021-01-03 14:25:43 +05:30
throw e;
});
2021-03-11 19:13:27 +05:30
}
static createEditorModel({
blobPath,
blobContent,
blobOriginalContent,
blobGlobalId,
instance,
isDiff,
} = {}) {
if (!instance) {
return null;
}
const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath);
const uri = Uri.file(uriFilePath);
const existingModel = monacoEditor.getModel(uri);
const model = existingModel || monacoEditor.createModel(blobContent, undefined, uri);
if (!isDiff) {
instance.setModel(model);
return model;
}
const diffModel = {
2021-10-27 15:23:28 +05:30
original: monacoEditor.createModel(blobOriginalContent, getBlobLanguage(model.uri.path)),
2021-03-11 19:13:27 +05:30
modified: model,
};
instance.setModel(diffModel);
return diffModel;
}
static convertMonacoToELInstance = (inst) => {
2021-09-30 23:02:18 +05:30
const sourceEditorInstanceAPI = {
2021-03-11 19:13:27 +05:30
updateModelLanguage: (path) => {
2021-09-30 23:02:18 +05:30
return SourceEditor.instanceUpdateLanguage(inst, path);
2021-03-11 19:13:27 +05:30
},
use: (exts = []) => {
2021-09-30 23:02:18 +05:30
return SourceEditor.instanceApplyExtension(inst, exts);
2021-03-11 19:13:27 +05:30
},
};
const handler = {
get(target, prop, receiver) {
2021-09-30 23:02:18 +05:30
if (Reflect.has(sourceEditorInstanceAPI, prop)) {
return sourceEditorInstanceAPI[prop];
2021-03-11 19:13:27 +05:30
}
return Reflect.get(target, prop, receiver);
},
};
return new Proxy(inst, handler);
};
static instanceUpdateLanguage(inst, path) {
2021-10-27 15:23:28 +05:30
const lang = getBlobLanguage(path);
2021-03-11 19:13:27 +05:30
const model = inst.getModel();
return monacoEditor.setModelLanguage(model, lang);
}
static instanceApplyExtension(inst, exts = []) {
const extensions = [].concat(exts);
extensions.forEach((extension) => {
2021-09-30 23:02:18 +05:30
SourceEditor.mixIntoInstance(extension, inst);
2021-03-11 19:13:27 +05:30
});
return inst;
}
static instanceRemoveFromRegistry(editor, instance) {
const index = editor.instances.findIndex((inst) => inst === instance);
editor.instances.splice(index, 1);
}
static instanceDisposeModels(editor, instance, model) {
const instanceModel = instance.getModel() || model;
if (!instanceModel) {
return;
}
if (instance.getEditorType() === EDITOR_TYPE_DIFF) {
const { original, modified } = instanceModel;
if (original) {
original.dispose();
}
if (modified) {
modified.dispose();
}
} else {
instanceModel.dispose();
}
}
/**
* Creates a monaco instance with the given options.
*
* @param {Object} options Options used to initialize monaco.
* @param {Element} options.el The element which will be used to create the monacoEditor.
* @param {string} options.blobPath The path used as the URI of the model. Monaco uses the extension of this path to determine the language.
* @param {string} options.blobContent The content to initialize the monacoEditor.
* @param {string} options.blobGlobalId This is used to help globally identify monaco instances that are created with the same blobPath.
*/
createInstance({
el = undefined,
blobPath = '',
blobContent = '',
blobOriginalContent = '',
blobGlobalId = uuids()[0],
extensions = [],
isDiff = false,
...instanceOptions
} = {}) {
2021-09-30 23:02:18 +05:30
SourceEditor.prepareInstance(el);
2021-03-11 19:13:27 +05:30
const createEditorFn = isDiff ? 'createDiffEditor' : 'create';
2021-09-30 23:02:18 +05:30
const instance = SourceEditor.convertMonacoToELInstance(
2021-03-11 19:13:27 +05:30
monacoEditor[createEditorFn].call(this, el, {
...this.options,
...instanceOptions,
}),
);
let model;
if (instanceOptions.model !== null) {
2021-09-30 23:02:18 +05:30
model = SourceEditor.createEditorModel({
2021-03-11 19:13:27 +05:30
blobGlobalId,
blobOriginalContent,
blobPath,
blobContent,
instance,
isDiff,
});
}
instance.onDidDispose(() => {
2021-09-30 23:02:18 +05:30
SourceEditor.instanceRemoveFromRegistry(this, instance);
SourceEditor.instanceDisposeModels(this, instance, model);
2021-03-11 19:13:27 +05:30
});
2021-09-30 23:02:18 +05:30
SourceEditor.manageDefaultExtensions(instance, el, extensions);
2020-07-28 23:09:34 +05:30
2020-11-24 15:15:51 +05:30
this.instances.push(instance);
return instance;
2020-07-28 23:09:34 +05:30
}
2021-03-11 19:13:27 +05:30
createDiffInstance(args) {
return this.createInstance({
...args,
isDiff: true,
});
}
2020-11-24 15:15:51 +05:30
dispose() {
2021-03-08 18:12:59 +05:30
this.instances.forEach((instance) => instance.dispose());
2020-07-28 23:09:34 +05:30
}
2021-03-11 19:13:27 +05:30
use(exts) {
this.instances.forEach((inst) => {
inst.use(exts);
});
return this;
2020-03-13 15:44:24 +05:30
}
}