All files / app/assets/javascripts/editor source_editor.js

94.33% Statements 50/53
87.87% Branches 29/33
100% Functions 12/12
94.11% Lines 48/51

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176                              34x 169x 169x     34x 169x 169x 44x   125x 12x 12x     12x       113x                   247x 247x 247x           247x   247x       234x 1x     233x   233x 4181x                         180x     180x 180x 180x 180x 180x 173x 173x   7x       7x 7x                                               234x   233x 233x               233x     233x 233x 180x                     233x 169x 169x     233x 233x 233x                 17x                     92x      
import { editor as monacoEditor, Uri } from 'monaco-editor';
import { defaultEditorOptions } from '~/ide/lib/editor_options';
import languages from '~/ide/lib/languages';
import { registerLanguages } from '~/ide/utils';
import { joinPaths } from '~/lib/utils/url_utility';
import { uuids } from '~/lib/utils/uuids';
import {
  SOURCE_EDITOR_INSTANCE_ERROR_NO_EL,
  URI_PREFIX,
  EDITOR_READY_EVENT,
  EDITOR_TYPE_DIFF,
} from './constants';
import { clearDomElement, setupEditorTheme, getBlobLanguage } from './utils';
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;
    Iif (original) {
      original.dispose();
    }
    Iif (modified) {
      modified.dispose();
    }
  } else {
    instanceModel.dispose();
  }
};
 
export default class SourceEditor {
  /**
   * Constructs a global editor.
   * @param {Object} options - Monaco config options used to create the editor
   */
  constructor(options = {}) {
    this.instances = [];
    this.extensionsStore = new Map();
    this.options = {
      extraEditorClassName: 'gl-source-editor',
      ...defaultEditorOptions,
      ...options,
    };
 
    setupEditorTheme();
 
    registerLanguages(...languages);
  }
 
  static prepareInstance(el) {
    if (!el) {
      throw new Error(SOURCE_EDITOR_INSTANCE_ERROR_NO_EL);
    }
 
    clearDomElement(el);
 
    monacoEditor.onDidCreateEditor(() => {
      delete el.dataset.editorLoading;
    });
  }
 
  static createEditorModel({
    blobPath,
    blobContent,
    blobOriginalContent,
    blobGlobalId,
    instance,
    isDiff,
    language,
  } = {}) {
    Iif (!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, language, uri);
    if (!isDiff) {
      instance.setModel(model);
      return model;
    }
    const diffModel = {
      original: monacoEditor.createModel(blobOriginalContent, getBlobLanguage(model.uri.path)),
      modified: model,
    };
    instance.setModel(diffModel);
    return diffModel;
  }
 
  /**
   * 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.
   * @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.blobOriginalContent The original blob's content. Is used when creating a Diff Instance.
   * @param {string} options.blobGlobalId This is used to help globally identify monaco instances that are created with the same blobPath.
   * @param {Boolean} options.isDiff Flag to enable creation of a Diff Instance?
   * @param {...*} options.instanceOptions Configuration options used to instantiate an instance.
   * @returns {EditorInstance}
   */
  createInstance({
    el = undefined,
    blobPath = '',
    blobContent = '',
    blobOriginalContent = '',
    blobGlobalId = uuids()[0],
    isDiff = false,
    ...instanceOptions
  } = {}) {
    SourceEditor.prepareInstance(el);
 
    const createEditorFn = isDiff ? 'createDiffEditor' : 'create';
    const instance = new EditorInstance(
      monacoEditor[createEditorFn].call(this, el, {
        ...this.options,
        ...instanceOptions,
      }),
      this.extensionsStore,
    );
 
    instance.layout();
 
    let model;
    const language = instanceOptions.language || getBlobLanguage(blobPath);
    if (instanceOptions.model !== null) {
      model = SourceEditor.createEditorModel({
        blobGlobalId,
        blobOriginalContent,
        blobPath,
        blobContent,
        instance,
        isDiff,
        language,
      });
    }
 
    instance.onDidDispose(() => {
      instanceRemoveFromRegistry(this, instance);
      instanceDisposeModels(this, instance, model);
    });
 
    this.instances.push(instance);
    el.dispatchEvent(new CustomEvent(EDITOR_READY_EVENT, { detail: { instance } }));
    return instance;
  }
 
  /**
   * Create a Diff Instance
   * @param {Object} args Options to be passed further down to createInstance() with the same signature
   * @returns {EditorInstance}
   */
  createDiffInstance(args) {
    return this.createInstance({
      ...args,
      isDiff: true,
    });
  }
 
  /**
   * Dispose global editor
   * Automatically disposes all the instances registered for this editor
   */
  dispose() {
    this.instances.forEach((instance) => instance.dispose());
  }
}