diff options
Diffstat (limited to 'app/assets/javascripts/editor/editor_lite.js')
-rw-r--r-- | app/assets/javascripts/editor/editor_lite.js | 221 |
1 files changed, 163 insertions, 58 deletions
diff --git a/app/assets/javascripts/editor/editor_lite.js b/app/assets/javascripts/editor/editor_lite.js index 1808f968b8c..79beb3a4857 100644 --- a/app/assets/javascripts/editor/editor_lite.js +++ b/app/assets/javascripts/editor/editor_lite.js @@ -1,12 +1,17 @@ import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor'; -import { DEFAULT_THEME, themes } from '~/ide/lib/themes'; -import languages from '~/ide/lib/languages'; +import { uuids } from '~/diffs/utils/uuids'; import { defaultEditorOptions } from '~/ide/lib/editor_options'; +import languages from '~/ide/lib/languages'; +import { DEFAULT_THEME, themes } from '~/ide/lib/themes'; import { registerLanguages } from '~/ide/utils'; import { joinPaths } from '~/lib/utils/url_utility'; +import { + EDITOR_LITE_INSTANCE_ERROR_NO_EL, + URI_PREFIX, + EDITOR_READY_EVENT, + EDITOR_TYPE_DIFF, +} from './constants'; import { clearDomElement } from './utils'; -import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from './constants'; -import { uuids } from '~/diffs/utils/uuids'; export default class EditorLite { constructor(options = {}) { @@ -29,15 +34,12 @@ export default class EditorLite { monacoEditor.setTheme(theme ? themeName : DEFAULT_THEME); } - static updateModelLanguage(path, instance) { - if (!instance) return; - const model = instance.getModel(); + static getModelLanguage(path) { const ext = `.${path.split('.').pop()}`; const language = monacoLanguages .getLanguages() .find((lang) => lang.extensions.indexOf(ext) !== -1); - const id = language ? language.id : 'plaintext'; - monacoEditor.setModelLanguage(model, id); + return language ? language.id : 'plaintext'; } static pushToImportsArray(arr, toImport) { @@ -73,50 +75,19 @@ export default class EditorLite { }); } - /** - * 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 = '', - blobGlobalId = uuids()[0], - extensions = [], - ...instanceOptions - } = {}) { + static prepareInstance(el) { if (!el) { throw new Error(EDITOR_LITE_INSTANCE_ERROR_NO_EL); } clearDomElement(el); - const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath); - - const model = monacoEditor.createModel(blobContent, undefined, Uri.file(uriFilePath)); - monacoEditor.onDidCreateEditor(() => { delete el.dataset.editorLoading; }); + } - const instance = monacoEditor.create(el, { - ...this.options, - ...instanceOptions, - }); - instance.setModel(model); - instance.onDidDispose(() => { - const index = this.instances.findIndex((inst) => inst === instance); - this.instances.splice(index, 1); - model.dispose(); - }); - instance.updateModelLanguage = (path) => EditorLite.updateModelLanguage(path, instance); - instance.use = (args) => this.use(args, instance); - + static manageDefaultExtensions(instance, el, extensions) { EditorLite.loadExtensions(extensions, instance) .then((modules) => { if (modules) { @@ -126,33 +97,167 @@ export default class EditorLite { } }) .then(() => { - el.dispatchEvent(new Event('editor-ready')); + el.dispatchEvent(new Event(EDITOR_READY_EVENT)); }) .catch((e) => { throw e; }); + } + + 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 = { + original: monacoEditor.createModel( + blobOriginalContent, + EditorLite.getModelLanguage(model.uri.path), + ), + modified: model, + }; + instance.setModel(diffModel); + return diffModel; + } + + static convertMonacoToELInstance = (inst) => { + const editorLiteInstanceAPI = { + updateModelLanguage: (path) => { + return EditorLite.instanceUpdateLanguage(inst, path); + }, + use: (exts = []) => { + return EditorLite.instanceApplyExtension(inst, exts); + }, + }; + const handler = { + get(target, prop, receiver) { + if (Reflect.has(editorLiteInstanceAPI, prop)) { + return editorLiteInstanceAPI[prop]; + } + return Reflect.get(target, prop, receiver); + }, + }; + return new Proxy(inst, handler); + }; + + static instanceUpdateLanguage(inst, path) { + const lang = EditorLite.getModelLanguage(path); + const model = inst.getModel(); + return monacoEditor.setModelLanguage(model, lang); + } + + static instanceApplyExtension(inst, exts = []) { + const extensions = [].concat(exts); + extensions.forEach((extension) => { + EditorLite.mixIntoInstance(extension, inst); + }); + 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 + } = {}) { + EditorLite.prepareInstance(el); + + const createEditorFn = isDiff ? 'createDiffEditor' : 'create'; + const instance = EditorLite.convertMonacoToELInstance( + monacoEditor[createEditorFn].call(this, el, { + ...this.options, + ...instanceOptions, + }), + ); + + let model; + if (instanceOptions.model !== null) { + model = EditorLite.createEditorModel({ + blobGlobalId, + blobOriginalContent, + blobPath, + blobContent, + instance, + isDiff, + }); + } + + instance.onDidDispose(() => { + EditorLite.instanceRemoveFromRegistry(this, instance); + EditorLite.instanceDisposeModels(this, instance, model); + }); + + EditorLite.manageDefaultExtensions(instance, el, extensions); this.instances.push(instance); return instance; } + createDiffInstance(args) { + return this.createInstance({ + ...args, + isDiff: true, + }); + } + dispose() { this.instances.forEach((instance) => instance.dispose()); } - use(exts = [], instance = null) { - const extensions = Array.isArray(exts) ? exts : [exts]; - const initExtensions = (inst) => { - extensions.forEach((extension) => { - EditorLite.mixIntoInstance(extension, inst); - }); - }; - if (instance) { - initExtensions(instance); - } else { - this.instances.forEach((inst) => { - initExtensions(inst); - }); - } + use(exts) { + this.instances.forEach((inst) => { + inst.use(exts); + }); + return this; } } |