gitlab-org--gitlab-foss/app/assets/javascripts/ide/lib/editor.js

240 lines
5.9 KiB
JavaScript

import { debounce } from 'lodash';
import { editor as monacoEditor, KeyCode, KeyMod, Range } from 'monaco-editor';
import DecorationsController from './decorations/controller';
import DirtyDiffController from './diff/controller';
import Disposable from './common/disposable';
import ModelManager from './common/model_manager';
import { editorOptions, defaultEditorOptions, defaultDiffEditorOptions } from './editor_options';
import { themes } from './themes';
import languages from './languages';
import keymap from './keymap.json';
import { clearDomElement } from '~/editor/utils';
import { registerLanguages } from '../utils';
function setupThemes() {
themes.forEach(theme => {
monacoEditor.defineTheme(theme.name, theme.data);
});
}
export default class Editor {
static create(...args) {
if (!this.editorInstance) {
this.editorInstance = new Editor(...args);
}
return this.editorInstance;
}
constructor(store, options = {}) {
this.currentModel = null;
this.instance = null;
this.dirtyDiffController = null;
this.disposable = new Disposable();
this.modelManager = new ModelManager();
this.decorationsController = new DecorationsController(this);
this.options = {
...defaultEditorOptions,
...options,
};
this.diffOptions = {
...defaultDiffEditorOptions,
...options,
};
this.store = store;
setupThemes();
registerLanguages(...languages);
this.debouncedUpdate = debounce(() => {
this.updateDimensions();
}, 200);
}
createInstance(domElement) {
if (!this.instance) {
clearDomElement(domElement);
this.disposable.add(
(this.instance = monacoEditor.create(domElement, {
...this.options,
})),
(this.dirtyDiffController = new DirtyDiffController(
this.modelManager,
this.decorationsController,
)),
);
this.addCommands();
window.addEventListener('resize', this.debouncedUpdate, false);
}
}
createDiffInstance(domElement) {
if (!this.instance) {
clearDomElement(domElement);
this.disposable.add(
(this.instance = monacoEditor.createDiffEditor(domElement, {
...this.diffOptions,
renderSideBySide: Editor.renderSideBySide(domElement),
})),
);
this.addCommands();
window.addEventListener('resize', this.debouncedUpdate, false);
}
}
createModel(file, head = null) {
return this.modelManager.addModel(file, head);
}
attachModel(model) {
if (this.isDiffEditorType) {
this.instance.setModel({
original: model.getOriginalModel(),
modified: model.getModel(),
});
return;
}
this.instance.setModel(model.getModel());
if (this.dirtyDiffController) this.dirtyDiffController.attachModel(model);
this.currentModel = model;
this.instance.updateOptions(
editorOptions.reduce((acc, obj) => {
Object.keys(obj).forEach(key => {
Object.assign(acc, {
[key]: obj[key](model),
});
});
return acc;
}, {}),
);
if (this.dirtyDiffController) this.dirtyDiffController.reDecorate(model);
}
attachMergeRequestModel(model) {
this.instance.setModel({
original: model.getBaseModel(),
modified: model.getModel(),
});
monacoEditor.createDiffNavigator(this.instance, {
alwaysRevealFirst: true,
});
}
clearEditor() {
if (this.instance) {
this.instance.setModel(null);
}
}
dispose() {
window.removeEventListener('resize', this.debouncedUpdate);
// catch any potential errors with disposing the error
// this is mainly for tests caused by elements not existing
try {
this.disposable.dispose();
this.instance = null;
} catch (e) {
this.instance = null;
if (process.env.NODE_ENV !== 'test') {
// eslint-disable-next-line no-console
console.error(e);
}
}
}
updateDimensions() {
this.instance.layout();
this.updateDiffView();
}
setPosition({ lineNumber, column }) {
this.instance.revealPositionInCenter({
lineNumber,
column,
});
this.instance.setPosition({
lineNumber,
column,
});
}
onPositionChange(cb) {
if (!this.instance.onDidChangeCursorPosition) return;
this.disposable.add(this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)));
}
updateDiffView() {
if (!this.isDiffEditorType) return;
this.instance.updateOptions({
renderSideBySide: Editor.renderSideBySide(this.instance.getDomNode()),
});
}
replaceSelectedText(text) {
let selection = this.instance.getSelection();
const range = new Range(
selection.startLineNumber,
selection.startColumn,
selection.endLineNumber,
selection.endColumn,
);
this.instance.executeEdits('', [{ range, text }]);
selection = this.instance.getSelection();
this.instance.setPosition({ lineNumber: selection.endLineNumber, column: selection.endColumn });
}
get isDiffEditorType() {
return this.instance.getEditorType() === 'vs.editor.IDiffEditor';
}
static renderSideBySide(domElement) {
return domElement.offsetWidth >= 700;
}
addCommands() {
const { store } = this;
const getKeyCode = key => {
const monacoKeyMod = key.indexOf('KEY_') === 0;
return monacoKeyMod ? KeyCode[key] : KeyMod[key];
};
keymap.forEach(command => {
const keybindings = command.bindings.map(binding => {
const keys = binding.split('+');
// eslint-disable-next-line no-bitwise
return keys.length > 1 ? getKeyCode(keys[0]) | getKeyCode(keys[1]) : getKeyCode(keys[0]);
});
this.instance.addAction({
id: command.id,
label: command.label,
keybindings,
run() {
store.dispatch(command.action.name, command.action.params);
return null;
},
});
});
}
}