2021-08-12 11:09:58 -04:00
|
|
|
import { editor as monacoEditor, Uri } from 'monaco-editor';
|
2021-12-15 10:15:54 -05:00
|
|
|
import { waitForCSSLoaded } from '~/helpers/startup_css_helper';
|
2020-01-29 13:08:47 -05:00
|
|
|
import { defaultEditorOptions } from '~/ide/lib/editor_options';
|
2021-02-14 13:09:20 -05:00
|
|
|
import languages from '~/ide/lib/languages';
|
2020-05-07 11:09:29 -04:00
|
|
|
import { registerLanguages } from '~/ide/utils';
|
2020-08-11 23:10:17 -04:00
|
|
|
import { joinPaths } from '~/lib/utils/url_utility';
|
2021-05-10 08:10:26 -04:00
|
|
|
import { uuids } from '~/lib/utils/uuids';
|
2021-02-03 19:09:18 -05:00
|
|
|
import {
|
2021-06-18 17:10:06 -04:00
|
|
|
SOURCE_EDITOR_INSTANCE_ERROR_NO_EL,
|
2021-02-03 19:09:18 -05:00
|
|
|
URI_PREFIX,
|
|
|
|
EDITOR_READY_EVENT,
|
|
|
|
EDITOR_TYPE_DIFF,
|
|
|
|
} from './constants';
|
2021-08-12 11:09:58 -04:00
|
|
|
import { clearDomElement, setupEditorTheme, getBlobLanguage } from './utils';
|
2021-12-15 10:15:54 -05:00
|
|
|
import EditorInstance from './source_editor_instance';
|
|
|
|
|
|
|
|
const instanceRemoveFromRegistry = (editor, instance) => {
|
|
|
|
const index = editor.instances.findIndex((inst) => inst === instance);
|
|
|
|
editor.instances.splice(index, 1);
|
|
|
|
};
|
|
|
|
|
|
|
|
const 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();
|
|
|
|
}
|
|
|
|
};
|
2020-01-29 13:08:47 -05:00
|
|
|
|
2021-06-18 17:10:06 -04:00
|
|
|
export default class SourceEditor {
|
2021-12-15 10:15:54 -05:00
|
|
|
/**
|
|
|
|
* Constructs a global editor.
|
|
|
|
* @param {Object} options - Monaco config options used to create the editor
|
|
|
|
*/
|
2020-01-29 13:08:47 -05:00
|
|
|
constructor(options = {}) {
|
2020-08-26 05:10:16 -04:00
|
|
|
this.instances = [];
|
2021-12-15 10:15:54 -05:00
|
|
|
this.extensionsStore = new Map();
|
2020-01-29 13:08:47 -05:00
|
|
|
this.options = {
|
2021-06-18 17:10:06 -04:00
|
|
|
extraEditorClassName: 'gl-source-editor',
|
2020-01-29 13:08:47 -05:00
|
|
|
...defaultEditorOptions,
|
|
|
|
...options,
|
|
|
|
};
|
|
|
|
|
2021-08-12 11:09:58 -04:00
|
|
|
setupEditorTheme();
|
2020-05-07 11:09:29 -04:00
|
|
|
|
|
|
|
registerLanguages(...languages);
|
2020-01-29 13:08:47 -05:00
|
|
|
}
|
|
|
|
|
2021-01-25 04:09:10 -05:00
|
|
|
static prepareInstance(el) {
|
|
|
|
if (!el) {
|
2021-06-18 17:10:06 -04:00
|
|
|
throw new Error(SOURCE_EDITOR_INSTANCE_ERROR_NO_EL);
|
2021-01-25 04:09:10 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
clearDomElement(el);
|
|
|
|
|
|
|
|
monacoEditor.onDidCreateEditor(() => {
|
|
|
|
delete el.dataset.editorLoading;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-02-03 19:09:18 -05:00
|
|
|
static createEditorModel({
|
|
|
|
blobPath,
|
|
|
|
blobContent,
|
|
|
|
blobOriginalContent,
|
|
|
|
blobGlobalId,
|
|
|
|
instance,
|
|
|
|
isDiff,
|
|
|
|
} = {}) {
|
2021-01-25 04:09:10 -05:00
|
|
|
if (!instance) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath);
|
|
|
|
const uri = Uri.file(uriFilePath);
|
|
|
|
const existingModel = monacoEditor.getModel(uri);
|
2021-02-03 19:09:18 -05:00
|
|
|
const model = existingModel || monacoEditor.createModel(blobContent, undefined, uri);
|
|
|
|
if (!isDiff) {
|
|
|
|
instance.setModel(model);
|
|
|
|
return model;
|
|
|
|
}
|
|
|
|
const diffModel = {
|
2021-08-12 11:09:58 -04:00
|
|
|
original: monacoEditor.createModel(blobOriginalContent, getBlobLanguage(model.uri.path)),
|
2021-02-03 19:09:18 -05:00
|
|
|
modified: model,
|
|
|
|
};
|
|
|
|
instance.setModel(diffModel);
|
|
|
|
return diffModel;
|
|
|
|
}
|
|
|
|
|
2020-08-11 23:10:17 -04:00
|
|
|
/**
|
2021-12-15 10:15:54 -05:00
|
|
|
* Creates a Source Editor Instance with the given options.
|
|
|
|
* @param {Object} options Options used to initialize the instance.
|
|
|
|
* @param {Element} options.el The element to attach the instance for.
|
2020-08-11 23:10:17 -04:00
|
|
|
* @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.
|
2021-12-15 10:15:54 -05:00
|
|
|
* @param {string} options.blobOriginalContent The original blob's content. Is used when creating a Diff Instance.
|
2020-08-11 23:10:17 -04:00
|
|
|
* @param {string} options.blobGlobalId This is used to help globally identify monaco instances that are created with the same blobPath.
|
2021-12-15 10:15:54 -05:00
|
|
|
* @param {Boolean} options.isDiff Flag to enable creation of a Diff Instance?
|
|
|
|
* @param {...*} options.instanceOptions Configuration options used to instantiate an instance.
|
|
|
|
* @returns {EditorInstance}
|
2020-08-11 23:10:17 -04:00
|
|
|
*/
|
2020-08-26 05:10:16 -04:00
|
|
|
createInstance({
|
|
|
|
el = undefined,
|
|
|
|
blobPath = '',
|
|
|
|
blobContent = '',
|
2021-02-03 19:09:18 -05:00
|
|
|
blobOriginalContent = '',
|
2020-10-28 08:08:40 -04:00
|
|
|
blobGlobalId = uuids()[0],
|
2021-02-03 19:09:18 -05:00
|
|
|
isDiff = false,
|
2020-08-26 05:10:16 -04:00
|
|
|
...instanceOptions
|
|
|
|
} = {}) {
|
2021-06-18 17:10:06 -04:00
|
|
|
SourceEditor.prepareInstance(el);
|
2020-01-29 13:08:47 -05:00
|
|
|
|
2021-02-03 19:09:18 -05:00
|
|
|
const createEditorFn = isDiff ? 'createDiffEditor' : 'create';
|
2021-12-15 10:15:54 -05:00
|
|
|
const instance = new EditorInstance(
|
2021-02-03 19:09:18 -05:00
|
|
|
monacoEditor[createEditorFn].call(this, el, {
|
|
|
|
...this.options,
|
|
|
|
...instanceOptions,
|
|
|
|
}),
|
2021-12-15 10:15:54 -05:00
|
|
|
this.extensionsStore,
|
2021-02-03 19:09:18 -05:00
|
|
|
);
|
2021-01-25 04:09:10 -05:00
|
|
|
|
2021-12-15 10:15:54 -05:00
|
|
|
waitForCSSLoaded(() => {
|
|
|
|
instance.layout();
|
|
|
|
});
|
|
|
|
|
2021-02-03 19:09:18 -05:00
|
|
|
let model;
|
|
|
|
if (instanceOptions.model !== null) {
|
2021-06-18 17:10:06 -04:00
|
|
|
model = SourceEditor.createEditorModel({
|
2021-02-03 19:09:18 -05:00
|
|
|
blobGlobalId,
|
|
|
|
blobOriginalContent,
|
|
|
|
blobPath,
|
|
|
|
blobContent,
|
|
|
|
instance,
|
|
|
|
isDiff,
|
|
|
|
});
|
|
|
|
}
|
2021-01-25 04:09:10 -05:00
|
|
|
|
2020-08-26 05:10:16 -04:00
|
|
|
instance.onDidDispose(() => {
|
2021-12-15 10:15:54 -05:00
|
|
|
instanceRemoveFromRegistry(this, instance);
|
|
|
|
instanceDisposeModels(this, instance, model);
|
2020-08-26 05:10:16 -04:00
|
|
|
});
|
2020-10-08 17:08:52 -04:00
|
|
|
|
2020-08-26 05:10:16 -04:00
|
|
|
this.instances.push(instance);
|
2022-01-10 16:16:38 -05:00
|
|
|
el.dispatchEvent(new CustomEvent(EDITOR_READY_EVENT, { detail: { instance } }));
|
2020-08-26 05:10:16 -04:00
|
|
|
return instance;
|
2020-01-29 13:08:47 -05:00
|
|
|
}
|
|
|
|
|
2021-12-15 10:15:54 -05:00
|
|
|
/**
|
|
|
|
* Create a Diff Instance
|
|
|
|
* @param {Object} args Options to be passed further down to createInstance() with the same signature
|
|
|
|
* @returns {EditorInstance}
|
|
|
|
*/
|
2021-02-03 19:09:18 -05:00
|
|
|
createDiffInstance(args) {
|
|
|
|
return this.createInstance({
|
|
|
|
...args,
|
|
|
|
isDiff: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-12-15 10:15:54 -05:00
|
|
|
/**
|
|
|
|
* Dispose global editor
|
|
|
|
* Automatically disposes all the instances registered for this editor
|
|
|
|
*/
|
2020-01-29 13:08:47 -05:00
|
|
|
dispose() {
|
2020-12-23 16:10:24 -05:00
|
|
|
this.instances.forEach((instance) => instance.dispose());
|
2020-01-29 13:08:47 -05:00
|
|
|
}
|
|
|
|
}
|